-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwebhook_send.py
200 lines (170 loc) · 7.01 KB
/
webhook_send.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
"""
This plugin uses the apprise python module to allow sending
the output of u-u in json format to almost any webhook you want.
You may configure parts of this plugin by using the following
variables which should be placed in /etc/apt/apt.conf.d/51uu-apprise.
<code>
// Webhooks
// Enable Webhooks: true, false
//Unattended-Upgrade::Webhook "false";
// Where do you want to send the json?
//Unattended-Upgrade::WebhookUrl {
// "";
// "";
//};
// When the webhook report should be sent.
// Set this value to one of: "always", "only-on-error" or "on-change"
//Unattended-Upgrade::WebhookReport "only-on-error";
// Set character limit for the JSON payload
// Default: 2000; If set to 0 there is no limit.
//Unattended-Upgrade::WebhookCharacterLimit 2000;
</code>
To install apprise you may use python pip or apt.
* python3 -m pip install apprise
* apt-get install apprise # Available since Debian 12 Bookworm
For Developers:
* "plugin_api": the API version as string of the form "1.0"
* "hostname": The hostname of the machine that run u-u.
* "success": A boolean that indicates if the run was successful
* "result": A string with a human readable (and translated) status message
* "packages_upgraded": A list of packages that got upgraded.
* "packages_kept_back": A list of packages kept back.
* "packages_kept_installed": A list of packages not auto-removed.
* "reboot_required": Indicates a reboot is required.
* "log_dpkg": The full dpkg log output.
* "log_unattended_upgrades": The full unattended-upgrades log.
"""
import apt_pkg
import json
import logging
import logging.handlers
logged_msgs = set() # type: AbstractSet[str]
def log_once(msg):
# type: (str) -> None
global logged_msgs
if msg not in logged_msgs:
logging.info(msg)
logged_msgs.add(msg) # type: ignore
try:
import apprise
except NameError:
log_once(_("Notifiying Webhook is skipped. Please "
"install the python module apprise to send webhooks."))
# Note the plugin name must start with UnattendedUpgradesPlugin*
class UnattendedUpgradesPluginApprise:
"""Apprise plugin for unattended-upgrades"""
def __init__(self):
self.webhooks = self.get_webhookurls()
self.charlimit = self.get_charlimit()
self.reporttype = self.get_reporttype()
self.is_enabled = self.get_enabled()
def postrun(self, result):
"""
The postrun function is run after an unattended-upgrades run
that generated some result. The result is a dict that is
kept backward compatible.
"""
# The data in result is a simple python dict that can easily
# be serialized to json. It contains information like what
# packages got upgraded, removed and kept back. Also the
# full u-u log and the dpkg log (if any).
if self.is_enabled:
self.send_summary_webhook(result)
def get_reporttype(self):
"""
Returns the value from Unattended-Upgrade::WebhookReport
If the value does not exist or its a value it does not recognize
it returns 'only-on-error'
Default: only-on-error
"""
val = apt_pkg.config.find("Unattended-Upgrade::WebhookReport",
"only-on-error")
if val in ['always', 'only-on-error', 'on-change']:
return val
else:
return "only-on-error"
def get_enabled(self):
"""
Boolean enables or disables this plugin
Default: disabled == False
"""
return apt_pkg.config.find_b("Unattended-Upgrade::Webhook", False)
def get_charlimit(self):
"""
Set a character limit for the payload.
Default: 2000
"""
return int(apt_pkg.config.find("Unattended-Upgrade::WebhookCharacterLimit", "2000"))
def get_webhookurls(self):
""" Return a list of webhook urls from apt.conf
"""
webhook_urls = []
key = "Unattended-Upgrade::WebhookUrl"
try:
# apt_pkg.config.value_list(key): returns two of the same value.
# work around make the list a set to make the urls unique.
for s in set(apt_pkg.config.value_list(key)):
webhook_urls.append(s)
except ValueError:
logging.error(_("Unable to parse %s." % key))
raise
return webhook_urls
def create_payload(self, data):
"""
Create the payload that will put into the webhook
Usually this is just a string with information
"""
# This is a weak attempt at ensuring we can send something to the webhook
# in case we go over a character limit.
if (len(str(data)) > self.charlimit) and (self.charlimit > 0):
# Replacing data['log_dpkg'] and data['log_unattended_upgrades']
# It's more likely the character limit will be met
data['log_dpkg'] = "R"
data['log_unattended_upgrades'] = "R"
payload = json.dumps(data)
# If we are still above the limit set by the user: stop and log the failure.
# It is up to the user to set a reasonable value.
# The default value is meant to be reasonable for most use cases.
if len(payload) > self.charlimit:
log_once(_("Notifiying Webhook is skipped. Please "
"set the character limit to higher value."))
return False
else:
return payload
else:
return json.dumps(data)
def webhook_notify(self, data):
payload = self.create_payload(data)
if payload:
# Create an Apprise instance
apobj = apprise.Apprise()
# Add all of the notification services by their server url.
for url in self.webhooks:
apobj.add(url)
# Then notify these services any time you desire. The below would
# notify all of the services loaded into our Apprise object.
apobj.notify(
body=payload,
title='Unattended-Upgrades',
)
else:
# TODO 20230105 Maybe we want to actually do something if its false
pass
def send_summary_webhook(self, data):
# If webhook is not enabled or there is no webhook url, exit here
if (not self.is_enabled) or (not self.webhooks):
return
# If the operation was successful and the user has requested to get
# webhooks only on errors, just exit here
if (data['success'] and (self.reporttype == "only-on-error")):
return
# If the run was successful but nothing had to be done skip sending the webhook
# unless the admin wants it anyway
if (((self.reporttype != "always") and data['success']
and not data['packages_upgraded']
and not data['packages_kept_back']
and not data['packages_kept_installed']
and not data['reboot_required'])):
return
# Notify webhook
self.webhook_notify(data)