Merge ~chad.smith/software-properties:ua-status-from-json-impish into software-properties:ubuntu/impish

Proposed by Chad Smith
Status: Merged
Merged at revision: cf0e4d7a9c528fb90cd7b8596e94e17bd5d3eac4
Proposed branch: ~chad.smith/software-properties:ua-status-from-json-impish
Merge into: software-properties:ubuntu/impish
Diff against target: 141 lines (+57/-31)
2 files modified
debian/changelog (+9/-0)
softwareproperties/gtk/utils.py (+48/-31)
Reviewer Review Type Date Requested Status
Corey Bryant Approve
Grant Orndorff Pending
Lucas Albuquerque Medeiros de Moura Pending
Robert Ancell Pending
Review via email: mp+411092@code.launchpad.net

Commit message

utils: prefer ua status from status.json. Support schema 0.1 format

Adjust gtk.utils.get_ua_status to prefer reading
/var/lib/ubuntu-advantage/status.json instead of invoking
ua status on the commandline due to a network roundtrip that
is performed while running the command.

This status.json file will exist on all machines attached to an
Ubuntu Advantage subscription.

Unattached machines will persist status.json due to a systemd timer
that will sync current unattached or attached status to
/var/lib/ubuntu-advantage/status.json.

Allow get_ua_status will now also check a _schema_version key from
ua status which will log if the schema version has changed from the
expected version "0.1".

Changes in schema version may imply incompatibility with reading
UA status.

To post a comment you must log in.
Revision history for this message
Corey Bryant (corey.bryant) wrote :

