diff --git a/.gitignore b/.gitignore index 3f4db9cc..867032fd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ build/ *.charm *.orig .coverage +.report **/__pycache__/ *.py[cod] .idea/ diff --git a/lib/charms/operator_libs_linux/v2/snap.py b/lib/charms/operator_libs_linux/v2/snap.py index 5cd0ffd4..3623d1db 100644 --- a/lib/charms/operator_libs_linux/v2/snap.py +++ b/lib/charms/operator_libs_linux/v2/snap.py @@ -102,7 +102,7 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 10 +LIBPATCH = 11 # Regex to locate 7-bit C1 ANSI sequences @@ -577,6 +577,9 @@ def _refresh( if revision: args.append(f'--revision="{revision}"') + if self.confinement == 'classic': + args.append('--classic') + if devmode: args.append("--devmode") diff --git a/tests/unit/test_snap.py b/tests/unit/test_snap.py index 5570466e..3c9fa98d 100644 --- a/tests/unit/test_snap.py +++ b/tests/unit/test_snap.py @@ -3,6 +3,8 @@ # pyright: reportPrivateUsage=false +from __future__ import annotations + import datetime import io import json @@ -10,10 +12,11 @@ import typing import unittest from subprocess import CalledProcessError -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from unittest.mock import MagicMock, mock_open, patch import fake_snapd as fake_snapd +import pytest from charms.operator_libs_linux.v2 import snap patch("charms.operator_libs_linux.v2.snap._cache_init", lambda x: x).start() @@ -621,6 +624,43 @@ def test_services_property(self, patched): ) +@patch('charms.operator_libs_linux.v2.snap.subprocess.check_output') +@pytest.mark.parametrize( + 'confinement,classic,expected_flag', + [ + ('classic', False, ['--classic']), + ('classic', True, ['--classic']), + ('strict', False, []), + ('strict', True, ['--classic']), + ], +) +def test_refresh_classic( + mock_subprocess: MagicMock, confinement: str, classic: bool, expected_flag: list[str] +): + """Test that ensure and _refresh add the --classic flag with confinement set to classic.""" + foo = snap.Snap( + name='foo', + state=snap.SnapState.Present, + channel='stable', + revision='1', + confinement=confinement, + apps=None, + cohort='A', + ) + foo.ensure(snap.SnapState.Latest, revision='2', classic=classic) + mock_subprocess.assert_called_with( + [ + 'snap', + 'refresh', + 'foo', + '--revision="2"', + *expected_flag, + '--cohort="A"', + ], + text=True, + ) + + class TestSocketClient(unittest.TestCase): def test_socket_not_found(self): client = snap.SnapClient(socket_path="/does/not/exist") @@ -707,8 +747,8 @@ def test_wait_changes(self): def _request_raw( method: str, path: str, - query: Dict = None, - headers: Dict = None, + query: dict = None, + headers: dict = None, data: bytes = None, ) -> typing.IO[bytes]: nonlocal change_finished @@ -818,8 +858,8 @@ def test_wait_failed(self): def _request_raw( method: str, path: str, - query: Dict = None, - headers: Dict = None, + query: dict = None, + headers: dict = None, data: bytes = None, ) -> typing.IO[bytes]: if method == "PUT" and path == "snaps/test/conf": @@ -884,7 +924,7 @@ def test_can_run_bare_changes(self, mock_subprocess: MagicMock): self.assertTrue(foo.present) snap.add("curl", state="latest") # cover string conversion path mock_subprocess.assert_called_with( - ["snap", "refresh", "curl", '--channel="latest"'], + ["snap", "refresh", "curl", '--channel="latest"', '--classic'], text=True, ) with self.assertRaises(TypeError): # cover error path @@ -925,6 +965,7 @@ def test_cohort(self, mock_subprocess): "refresh", "curl", '--channel="latest/beta"', + '--classic', '--cohort="+"', ], text=True, @@ -1011,7 +1052,7 @@ def test_snap_get(self): An invalid key will raise an error if typed=False, but return None if typed=True. """ - def fake_snap(command: str, optargs: Optional[Iterable[str]] = None) -> str: + def fake_snap(command: str, optargs: Iterable[str] | None) -> str: """Snap._snap would normally call subprocess.check_output(["snap", ...], ...). Here we only handle the "get" commands generated by Snap.get: @@ -1040,7 +1081,7 @@ def fake_snap(command: str, optargs: Optional[Iterable[str]] = None) -> str: foo = snap.Snap("foo", snap.SnapState.Latest, "stable", "1", "classic") foo._snap = MagicMock(side_effect=fake_snap) - keys_and_values: Dict[str, Any] = { + keys_and_values: dict[str, Any] = { "key_w_string_value": "string", "key_w_float_value": 4.2, "key_w_int_value": 13,