[Merge] ~robert-ancell/software-properties:esm into software-properties:ubuntu/master
Chad Smith
chad.smith at canonical.com
Mon May 31 20:42:30 UTC 2021
Thanks Robert again for looking at this for Desktop support of esm-infra and/or esm-apps services.
I've provided a more significant and correct review. Sorry for the back and forth here.
The high level fixes in my patch:
- Take into account either esm-infra or esm-apps service status when determining whether "Extended Security Maintenance" is active.
- Consume /var/lib/ubuntu-advantage/status.json directly. Calls to `ua status` directly make a remote call to contracts.canonical.com/v1/resources to check what resources are available. This will timeout on desktops that don't have internet/firewall access and result in invalid empty values on systems which may have been attached.
- status.json is written when any root user has run `ua status` on a machine or when any config changes are made via `ua (attach|detach|enable|disable)`. This means we can rely on the flat file to represent current attached machine service statuses.
- For esm-infra and esm-apps we can determine if esm-infra or esm-apps are available based on if our running distro release is LTS. No ESM services will be offered on interim (non-LTS) releases.
I generalized your get_esm_status function to get_ua_service_status since there are multiple services "UA Infra: ESM (esm-infra)" and "UA Apps: ESM (esm-apps)" (to be published shortly) this logic will need to be able to cope with either or both minimally. It could also be used to check on livepatch status too if desired.
UA Infra: ESM is a service required on LTS releases that have entered Extended Security Maintenance after EOL
UA Apps: ESM is a service that provides security upgrades for common commercial apps stacks as well as various software living in universe on any LTS more recent than Trusty.
An LTS releaes of Ubuntu doesn't have to have crossed into EOL(ESM) territory to be entitled to UA Apps: ESM and to get value out of security patches.
As such, you'll probably need a more generic bit of logic that takes into account minimally esm-inra and esm-apps when rendering dialogs about whether "Extended Security Maintenance" is enabled as either applies.
Both esm-infra and esm-apps are available on any LTS (trusty, xenial, bionic and focal). For future branches as you flesh out https://wiki.ubuntu.com/SoftwareUpdates#Extended_Security_Maintenance, the dialogs probably shouldn't mention esm-infra generally unless we are near initial EOL date on an LTS release. On any LTS (once esm-apps goes live) UA Apps: ESM(esm-apps) is available with security fixes above and beyond what is in Ubuntu standard -updates and -security pockets.
Here's a patch with a recommended URL https://ubuntu.com/16-04 (for EOL LTS releases) vs. https://ubuntu.com/esm (for non-EOL LTS releases). That hopefully gives a bit more context on esm-infra vs esm-apps and handling of that release-specific URL for EOL LTSes.
https://paste.ubuntu.com/p/4zbMWPRxCP/
diff --git a/softwareproperties/gtk/SoftwarePropertiesGtk.py b/softwareproperties/gtk/SoftwarePropertiesGtk.py
index df8d67e..c8d187d 100644
--- a/softwareproperties/gtk/SoftwarePropertiesGtk.py
+++ b/softwareproperties/gtk/SoftwarePropertiesGtk.py
@@ -62,9 +62,10 @@ from softwareproperties.SoftwareProperties import SoftwareProperties
import softwareproperties.SoftwareProperties
from softwareproperties.gtk.utils import (
- get_esm_apps_status,
- current_distro_eol,
- current_distro_eol_esm
+ get_ua_status,
+ get_ua_service_status,
+ current_distro,
+ is_current_distro_lts,
)
from UbuntuDrivers import detect
@@ -408,21 +409,55 @@ class SoftwarePropertiesGtk(SoftwareProperties, SimpleGtkbuilderApp):
self.dev_box.add(checkbox)
checkbox.show()
- (esm_available, esm_enabled) = get_esm_apps_status()
+ if is_current_distro_lts():
+ # All LTS >= Trusty have access to esm-infra and esm-apps
+ esm_available = True
+ infra_status = get_ua_service_status("esm-infra")
+ apps_status = get_ua_service_status("esm-apps")
+ else:
+ esm_available = False
+ infra_status = apps_status = "n/a" # Non-LTS means no ESM* service
+ esm_enabled = "enabled" in (infra_status, apps_status)
+ distro = current_distro()
if esm_enabled:
+ status = get_ua_status()
eol_text = _("Extended Security Maintenance")
- eol_date = current_distro_eol_esm()
+ # EOL date should probably be UA contract expiry.
+ # This is probably sooner than ESM EOL for the distro and
+ # gives software properties dialogs a chance to interact about
+ # renewals if needed.
+ try:
+ eol_date = datetime.datetime.strptime(
+ status.get("expires"), "%Y-%m-%dT%H:%M:%S"
+ ).date()
+ except ValueError:
+ print("Unable to determine UA contract expiry")
+ eol_date = distro.eol
+ esm_eol = distro.eol_esm
else:
eol_text = _("Basic Security Maintenance")
- eol_date = current_distro_eol()
+ eol_date = distro.eol
self.label_esm_status.set_markup(eol_text)
- self.label_esm_subscribe.set_markup("<a href=\"https://ubuntu.com/security/esm\">%s</a>" % _("Extend…"))
- self.label_esm_subscribe.set_visible(esm_available and not esm_enabled)
- if datetime.datetime.now().date() > eol_date:
- eol_expiry_text = _("Ended %s") % eol_date.strftime("%x")
+ esm_url = "https://ubuntu.com/esm" # Non-EOL LTS generic ESM
+ today = datetime.datetime.now().date()
+ if today >= eol_date:
+ if esm_available:
+ # EOL LTS uses release-specific ESM ubuntu.com/XX-YY
+ distro_ver = distro.version.replace(' LTS', '')
+ esm_url = "https://ubuntu.com/%s" % distro_ver.replace(".", "-")
+ eol_expiry_text = _("Ended %s - extend or upgrade now") % eol_date.strftime("%x")
+ elif today >= eol_date - datetime.timedelta(days=60):
+ eol_expiry_text = _("Ends %s - extend or upgrade soon") % eol_date.strftime("%x")
else:
eol_expiry_text = _("Active until %s") % eol_date.strftime("%x")
self.label_eol.set_label(eol_expiry_text);
+ self.label_esm_subscribe.set_markup(
+ "<a href=\"%s\">%s</a>" % (esm_url, _("Extend…"))
+ )
+ self.label_esm_subscribe.set_visible(
+ esm_available and not esm_enabled
+ )
+ eol_expiry_text = _("Ended %s") % eol_date.strftime("%x")
# Setup the combo box for updates subscriptions
combobox = self.combobox_updates_subscription
diff --git a/softwareproperties/gtk/utils.py b/softwareproperties/gtk/utils.py
index a665611..35b7498 100644
--- a/softwareproperties/gtk/utils.py
+++ b/softwareproperties/gtk/utils.py
@@ -26,6 +26,7 @@ import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gio, Gtk
import json
+import os
import subprocess
import logging
@@ -33,6 +34,8 @@ LOG=logging.getLogger(__name__)
import time
+UA_STATUS_JSON = "/var/lib/ubuntu-advantage/status.json"
+
def setup_ui(self, path, domain):
# setup ui
self.builder = Gtk.Builder()
@@ -63,7 +66,7 @@ def is_current_distro_supported():
di = distro_info.UbuntuDistroInfo()
return distro.codename in di.supported(datetime.now().date())
-def _current_distro():
+def current_distro():
distro = aptsources.distro.get_distro()
di = distro_info.UbuntuDistroInfo()
releases = di.get_all(result="object")
@@ -71,41 +74,45 @@ def _current_distro():
if release.series == distro.codename:
return release
-def current_distro_eol():
- return _current_distro().eol
-
-def current_distro_eol_esm():
- return _current_distro().eol_esm
-
-def get_esm_apps_status():
+def get_ua_status():
+ """Return a dict of all UA status information or empty dict on error."""
+ # status.json will exist on any attached system or any unattached system
+ # which has already run `ua status`. Don't call ua status directly on
+ # network disconnected machines as it will timeout trying to access
+ # contracts.canonical.com/v1/resources.
+ if not os.path.exists(UA_STATUS_JSON):
+ return {}
+ with open(UA_STATUS_JSON, "r") as stream:
+ status_json = stream.read()
try:
- result = subprocess.run(['ua', 'status', '--format=json'], capture_output=True)
- except Exception as e:
- print("Failed to call ubuntu advantage client:\n%s" % e)
- return (False, False)
-
- if result.returncode != 0:
- print("Ubuntu advantage client returned code %d" % result.returncode)
- return (False, False)
-
- try:
- status = json.loads(result.stdout)
+ status = json.loads(status_json)
except json.JSONDecodeError as e:
print("Failed to parse ubuntu advantage client JSON:\n%s" % e)
- return (False, False)
+ return {}
+ return status
- services = status.get("services")
- if services == None:
- services = []
+def get_ua_service_status(service_name="esm-infra"):
+ """Check `ua status`, returning a string representeing the servide status.
- for service in services:
- if service.get("name") == "esm-apps":
- available = service.get("available") == "yes"
- entitled = service.get("entitled") == "yes"
- service_status = service.get("status")
- return (available or entitled, service_status == "enabled")
+ Generally this will be one of: n/a, enabled, disabled, available
- return (False, False)
+ Return None when service_name is undefined or `ua status` not understood.
+ """
+ status = get_ua_status()
+
+ for service in status.get("services", []):
+ if service.get("name") == service_name:
+ if service.get("available"): # then we are not attached
+ if service.get("available") == "no":
+ # Available "no" means service is not compatible with
+ # either platform, release or kernel when machine is
+ # unattached. So, return n/a for simplicity because that is
+ # what is returned from "status" if machine were attached.
+ return "n/a"
+ else:
+ return "disabled" # Report not enabled since unattached
+ return service.get("status") # Will be enabled, disabled or n/a
+ return None
def retry(exceptions, tries=10, delay=0.1, backoff=2):
"""
Diff comments:
> diff --git a/softwareproperties/gtk/SoftwarePropertiesGtk.py b/softwareproperties/gtk/SoftwarePropertiesGtk.py
> index d83d7bc..df8d67e 100644
> --- a/softwareproperties/gtk/SoftwarePropertiesGtk.py
> +++ b/softwareproperties/gtk/SoftwarePropertiesGtk.py
> @@ -401,6 +408,22 @@ class SoftwarePropertiesGtk(SoftwareProperties, SimpleGtkbuilderApp):
> self.dev_box.add(checkbox)
> checkbox.show()
>
> + (esm_available, esm_enabled) = get_esm_apps_status()
> + if esm_enabled:
> + eol_text = _("Extended Security Maintenance")
> + eol_date = current_distro_eol_esm()
> + else:
> + eol_text = _("Basic Security Maintenance")
> + eol_date = current_distro_eol()
> + self.label_esm_status.set_markup(eol_text)
> + self.label_esm_subscribe.set_markup("<a href=\"https://ubuntu.com/security/esm\">%s</a>" % _("Extend…"))
> + self.label_esm_subscribe.set_visible(esm_available and not esm_enabled)
> + if datetime.datetime.now().date() > eol_date:
> + eol_expiry_text = _("Ended %s") % eol_date.strftime("%x")
Make that
hyphenated_release = _current_distro().release.split(" ")[0].replace(".", "-")
url = "https://ubuntu.com/%s" % hyphenated_release # dropped the /security part of the route
> + else:
> + eol_expiry_text = _("Active until %s") % eol_date.strftime("%x")
> + self.label_eol.set_label(eol_expiry_text);
> +
> # Setup the combo box for updates subscriptions
> combobox = self.combobox_updates_subscription
> self.handlers[combobox] = combobox.connect(
--
https://code.launchpad.net/~robert-ancell/software-properties/+git/software-properties/+merge/400153
Your team Ubuntu Core Development Team is requested to review the proposed merge of ~robert-ancell/software-properties:esm into software-properties:ubuntu/master.
More information about the Ubuntu-reviews
mailing list