Merge ~tiago.pasqualini/layer-snap:snap_list into layer-snap:master

Proposed by Tiago Pasqualini da Silva
Status: Merged
Approved by: Tom Haddon
Approved revision: e5755c5a7bbdf73dd6b3c23b6074c77d90722599
Merged at revision: c37f9333bdf85563439244d1aed87dc733a8f085
Proposed branch: ~tiago.pasqualini/layer-snap:snap_list
Merge into: layer-snap:master
Diff against target: 71 lines (+32/-6)
1 file modified
lib/charms/layer/snap.py (+32/-6)
Reviewer Review Type Date Requested Status
Tom Haddon Approve
Canonical IS Reviewers Pending
layer-snap-charmers Pending
Review via email: mp+410304@code.launchpad.net

This proposal supersedes a proposal from 2021-07-21.

Commit message

Change 'snap info' to 'snap list'

Currently snap info is being used to retrieve the version and
tracking channel of the installed snaps, which will fail if the
system does not have access to the snapstore. This patch changes
the layer to use 'snap list', which is an offline command.

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : Posted in a previous version of this proposal

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : Posted in a previous version of this proposal

Unable to determine commit message from repository - please click "Set commit message" and enter the commit message manually.

Revision history for this message
Tom Haddon (mthaddon) wrote : Posted in a previous version of this proposal

One comment inline about docstring.

The way this is set up currently we're going to have a different exception from previous. With the previous code we'd raise an IndexError if the snap existed but wasn't installed, or a subprocess.CalledProcessError if the snap didn't exist. This code will now raise a KeyError in both cases.

I don't think that's terrible, but I think it might be nice to raise a custom exception so that charm writers can have some stability there by inspecting that.

Revision history for this message
Tiago Pasqualini da Silva (tiago.pasqualini) wrote : Posted in a previous version of this proposal

Hi Tom,

Fixed the docstring and rebased on top of master.

Regarding the exception, the code already checks if the snap is installed and returns nothing in that case, so it is not expected to fail if used correctly.

Please let me know if changed are enough.

Thanks,
Tiago

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Tiago Pasqualini da Silva (tiago.pasqualini) wrote :

Added the same treatment from get_installed_channel on get_installed_version. Now the exception will be swallowed by the functions and no exceptions will be raised to the caller.

Revision history for this message
Tiago Pasqualini da Silva (tiago.pasqualini) wrote :

Also fixed small indexing issue, which was causing it to ignore the last line from snap list.

I'm currently testing this with the graylog charm and it is working.

Thanks,
Tiago

Revision history for this message
Tom Haddon (mthaddon) wrote :

Some docstrings now need updating based on changed behaviour.

Revision history for this message
Tom Haddon (mthaddon) wrote :

LGTM, thx

review: Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision c37f9333bdf85563439244d1aed87dc733a8f085

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/charms/layer/snap.py b/lib/charms/layer/snap.py
index 06cc4b1..ae9be45 100644
--- a/lib/charms/layer/snap.py
+++ b/lib/charms/layer/snap.py
@@ -273,11 +273,30 @@ def get(snapname, key):
273 return subprocess.check_output(["snap", "get", snapname, key]).strip()273 return subprocess.check_output(["snap", "get", snapname, key]).strip()
274274
275275
276def _snap_list():
277 """Constructs a dict with all installed snaps.
278
279 Queries all the snaps installed and returns a dict containing their
280 versions and tracking channels, indexed by the snap name.
281 """
282 cmd = ["snap", "list"]
283 out = subprocess.check_output(cmd).decode("utf-8", errors="replace").split()
284 snaps = {}
285 for i in range(6, len(out) - 5, 6): # Skip first six, which are the titles
286 # Snap list has 6 columns:
287 # name, version, revision, tracking channel, publisher and notes
288 # We only care about name (0), version (1) and tracking channel (3)
289 snaps[out[i]] = {
290 'version': out[i + 1],
291 'channel': out[i + 3],
292 }
293 return snaps
294
295
276def get_installed_version(snapname):296def get_installed_version(snapname):
277 """Gets the installed version of a snapname.297 """Gets the installed version of a snapname.
278 This function will fail if snapname is not an installed snap.298 This function will return nothing if snapname is not an installed snap.
279 """299 """
280 cmd = ["snap", "info", snapname]
281 hookenv.log("Get installed key for snap {}".format(snapname))300 hookenv.log("Get installed key for snap {}".format(snapname))
282 if not reactive.is_flag_set(get_installed_flag(snapname)):301 if not reactive.is_flag_set(get_installed_flag(snapname)):
283 hookenv.log(302 hookenv.log(
@@ -285,14 +304,21 @@ def get_installed_version(snapname):
285 hookenv.WARNING,304 hookenv.WARNING,
286 )305 )
287 return306 return
288 return subprocess.check_output(cmd).decode("utf-8", errors="replace").partition("installed:")[-1].split()[0]307 try:
308 return _snap_list()[snapname]['version']
309 except Exception as e:
310 # If it fails to get the version information(ex. installed via resource), return nothing.
311 hookenv.log(
312 "Cannot get snap version: {}".format(e),
313 hookenv.WARNING,
314 )
315 return
289316
290317
291def get_installed_channel(snapname):318def get_installed_channel(snapname):
292 """Gets the tracking (channel) of a snapname.319 """Gets the tracking (channel) of a snapname.
293 This function will fail if snapname is not an installed snap.320 This function will return nothing if snapname is not an installed snap.
294 """321 """
295 cmd = ["snap", "info", snapname]
296 hookenv.log("Get channel for snap {}".format(snapname))322 hookenv.log("Get channel for snap {}".format(snapname))
297 if not reactive.is_flag_set(get_installed_flag(snapname)):323 if not reactive.is_flag_set(get_installed_flag(snapname)):
298 hookenv.log(324 hookenv.log(
@@ -301,7 +327,7 @@ def get_installed_channel(snapname):
301 )327 )
302 return328 return
303 try:329 try:
304 return subprocess.check_output(cmd).decode("utf-8", errors="replace").partition("tracking:")[-1].split()[0]330 return _snap_list()[snapname]['channel']
305 except Exception as e:331 except Exception as e:
306 # If it fails to get the channel information(ex. installed via resource), return nothing.332 # If it fails to get the channel information(ex. installed via resource), return nothing.
307 hookenv.log(333 hookenv.log(

Subscribers

People subscribed via source and target branches