diff -Nru software-properties-0.96.24.32.17/debian/changelog software-properties-0.96.24.32.18/debian/changelog --- software-properties-0.96.24.32.17/debian/changelog 2021-07-07 23:47:26.000000000 +0000 +++ software-properties-0.96.24.32.18/debian/changelog 2021-11-05 03:44:17.000000000 +0000 @@ -1,3 +1,13 @@ +software-properties (0.96.24.32.18) bionic; urgency=medium + + [ Chad Smith ] + * utils: prefer /var/lib/ubuntu-advantage/status.json over ua status + - Handle absent /var/lib/ubuntu-advantage/status.json for non-root + users (LP: #1939732) + - print unexpected errors and if _schema_version not equal to 0.1 + + -- Robert Ancell Fri, 05 Nov 2021 16:44:17 +1300 + software-properties (0.96.24.32.17) bionic; urgency=medium * Show Ubuntu Pro banner on Livepatch page (LP: #1934439) diff -Nru software-properties-0.96.24.32.17/softwareproperties/gtk/SoftwarePropertiesGtk.py software-properties-0.96.24.32.18/softwareproperties/gtk/SoftwarePropertiesGtk.py --- software-properties-0.96.24.32.17/softwareproperties/gtk/SoftwarePropertiesGtk.py 2021-07-07 23:46:51.000000000 +0000 +++ software-properties-0.96.24.32.18/softwareproperties/gtk/SoftwarePropertiesGtk.py 2021-11-05 03:43:33.000000000 +0000 @@ -397,8 +397,11 @@ # gives software properties dialogs a chance to interact about # renewals if needed. try: + # Ignore timezone to simplify formatting python < 3.7 + # 3.7 has datetime.fromisoformat() + dt, _sep, _tz = status.get("expires", "").partition("+") eol_date = datetime.datetime.strptime( - status.get("expires"), "%Y-%m-%dT%H:%M:%S" + dt, "%Y-%m-%dT%H:%M:%S" ).date() except ValueError: print("Unable to determine UA contract expiry") diff -Nru software-properties-0.96.24.32.17/softwareproperties/gtk/utils.py software-properties-0.96.24.32.18/softwareproperties/gtk/utils.py --- software-properties-0.96.24.32.17/softwareproperties/gtk/utils.py 2021-07-07 23:46:51.000000000 +0000 +++ software-properties-0.96.24.32.18/softwareproperties/gtk/utils.py 2021-11-05 03:43:33.000000000 +0000 @@ -26,6 +26,7 @@ gi.require_version("Gtk", "3.0") from gi.repository import Gio, Gtk import json +import os import subprocess import logging @@ -73,37 +74,52 @@ if release.series == distro.codename: return release + 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`. Calling ua status directly on - # network disconnected machines will raise a TimeoutException trying to - # access contracts.canonical.com/v1/resources. - try: - # Success writes UA_STATUS_JSON - result = subprocess.run(['ua', 'status', '--format=json'], stdout=subprocess.PIPE) - except Exception as e: - print("Failed to call ubuntu advantage client:\n%s" % e) - return {} - if result.returncode != 0: - print("Ubuntu advantage client returned code %d" % result.returncode) + # status.json will exist on any attached system. It will also be created + # by the systemd timer ua-timer which will update UA_STATUS_JSON every 12 + # hours to reflect current status of UA subscription services. + # Invoking `ua status` with subp will result in a network call to + # contracts.canonical.com which could raise Timeouts on network limited + # machines. So, prefer the status.json file when possible. + status_json = "" + if os.path.exists(UA_STATUS_JSON): + with open(UA_STATUS_JSON) as stream: + status_json = stream.read() + else: + try: + # Success writes UA_STATUS_JSON + result = subprocess.run( + ['ua', 'status', '--format=json'], stdout=subprocess.PIPE + ) + except Exception as e: + print("Failed to run `ua status`:\n%s" % e) + return {} + if result.returncode != 0: + print( + "Ubuntu Advantage client returned code %d" % result.returncode + ) + return {} + status_json = result.stdout + if not status_json: + print( + "Warning: no Ubuntu Advantage status found." + " Is ubuntu-advantage-tools installed?" + ) return {} - - try: - status_file = open(UA_STATUS_JSON, "r") - except Exception as e: - print("No ua status file written:\n%s" % e) - return {} - - with status_file as stream: - status_json = stream.read() try: status = json.loads(status_json) except json.JSONDecodeError as e: print("Failed to parse ubuntu advantage client JSON:\n%s" % e) return {} + if status.get("_schema_version", "0.1") != "0.1": + print( + "UA status schema version change: %s" % status["_schema_version"] + ) return status + def get_ua_service_status(service_name='esm-infra', status=None): """Get service availability and status for a specific UA service. @@ -112,26 +128,27 @@ - attached contract is entitled to the service - unattached machine reports service "availability" as "yes" :str service_status: will be one of the following: - - "disabled" when the service is available but not active + - "disabled" when the service is available and applicable but not + active - "enabled" when the service is available and active - - "n/a" when the service is not applicable on the environment or not + - "n/a" when the service is not applicable to the environment or not entitled for the attached contract """ if not status: status = get_ua_status() + # Assume unattached on empty status dict available = False service_status = "n/a" for service in status.get("services", []): - if service.get("name") == service_name: - if service.get("available"): # then we are not attached - if service["available"] == "yes": - available = True - service_status = "disabled" # Disabled since unattached - else: # attached - available = service.get("entitled") == "yes" - service_status = service.get("status") # Will be enabled, disabled or n/a + if service.get("name") != service_name: + continue + if "available" in service: + available = bool("yes" == service["available"]) + if "status" in service: + service_status = service["status"] # enabled, disabled or n/a return (available, service_status) + def retry(exceptions, tries=10, delay=0.1, backoff=2): """ Retry calling the decorated function using an exponential backoff.