I think this is safe to merge without Robert's approval. Robert approved the jammy and hirsute changes, which are the same.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/debian/changelog b/debian/changelog
index 0cf9bd5..2487bde 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,12 @@
1software-properties (0.99.13.1) impish; urgency=medium
2
3 * utils: prefer /var/lib/ubuntu-advantage/status.json over ua status
4 - Handle absent /var/lib/ubuntu-advantage/status.json for non-root
5 users (LP: #1939732)
6 - print unexcepted errors and if _schema_version not equal to 0.1
7
8 -- Chad Smith <chad.smith@canonical.com> Mon, 01 Nov 2021 11:42:27 -0600
9
1software-properties (0.99.13) impish; urgency=medium10software-properties (0.99.13) impish; urgency=medium
211
3 * Catch exception if UA status file is not written12 * Catch exception if UA status file is not written
diff --git a/softwareproperties/gtk/utils.py b/softwareproperties/gtk/utils.py
index d478671..522faa0 100644
--- a/softwareproperties/gtk/utils.py
+++ b/softwareproperties/gtk/utils.py
@@ -26,6 +26,7 @@ import gi
26gi.require_version("Gtk", "3.0")26gi.require_version("Gtk", "3.0")
27from gi.repository import Gio, Gtk27from gi.repository import Gio, Gtk
28import json28import json
29import os
29import subprocess30import subprocess
3031
31import logging32import logging
@@ -73,37 +74,52 @@ def current_distro():
73 if release.series == distro.codename:74 if release.series == distro.codename:
74 return release75 return release
7576
77
76def get_ua_status():78def get_ua_status():
77 """Return a dict of all UA status information or empty dict on error."""79 """Return a dict of all UA status information or empty dict on error."""
78 # status.json will exist on any attached system or any unattached system80 # status.json will exist on any attached system. It will also be created
79 # which has already run `ua status`. Calling ua status directly on81 # by the systemd timer ua-timer which will update UA_STATUS_JSON every 12
80 # network disconnected machines will raise a TimeoutException trying to82 # hours to reflect current status of UA subscription services.
81 # access contracts.canonical.com/v1/resources.83 # Invoking `ua status` with subp will result in a network call to
82 try:84 # contracts.canonical.com which could raise Timeouts on network limited
83 # Success writes UA_STATUS_JSON85 # machines. So, prefer the status.json file when possible.
84 result = subprocess.run(['ua', 'status', '--format=json'], capture_output=True)86 status_json = ""
85 except Exception as e:87 if os.path.exists(UA_STATUS_JSON):
86 print("Failed to call ubuntu advantage client:\n%s" % e)88 with open(UA_STATUS_JSON) as stream:
87 return {}89 status_json = stream.read()
88 if result.returncode != 0:90 else:
89 print("Ubuntu advantage client returned code %d" % result.returncode)91 try:
92 # Success writes UA_STATUS_JSON
93 result = subprocess.run(
94 ['ua', 'status', '--format=json'], stdout=subprocess.PIPE
95 )
96 except Exception as e:
97 print("Failed to run `ua status`:\n%s" % e)
98 return {}
99 if result.returncode != 0:
100 print(
101 "Ubuntu Advantage client returned code %d" % result.returncode
102 )
103 return {}
104 status_json = result.stdout
105 if not status_json:
106 print(
107 "Warning: no Ubuntu Advantage status found."
108 " Is ubuntu-advantage-tools installed?"
109 )
90 return {}110 return {}
91
92 try:
93 status_file = open(UA_STATUS_JSON, "r")
94 except Exception as e:
95 print("No ua status file written:\n%s" % e)
96 return {}
97
98 with status_file as stream:
99 status_json = stream.read()
100 try:111 try:
101 status = json.loads(status_json)112 status = json.loads(status_json)
102 except json.JSONDecodeError as e:113 except json.JSONDecodeError as e:
103 print("Failed to parse ubuntu advantage client JSON:\n%s" % e)114 print("Failed to parse ubuntu advantage client JSON:\n%s" % e)
104 return {}115 return {}
116 if status.get("_schema_version", "0.1") != "0.1":
117 print(
118 "UA status schema version change: %s" % status["_schema_version"]
119 )
105 return status120 return status
106121
122
107def get_ua_service_status(service_name='esm-infra', status=None):123def get_ua_service_status(service_name='esm-infra', status=None):
108 """Get service availability and status for a specific UA service.124 """Get service availability and status for a specific UA service.
109125
@@ -112,26 +128,27 @@ def get_ua_service_status(service_name='esm-infra', status=None):
112 - attached contract is entitled to the service128 - attached contract is entitled to the service
113 - unattached machine reports service "availability" as "yes"129 - unattached machine reports service "availability" as "yes"
114 :str service_status: will be one of the following:130 :str service_status: will be one of the following:
115 - "disabled" when the service is available but not active131 - "disabled" when the service is available and applicable but not
132 active
116 - "enabled" when the service is available and active133 - "enabled" when the service is available and active
117 - "n/a" when the service is not applicable on the environment or not134 - "n/a" when the service is not applicable to the environment or not
118 entitled for the attached contract135 entitled for the attached contract
119 """136 """
120 if not status:137 if not status:
121 status = get_ua_status()138 status = get_ua_status()
139 # Assume unattached on empty status dict
122 available = False140 available = False
123 service_status = "n/a"141 service_status = "n/a"
124 for service in status.get("services", []):142 for service in status.get("services", []):
125 if service.get("name") == service_name:143 if service.get("name") != service_name:
126 if service.get("available"): # then we are not attached144 continue
127 if service["available"] == "yes":145 if "available" in service:
128 available = True146 available = bool("yes" == service["available"])
129 service_status = "disabled" # Disabled since unattached147 if "status" in service:
130 else: # attached148 service_status = service["status"] # enabled, disabled or n/a
131 available = service.get("entitled") == "yes"
132 service_status = service.get("status") # Will be enabled, disabled or n/a
133 return (available, service_status)149 return (available, service_status)
134150
151
135def retry(exceptions, tries=10, delay=0.1, backoff=2):152def retry(exceptions, tries=10, delay=0.1, backoff=2):
136 """153 """
137 Retry calling the decorated function using an exponential backoff.154 Retry calling the decorated function using an exponential backoff.

Subscribers

People subscribed via source and target branches