From 862d7d7aaf941125ecca38fbd6b65c74e7fe7b7c Mon Sep 17 00:00:00 2001 From: Alexei Znamensky <103110+russoz@users.noreply.github.com> Date: Thu, 14 May 2026 22:02:28 +1200 Subject: [PATCH] snap: support `system` as a configuration target (#12025) * feat(snap): support snap system configuration via name=system Treat `system` as a virtual snap that is always considered installed, bypassing snap info lookup and install/refresh logic, while still allowing snap set/get operations via the options parameter. Fixes #11266 Co-Authored-By: Claude Sonnet 4.6 * docs(snap): note version_added for system support; add changelog fragment Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Sonnet 4.6 --- changelogs/fragments/12025-snap-system.yml | 5 ++ plugins/modules/snap.py | 40 ++++++++--- tests/unit/plugins/modules/test_snap.py | 84 ++++++++++++++++++++++ 3 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 changelogs/fragments/12025-snap-system.yml diff --git a/changelogs/fragments/12025-snap-system.yml b/changelogs/fragments/12025-snap-system.yml new file mode 100644 index 0000000000..41281a411c --- /dev/null +++ b/changelogs/fragments/12025-snap-system.yml @@ -0,0 +1,5 @@ +minor_changes: + - snap - add support for ``system`` as a special configuration target, allowing + ``snap set system`` to be used via the ``options`` parameter + (https://github.com/ansible-collections/community.general/issues/11266, + https://github.com/ansible-collections/community.general/pull/12025). diff --git a/plugins/modules/snap.py b/plugins/modules/snap.py index d5585b772b..629b2482cf 100644 --- a/plugins/modules/snap.py +++ b/plugins/modules/snap.py @@ -29,6 +29,10 @@ options: - Name of the snaps to be installed. - Any named snap accepted by the C(snap) command is valid. - O(dangerous=true) may be necessary when installing C(.snap) files. See O(dangerous) for more details. + - The special name V(system) refers to the snapd system-wide configuration namespace. + When used with O(options), it runs C(snap set system ). + It is always considered present and only V(state=present) and O(options) are meaningful for it. + Support for V(system) was added in community.general 13.0.0. required: true type: list elements: str @@ -155,6 +159,14 @@ EXAMPLES = r""" name: helm classic: true revision: 481 + +# Set snap system-wide configuration +- name: Configure snapd proxy + community.general.snap: + name: system + options: + - proxy.http=http://proxy.example.com:3128/ + - proxy.https=http://proxy.example.com:3128/ """ RETURN = r""" @@ -201,6 +213,8 @@ import re from ansible.module_utils.common.text.converters import to_native +_VIRTUAL_SNAPS = frozenset({"system"}) + from ansible_collections.community.general.plugins.module_utils._module_helper import StateModuleHelper from ansible_collections.community.general.plugins.module_utils._snap import get_version, snap_runner @@ -345,6 +359,11 @@ class Snap(StateModuleHelper): ) def names_from_snaps(self, snaps): + real_snaps = [s for s in snaps if s not in _VIRTUAL_SNAPS] + + if not real_snaps: + return list(snaps) + def process_one(rc, out, err): res = [line for line in out.split("\n") if line.startswith("name:")] name = res[0].split()[1] @@ -361,7 +380,7 @@ class Snap(StateModuleHelper): return res def process(rc, out, err): - if len(snaps) == 1: + if len(real_snaps) == 1: check_error = err process_ = process_one else: @@ -373,17 +392,20 @@ class Snap(StateModuleHelper): self.do_raise(f"Snaps not found: {snaps_not_found}.") return process_(rc, out, err) - names = [] - if snaps: - with self.runner("info name", output_process=process) as ctx: - try: - names = ctx.run(name=snaps) - finally: - self.vars.snapinfo_run_info.append(ctx.run_info) - return names + real_names = [] + with self.runner("info name", output_process=process) as ctx: + try: + real_names = ctx.run(name=real_snaps) + finally: + self.vars.snapinfo_run_info.append(ctx.run_info) + + real_name_iter = iter(real_names) + return [s if s in _VIRTUAL_SNAPS else next(real_name_iter) for s in snaps] def snap_status(self, snap_name, channel, revision=None): def _status_check(name, channel, revision, installed): + if name in _VIRTUAL_SNAPS: + return Snap.INSTALLED match = [(r, c) for n, r, c in installed if n == name] if not match: return Snap.NOT_INSTALLED diff --git a/tests/unit/plugins/modules/test_snap.py b/tests/unit/plugins/modules/test_snap.py index 9bea05aa34..c210adcec5 100644 --- a/tests/unit/plugins/modules/test_snap.py +++ b/tests/unit/plugins/modules/test_snap.py @@ -438,6 +438,90 @@ TEST_SPEC = dict( ], ), ), + dict( + id="set_system_option", + input={"name": ["system"], "options": ["proxy.http=http://proxy.example.com:3128/"]}, + output=dict(changed=True, options_changed=["system:proxy.http=http://proxy.example.com:3128/"]), + flags={}, + mocks=dict( + run_command=[ + dict( + command=["/testbin/snap", "version"], + environ=default_env, + rc=0, + out=default_version_out, + err="", + ), + # No "snap info system" — virtual snap bypasses names_from_snaps + dict( + command=["/testbin/snap", "list"], + environ=default_env, + rc=0, + out="", + err="", + ), + dict( + command=["/testbin/snap", "get", "-d", "system"], + environ=default_env, + rc=0, + out="{}", + err="", + ), + dict( + command=["/testbin/snap", "set", "system", "proxy.http=http://proxy.example.com:3128/"], + environ=default_env, + rc=0, + out="", + err="", + ), + dict( + command=["/testbin/snap", "list"], + environ=default_env, + rc=0, + out="", + err="", + ), + ], + ), + ), + dict( + id="set_system_option_idempotent", + input={"name": ["system"], "options": ["proxy.http=http://proxy.example.com:3128/"]}, + output=dict(changed=False), + flags={}, + mocks=dict( + run_command=[ + dict( + command=["/testbin/snap", "version"], + environ=default_env, + rc=0, + out=default_version_out, + err="", + ), + dict( + command=["/testbin/snap", "list"], + environ=default_env, + rc=0, + out="", + err="", + ), + dict( + command=["/testbin/snap", "get", "-d", "system"], + environ=default_env, + rc=0, + out='{"proxy": {"http": "http://proxy.example.com:3128/"}}', + err="", + ), + dict( + command=["/testbin/snap", "list"], + environ=default_env, + rc=0, + out="", + err="", + ), + ], + ), + ), dict( id="issue_6803", input={"name": ["microk8s", "kubectl"], "classic": True},