diff --git a/lib/charms/operator_libs_linux/v2/snap.py b/lib/charms/operator_libs_linux/v2/snap.py index 4370eb12..a706c166 100644 --- a/lib/charms/operator_libs_linux/v2/snap.py +++ b/lib/charms/operator_libs_linux/v2/snap.py @@ -109,7 +109,7 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 13 +LIBPATCH = 14 PYDEPS = ["opentelemetry-api"] @@ -149,6 +149,7 @@ class _SnapDict(TypedDict, total=True): name: str channel: str revision: str + version: str confinement: str apps: NotRequired[list[dict[str, JSONType]] | None] @@ -309,6 +310,7 @@ class Snap: - channel: "stable", "candidate", "beta", and "edge" are common - revision: a string representing the snap's revision - confinement: "classic", "strict", or "devmode" + - version: a string representing the snap's version, if set by the snap author """ def __init__( @@ -320,6 +322,8 @@ def __init__( confinement: str, apps: list[dict[str, JSONType]] | None = None, cohort: str | None = None, + *, + version: str | None = None, ) -> None: self._name = name self._state = state @@ -328,6 +332,7 @@ def __init__( self._confinement = confinement self._cohort = cohort or "" self._apps = apps or [] + self._version = version self._snap_client = SnapClient() def __eq__(self, other: object) -> bool: @@ -783,6 +788,11 @@ def held(self) -> bool: info = self._snap("info") return "hold:" in info + @property + def version(self) -> str | None: + """Returns the version for a snap.""" + return self._version + class _UnixSocketConnection(http.client.HTTPConnection): """Implementation of HTTPConnection that connects to a named Unix socket.""" @@ -1048,6 +1058,7 @@ def _load_installed_snaps(self) -> None: revision=i["revision"], confinement=i["confinement"], apps=i.get("apps"), + version=i.get("version"), ) self._snap_map[snap.name] = snap @@ -1067,6 +1078,7 @@ def _load_info(self, name: str) -> Snap: revision=info["revision"], confinement=info["confinement"], apps=None, + version=info.get("version"), ) diff --git a/tests/integration/test_snap.py b/tests/integration/test_snap.py index 787b7fa6..2ad73f2e 100644 --- a/tests/integration/test_snap.py +++ b/tests/integration/test_snap.py @@ -204,12 +204,14 @@ def test_snap_ensure_revision(): ["snap", "info", "juju"], capture_output=True, encoding="utf-8" ).stdout.split("\n") + edge_version = None edge_revision = None for line in snap_info_juju: - match = re.search(r"3/stable.*\((\d+)\)", line) + match = re.search(r"3/stable:\s+([^\s]+).+\((\d+)\)", line) if match: - edge_revision = match.group(1) + edge_version = match.group(1) + edge_revision = match.group(2) break assert edge_revision is not None @@ -226,10 +228,13 @@ def test_snap_ensure_revision(): assert "installed" in snap_info_juju for line in snap_info_juju.split("\n"): if "installed" in line: - match = re.search(r"installed.*\((\d+)\)", line) + match = re.search(r"installed:\s+([^\s]+).+\((\d+)\)", line) assert match is not None - assert match.group(1) == edge_revision + assert match.group(1) == edge_version + assert match.group(2) == edge_revision + + assert juju.version == edge_version def test_snap_start(): diff --git a/tests/unit/test_snap.py b/tests/unit/test_snap.py index 88e3fe12..a98c93f4 100644 --- a/tests/unit/test_snap.py +++ b/tests/unit/test_snap.py @@ -291,6 +291,7 @@ def test_can_lazy_load_snap_info(self, mock_exists, m): self.assertEqual(result.channel, "stable") self.assertEqual(result.confinement, "strict") self.assertEqual(result.revision, "233") + self.assertEqual(result.version, "7.78.0") @patch("os.path.isfile") def test_can_load_installed_snap_info(self, mock_exists): @@ -326,10 +327,15 @@ def test_raises_error_if_snap_not_running(self, mock_exists): self.assertIn("snapd is not running", ctx.exception.message) def test_can_compare_snap_equality(self): - foo1 = snap.Snap("foo", snap.SnapState.Present, "stable", "1", "classic") + foo1 = snap.Snap("foo", snap.SnapState.Present, "stable", "1", "classic", version="v42") foo2 = snap.Snap("foo", snap.SnapState.Present, "stable", "1", "classic") self.assertEqual(foo1, foo2) + def test_can_compare_snap_inequality(self): + foo1 = snap.Snap("foo", snap.SnapState.Present, "stable", "1", "classic", version="v42") + foo2 = snap.Snap("foo", snap.SnapState.Present, "stable", "2", "classic", version="v42") + self.assertNotEqual(foo1, foo2) + def test_snap_magic_methods(self): foo = snap.Snap("foo", snap.SnapState.Present, "stable", "1", "classic") self.assertEqual(hash(foo), hash((foo._name, foo._revision)))