Merge ~robert-ancell/software-properties:esm-bionic into software-properties:ubuntu/bionic
- Git
- lp:~robert-ancell/software-properties
- esm-bionic
- Merge into ubuntu/bionic
Proposed by
Robert Ancell
Status: | Needs review |
---|---|
Proposed branch: | ~robert-ancell/software-properties:esm-bionic |
Merge into: | software-properties:ubuntu/bionic |
Diff against target: |
356 lines (+233/-6) 4 files modified
data/gtkbuilder/main.ui (+106/-6) debian/changelog (+6/-0) softwareproperties/gtk/SoftwarePropertiesGtk.py (+56/-0) softwareproperties/gtk/utils.py (+65/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Core Development Team | Pending | ||
Review via email: mp+400304@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Unmerged commits
- b8e712d... by Robert Ancell
-
Show extended security maintenance status
Also worked on by Chad Smith.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/data/gtkbuilder/main.ui b/data/gtkbuilder/main.ui |
2 | index 642e45c..7df32b9 100644 |
3 | --- a/data/gtkbuilder/main.ui |
4 | +++ b/data/gtkbuilder/main.ui |
5 | @@ -550,7 +550,18 @@ |
6 | <packing> |
7 | <property name="expand">False</property> |
8 | <property name="fill">True</property> |
9 | - <property name="position">0</property> |
10 | + </packing> |
11 | + </child> |
12 | + <child> |
13 | + <object class="GtkLabel"> |
14 | + <property name="visible">True</property> |
15 | + <property name="can_focus">False</property> |
16 | + <property name="label" translatable="yes">Snap package updates are checked routinely and installed automatically.</property> |
17 | + <property name="xalign">0</property> |
18 | + </object> |
19 | + <packing> |
20 | + <property name="expand">False</property> |
21 | + <property name="fill">False</property> |
22 | </packing> |
23 | </child> |
24 | <child> |
25 | @@ -565,6 +576,98 @@ |
26 | <property name="can_focus">False</property> |
27 | <property name="spacing">6</property> |
28 | <child> |
29 | + <object class="GtkHBox"> |
30 | + <property name="visible">True</property> |
31 | + <property name="can_focus">False</property> |
32 | + <property name="spacing">6</property> |
33 | + <child> |
34 | + <object class="GtkLabel" id="label_esm_heading"> |
35 | + <property name="visible">True</property> |
36 | + <property name="can_focus">False</property> |
37 | + <property name="xalign">1</property> |
38 | + <property name="label" translatable="yes">For other packages, this system has:</property> |
39 | + </object> |
40 | + <packing> |
41 | + <property name="expand">False</property> |
42 | + <property name="fill">False</property> |
43 | + <property name="position">0</property> |
44 | + </packing> |
45 | + </child> |
46 | + <child> |
47 | + <object class="GtkHBox"> |
48 | + <property name="visible">True</property> |
49 | + <property name="can_focus">False</property> |
50 | + <property name="spacing">6</property> |
51 | + <child> |
52 | + <object class="GtkLabel" id="label_esm_status"> |
53 | + <property name="visible">True</property> |
54 | + <property name="can_focus">False</property> |
55 | + <property name="xalign">0</property> |
56 | + </object> |
57 | + <packing> |
58 | + <property name="expand">False</property> |
59 | + <property name="fill">True</property> |
60 | + </packing> |
61 | + </child> |
62 | + <child> |
63 | + <object class="GtkLabel" id="label_esm_subscribe"> |
64 | + <property name="visible">True</property> |
65 | + <property name="can_focus">False</property> |
66 | + <property name="xalign">1</property> |
67 | + </object> |
68 | + <packing> |
69 | + <property name="expand">True</property> |
70 | + <property name="fill">True</property> |
71 | + </packing> |
72 | + </child> |
73 | + </object> |
74 | + <packing> |
75 | + <property name="expand">True</property> |
76 | + <property name="fill">True</property> |
77 | + <property name="position">1</property> |
78 | + </packing> |
79 | + </child> |
80 | + </object> |
81 | + <packing> |
82 | + <property name="expand">False</property> |
83 | + <property name="fill">False</property> |
84 | + </packing> |
85 | + </child> |
86 | + <child> |
87 | + <object class="GtkHBox"> |
88 | + <property name="visible">True</property> |
89 | + <property name="can_focus">False</property> |
90 | + <property name="spacing">6</property> |
91 | + <child> |
92 | + <object class="GtkLabel" id="label_eol_heading"> |
93 | + <property name="visible">True</property> |
94 | + <property name="can_focus">False</property> |
95 | + </object> |
96 | + <packing> |
97 | + <property name="expand">False</property> |
98 | + <property name="fill">False</property> |
99 | + <property name="position">0</property> |
100 | + </packing> |
101 | + </child> |
102 | + <child> |
103 | + <object class="GtkLabel" id="label_eol"> |
104 | + <property name="visible">True</property> |
105 | + <property name="can_focus">False</property> |
106 | + <property name="xalign">0</property> |
107 | + </object> |
108 | + <packing> |
109 | + <property name="expand">True</property> |
110 | + <property name="fill">True</property> |
111 | + <property name="position">1</property> |
112 | + </packing> |
113 | + </child> |
114 | + </object> |
115 | + <packing> |
116 | + <property name="expand">False</property> |
117 | + <property name="fill">False</property> |
118 | + </packing> |
119 | + </child> |
120 | + <child> |
121 | <object class="GtkHBox" id="hbox_check_for_updates"> |
122 | <property name="visible">True</property> |
123 | <property name="can_focus">False</property> |
124 | @@ -604,7 +707,6 @@ |
125 | <packing> |
126 | <property name="expand">False</property> |
127 | <property name="fill">False</property> |
128 | - <property name="position">0</property> |
129 | </packing> |
130 | </child> |
131 | <child> |
132 | @@ -647,7 +749,6 @@ |
133 | <packing> |
134 | <property name="expand">False</property> |
135 | <property name="fill">False</property> |
136 | - <property name="position">1</property> |
137 | </packing> |
138 | </child> |
139 | <child> |
140 | @@ -690,7 +791,6 @@ |
141 | <packing> |
142 | <property name="expand">False</property> |
143 | <property name="fill">False</property> |
144 | - <property name="position">2</property> |
145 | </packing> |
146 | </child> |
147 | </object> |
148 | @@ -699,7 +799,6 @@ |
149 | <packing> |
150 | <property name="expand">True</property> |
151 | <property name="fill">True</property> |
152 | - <property name="position">1</property> |
153 | </packing> |
154 | </child> |
155 | <child> |
156 | @@ -749,7 +848,6 @@ |
157 | <packing> |
158 | <property name="expand">False</property> |
159 | <property name="fill">False</property> |
160 | - <property name="position">2</property> |
161 | </packing> |
162 | </child> |
163 | </object> |
164 | @@ -1406,6 +1504,8 @@ |
165 | </object> |
166 | <object class="GtkSizeGroup" id="sizegroup1"> |
167 | <widgets> |
168 | + <widget name="label_esm_heading"/> |
169 | + <widget name="label_eol_heading"/> |
170 | <widget name="label3"/> |
171 | <widget name="label4"/> |
172 | <widget name="label5"/> |
173 | diff --git a/debian/changelog b/debian/changelog |
174 | index 0079494..463fc2c 100644 |
175 | --- a/debian/changelog |
176 | +++ b/debian/changelog |
177 | @@ -1,3 +1,9 @@ |
178 | +software-properties (0.96.24.32.15) UNRELEASED; urgency=medium |
179 | + |
180 | + * Show ESM support status (LP: #1920836) |
181 | + |
182 | + -- Robert Ancell <robert.ancell@canonical.com> Tue, 23 Mar 2021 16:44:44 +1300 |
183 | + |
184 | software-properties (0.96.24.32.14) bionic-security; urgency=medium |
185 | |
186 | * SECURITY UPDATE: malicious repo could send ANSI sequences to terminal |
187 | diff --git a/softwareproperties/gtk/SoftwarePropertiesGtk.py b/softwareproperties/gtk/SoftwarePropertiesGtk.py |
188 | index 9444e55..867b7a8 100644 |
189 | --- a/softwareproperties/gtk/SoftwarePropertiesGtk.py |
190 | +++ b/softwareproperties/gtk/SoftwarePropertiesGtk.py |
191 | @@ -27,6 +27,7 @@ from __future__ import absolute_import, print_function |
192 | |
193 | import apt |
194 | import apt_pkg |
195 | +import datetime |
196 | import dbus |
197 | from gettext import gettext as _ |
198 | import gettext |
199 | @@ -57,6 +58,13 @@ import softwareproperties.distro |
200 | from softwareproperties.SoftwareProperties import SoftwareProperties |
201 | import softwareproperties.SoftwareProperties |
202 | |
203 | +from softwareproperties.gtk.utils import ( |
204 | + get_ua_status, |
205 | + get_ua_service_status, |
206 | + current_distro, |
207 | + is_current_distro_lts, |
208 | +) |
209 | + |
210 | from UbuntuDrivers import detect |
211 | |
212 | if GLib.pyglib_version < (3, 9, 1): |
213 | @@ -369,6 +377,54 @@ class SoftwarePropertiesGtk(SoftwareProperties, SimpleGtkbuilderApp): |
214 | self.vbox_updates.add(checkbox) |
215 | checkbox.show() |
216 | |
217 | + status = get_ua_status() |
218 | + if not is_current_distro_lts(): |
219 | + esm_available = False |
220 | + esm_enabled = False |
221 | + else: |
222 | + (infra_available, infra_status) = get_ua_service_status("esm-infra", status=status) |
223 | + (apps_available, apps_status) = get_ua_service_status("esm-apps", status=status) |
224 | + esm_available = bool(infra_available or apps_available) |
225 | + esm_enabled = "enabled" in (infra_status, apps_status) |
226 | + distro = current_distro() |
227 | + if esm_enabled: |
228 | + eol_text = _("Extended Security Maintenance") |
229 | + # EOL date should probably be UA contract expiry. |
230 | + # This is probably sooner than ESM EOL for the distro and |
231 | + # gives software properties dialogs a chance to interact about |
232 | + # renewals if needed. |
233 | + try: |
234 | + eol_date = datetime.datetime.strptime( |
235 | + status.get("expires"), "%Y-%m-%dT%H:%M:%S" |
236 | + ).date() |
237 | + except ValueError: |
238 | + print("Unable to determine UA contract expiry") |
239 | + eol_date = distro.eol |
240 | + else: |
241 | + eol_text = _("Basic Security Maintenance") |
242 | + eol_date = distro.eol |
243 | + self.label_esm_status.set_markup(eol_text) |
244 | + esm_url = "https://ubuntu.com/esm" # Non-EOL LTS generic ESM |
245 | + today = datetime.datetime.now().date() |
246 | + if today >= eol_date: |
247 | + if esm_available: |
248 | + # EOL LTS uses release-specific ESM ubuntu.com/XX-YY |
249 | + distro_ver = distro.version.replace(' LTS', '') |
250 | + esm_url = "https://ubuntu.com/%s" % distro_ver.replace(".", "-") |
251 | + eol_expiry_text = _("Ended %s - extend or upgrade now") % eol_date.strftime("%x") |
252 | + elif today >= eol_date - datetime.timedelta(days=60): |
253 | + eol_expiry_text = _("Ends %s - extend or upgrade soon") % eol_date.strftime("%x") |
254 | + else: |
255 | + eol_expiry_text = _("Active until %s") % eol_date.strftime("%x") |
256 | + self.label_eol.set_label(eol_expiry_text) |
257 | + self.label_esm_subscribe.set_markup( |
258 | + "<a href=\"%s\">%s</a>" % (esm_url, _("Extend…")) |
259 | + ) |
260 | + self.label_esm_subscribe.set_visible( |
261 | + esm_available and not esm_enabled |
262 | + ) |
263 | + eol_expiry_text = _("Ended %s") % eol_date.strftime("%x") |
264 | + |
265 | # setup the server chooser |
266 | cell = Gtk.CellRendererText() |
267 | self.combobox_server.pack_start(cell, True) |
268 | diff --git a/softwareproperties/gtk/utils.py b/softwareproperties/gtk/utils.py |
269 | index e0ddca9..0061785 100644 |
270 | --- a/softwareproperties/gtk/utils.py |
271 | +++ b/softwareproperties/gtk/utils.py |
272 | @@ -25,12 +25,16 @@ from functools import wraps |
273 | import gi |
274 | gi.require_version("Gtk", "3.0") |
275 | from gi.repository import Gio, Gtk |
276 | +import json |
277 | +import subprocess |
278 | |
279 | import logging |
280 | LOG=logging.getLogger(__name__) |
281 | |
282 | import time |
283 | |
284 | +UA_STATUS_JSON = "/var/lib/ubuntu-advantage/status.json" |
285 | + |
286 | def setup_ui(self, path, domain): |
287 | # setup ui |
288 | self.builder = Gtk.Builder() |
289 | @@ -61,6 +65,67 @@ def is_current_distro_supported(): |
290 | di = distro_info.UbuntuDistroInfo() |
291 | return distro.codename in di.supported(datetime.now().date()) |
292 | |
293 | +def current_distro(): |
294 | + distro = aptsources.distro.get_distro() |
295 | + di = distro_info.UbuntuDistroInfo() |
296 | + releases = di.get_all(result="object") |
297 | + for release in releases: |
298 | + if release.series == distro.codename: |
299 | + return release |
300 | + |
301 | +def get_ua_status(): |
302 | + """Return a dict of all UA status information or empty dict on error.""" |
303 | + # status.json will exist on any attached system or any unattached system |
304 | + # which has already run `ua status`. Calling ua status directly on |
305 | + # network disconnected machines will raise a TimeoutException trying to |
306 | + # access contracts.canonical.com/v1/resources. |
307 | + try: |
308 | + # Success writes UA_STATUS_JSON |
309 | + result = subprocess.run(['ua', 'status', '--format=json'], stdout=subprocess.PIPE) |
310 | + except Exception as e: |
311 | + print("Failed to call ubuntu advantage client:\n%s" % e) |
312 | + return {} |
313 | + if result.returncode != 0: |
314 | + print("Ubuntu advantage client returned code %d" % result.returncode) |
315 | + return {} |
316 | + |
317 | + with open(UA_STATUS_JSON, "r") as stream: |
318 | + status_json = stream.read() |
319 | + try: |
320 | + status = json.loads(status_json) |
321 | + except json.JSONDecodeError as e: |
322 | + print("Failed to parse ubuntu advantage client JSON:\n%s" % e) |
323 | + return {} |
324 | + return status |
325 | + |
326 | +def get_ua_service_status(service_name='esm-infra', status=None): |
327 | + """Get service availability and status for a specific UA service. |
328 | + |
329 | + Return a tuple (available, service_status). |
330 | + :boolean available: set True when either: |
331 | + - attached contract is entitled to the service |
332 | + - unattached machine reports service "availability" as "yes" |
333 | + :str service_status: will be one of the following: |
334 | + - "disabled" when the service is available but not active |
335 | + - "enabled" when the service is available and active |
336 | + - "n/a" when the service is not applicable on the environment or not |
337 | + entitled for the attached contract |
338 | + """ |
339 | + if not status: |
340 | + status = get_ua_status() |
341 | + available = False |
342 | + service_status = "n/a" |
343 | + for service in status.get("services", []): |
344 | + if service.get("name") == service_name: |
345 | + if service.get("available"): # then we are not attached |
346 | + if service["available"] == "yes": |
347 | + available = True |
348 | + service_status = "disabled" # Disabled since unattached |
349 | + else: # attached |
350 | + available = service.get("entitled") == "yes" |
351 | + service_status = service.get("status") # Will be enabled, disabled or n/a |
352 | + return (available, service_status) |
353 | + |
354 | def retry(exceptions, tries=10, delay=0.1, backoff=2): |
355 | """ |
356 | Retry calling the decorated function using an exponential backoff. |
See https:/ /code.launchpad .net/~robert- ancell/ software- properties/ +git/software- properties/ +merge/ 400153 for the related change to master.