Skip to content

Commit

Permalink
feat: Add support for devmode confinement
Browse files Browse the repository at this point in the history
  • Loading branch information
gruyaume committed Feb 2, 2024
1 parent 7680b81 commit 2680e28
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 8 deletions.
46 changes: 38 additions & 8 deletions lib/charms/operator_libs_linux/v2/snap.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 3
LIBPATCH = 4


# Regex to locate 7-bit C1 ANSI sequences
Expand Down Expand Up @@ -214,7 +214,7 @@ class Snap(object):
- state: a `SnapState` representation of its install status
- channel: "stable", "candidate", "beta", and "edge" are common
- revision: a string representing the snap's revision
- confinement: "classic" or "strict"
- confinement: "classic", "strict", or "devmode"
"""

def __init__(
Expand Down Expand Up @@ -475,6 +475,8 @@ def _install(
args = []
if self.confinement == "classic":
args.append("--classic")
if self.confinement == "devmode":
args.append("--devmode")
if channel:
args.append('--channel="{}"'.format(channel))
if revision:
Expand Down Expand Up @@ -530,6 +532,7 @@ def ensure(
self,
state: SnapState,
classic: Optional[bool] = False,
devmode: Optional[bool] = False,
channel: Optional[str] = "",
cohort: Optional[str] = "",
revision: Optional[str] = None,
Expand All @@ -539,6 +542,7 @@ def ensure(
Args:
state: a `SnapState` to reconcile to.
classic: an (Optional) boolean indicating whether classic confinement should be used
devmode: an (Optional) boolean indicating whether devmode confinement should be used
channel: the channel to install from
cohort: optional. Specify the key of a snap cohort.
revision: optional. the revision of the snap to install/refresh
Expand All @@ -549,7 +553,15 @@ def ensure(
Raises:
SnapError if an error is encountered
"""
self._confinement = "classic" if classic or self._confinement == "classic" else ""
if classic and devmode:
raise ValueError("Cannot set both classic and devmode confinement")

if classic or self._confinement == "classic":
self._confinement = "classic"
elif devmode or self._confinement == "devmode":
self._confinement = "devmode"
else:
self._confinement = ""

if state not in (SnapState.Present, SnapState.Latest):
# We are attempting to remove this snap.
Expand Down Expand Up @@ -892,6 +904,7 @@ def add(
state: Union[str, SnapState] = SnapState.Latest,
channel: Optional[str] = "",
classic: Optional[bool] = False,
devmode: Optional[bool] = False,
cohort: Optional[str] = "",
revision: Optional[str] = None,
) -> Union[Snap, List[Snap]]:
Expand All @@ -904,6 +917,8 @@ def add(
channel: an (Optional) channel as a string. Defaults to 'latest'
classic: an (Optional) boolean specifying whether it should be added with classic
confinement. Default `False`
devmode: an (Optional) boolean specifying whether it should be added with devmode
confinement. Default `False`
cohort: an (Optional) string specifying the snap cohort to use
revision: an (Optional) string specifying the snap revision to use
Expand All @@ -920,7 +935,7 @@ def add(
if isinstance(state, str):
state = SnapState(state)

return _wrap_snap_operations(snap_names, state, channel, classic, cohort, revision)
return _wrap_snap_operations(snap_names, state, channel, classic, devmode, cohort, revision)


@_cache_init
Expand All @@ -937,7 +952,7 @@ def remove(snap_names: Union[str, List[str]]) -> Union[Snap, List[Snap]]:
if not snap_names:
raise TypeError("Expected at least one snap to add, received zero!")

return _wrap_snap_operations(snap_names, SnapState.Absent, "", False)
return _wrap_snap_operations(snap_names, SnapState.Absent, "", False, False)


@_cache_init
Expand All @@ -946,6 +961,7 @@ def ensure(
state: str,
channel: Optional[str] = "",
classic: Optional[bool] = False,
devmode: Optional[bool] = False,
cohort: Optional[str] = "",
revision: Optional[int] = None,
) -> Union[Snap, List[Snap]]:
Expand All @@ -957,6 +973,8 @@ def ensure(
channel: an (Optional) channel as a string. Defaults to 'latest'
classic: an (Optional) boolean specifying whether it should be added with classic
confinement. Default `False`
devmode: an (Optional) boolean specifying whether it should be added with devmode
confinement. Default `False`
cohort: an (Optional) string specifying the snap cohort to use
revision: an (Optional) integer specifying the snap revision to use
Expand All @@ -970,7 +988,7 @@ def ensure(
channel = "latest"

if state in ("present", "latest") or revision:
return add(snap_names, SnapState(state), channel, classic, cohort, revision)
return add(snap_names, SnapState(state), channel, classic, devmode, cohort, revision)
else:
return remove(snap_names)

Expand All @@ -980,6 +998,7 @@ def _wrap_snap_operations(
state: SnapState,
channel: str,
classic: bool,
devmode: bool,
cohort: Optional[str] = "",
revision: Optional[str] = None,
) -> Union[Snap, List[Snap]]:
Expand All @@ -995,7 +1014,12 @@ def _wrap_snap_operations(
snap.ensure(state=SnapState.Absent)
else:
snap.ensure(
state=state, classic=classic, channel=channel, cohort=cohort, revision=revision
state=state,
classic=classic,
devmode=devmode,
channel=channel,
cohort=cohort,
revision=revision,
)
snaps["success"].append(snap)
except SnapError as e:
Expand All @@ -1014,13 +1038,17 @@ def _wrap_snap_operations(


def install_local(
filename: str, classic: Optional[bool] = False, dangerous: Optional[bool] = False
filename: str,
classic: Optional[bool] = False,
devmode: Optional[bool] = False,
dangerous: Optional[bool] = False,
) -> Snap:
"""Perform a snap operation.
Args:
filename: the path to a local .snap file to install
classic: whether to use classic confinement
devmode: whether to use devmode confinement
dangerous: whether --dangerous should be passed to install snaps without a signature
Raises:
Expand All @@ -1033,6 +1061,8 @@ def install_local(
]
if classic:
args.append("--classic")
if devmode:
args.append("--devmode")
if dangerous:
args.append("--dangerous")
try:
Expand Down
31 changes: 31 additions & 0 deletions tests/unit/test_snap.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,37 @@ def test_can_run_snap_commands(self, mock_subprocess):
["snap", "install", "foo", "--classic", '--revision="123"'], universal_newlines=True
)

@patch("charms.operator_libs_linux.v2.snap.subprocess.check_output")
def test_can_run_snap_commands_devmode(self, mock_subprocess):
mock_subprocess.return_value = 0
foo = snap.Snap("foo", snap.SnapState.Present, "stable", "1", "devmode")
self.assertEqual(foo.present, True)

foo.ensure(snap.SnapState.Absent)
mock_subprocess.assert_called_with(["snap", "remove", "foo"], universal_newlines=True)

foo.ensure(snap.SnapState.Latest, devmode=True, channel="latest/edge")

mock_subprocess.assert_called_with(
[
"snap",
"install",
"foo",
"--devmode",
'--channel="latest/edge"',
],
universal_newlines=True,
)
self.assertEqual(foo.latest, True)

foo.state = snap.SnapState.Absent
mock_subprocess.assert_called_with(["snap", "remove", "foo"], universal_newlines=True)

foo.ensure(snap.SnapState.Latest, revision=123)
mock_subprocess.assert_called_with(
["snap", "install", "foo", "--devmode", '--revision="123"'], universal_newlines=True
)

@patch("charms.operator_libs_linux.v2.snap.subprocess.run")
def test_can_run_snap_daemon_commands(self, mock_subprocess):
mock_subprocess.return_value = MagicMock()
Expand Down

0 comments on commit 2680e28

Please sign in to comment.