1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

snap: add param "dangerous" (#6908)

* snap: add param "dangerous"

* adjusted run_command out for simple test case

* add changelog frag
This commit is contained in:
Alexei Znamensky 2023-07-15 22:54:39 +12:00 committed by GitHub
parent 70503411ee
commit ea6fb9da8f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 159 additions and 11 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- snap - add option ``dangerous`` to the module, that will map into the command line argument ``--dangerous``, allowing unsigned snap files to be installed (https://github.com/ansible-collections/community.general/pull/6908, https://github.com/ansible-collections/community.general/issues/5715).

View file

@ -39,6 +39,8 @@ def snap_runner(module, **kwargs):
classic=cmd_runner_fmt.as_bool("--classic"), classic=cmd_runner_fmt.as_bool("--classic"),
channel=cmd_runner_fmt.as_func(lambda v: [] if v == 'stable' else ['--channel', '{0}'.format(v)]), channel=cmd_runner_fmt.as_func(lambda v: [] if v == 'stable' else ['--channel', '{0}'.format(v)]),
options=cmd_runner_fmt.as_list(), options=cmd_runner_fmt.as_list(),
info=cmd_runner_fmt.as_fixed("info"),
dangerous=cmd_runner_fmt.as_bool("--dangerous"),
), ),
check_rc=False, check_rc=False,
**kwargs **kwargs

View file

@ -17,7 +17,7 @@ DOCUMENTATION = '''
module: snap module: snap
short_description: Manages snaps short_description: Manages snaps
description: description:
- "Manages snaps packages." - Manages snaps packages.
extends_documentation_fragment: extends_documentation_fragment:
- community.general.attributes - community.general.attributes
attributes: attributes:
@ -28,7 +28,11 @@ attributes:
options: options:
name: name:
description: description:
- Name of the snaps. - Name of the snaps to be installed.
- Any named snap accepted by the C(snap) command is valid.
- >
Notice that snap files might require O(dangerous=true) to ignore the error
"cannot find signatures with metadata for snap".
required: true required: true
type: list type: list
elements: str elements: str
@ -45,7 +49,7 @@ options:
description: description:
- Confinement policy. The classic confinement allows a snap to have - Confinement policy. The classic confinement allows a snap to have
the same level of access to the system as "classic" packages, the same level of access to the system as "classic" packages,
like those managed by APT. This option corresponds to the --classic argument. like those managed by APT. This option corresponds to the C(--classic) argument.
This option can only be specified if there is a single snap in the task. This option can only be specified if there is a single snap in the task.
type: bool type: bool
required: false required: false
@ -69,6 +73,14 @@ options:
type: list type: list
elements: str elements: str
version_added: 4.4.0 version_added: 4.4.0
dangerous:
description:
- Install the given snap file even if there are no pre-acknowledged signatures for it,
meaning it was not verified and could be dangerous.
type: bool
required: false
default: false
version_added: 7.2.0
author: author:
- Victor Carceler (@vcarceler) <vcarceler@iespuigcastellar.xeill.net> - Victor Carceler (@vcarceler) <vcarceler@iespuigcastellar.xeill.net>
@ -179,6 +191,7 @@ class Snap(StateModuleHelper):
'classic': dict(type='bool', default=False), 'classic': dict(type='bool', default=False),
'channel': dict(type='str'), 'channel': dict(type='str'),
'options': dict(type='list', elements='str'), 'options': dict(type='list', elements='str'),
'dangerous': dict(type='bool', default=False),
}, },
supports_check_mode=True, supports_check_mode=True,
) )
@ -193,7 +206,16 @@ class Snap(StateModuleHelper):
def __init_module__(self): def __init_module__(self):
self.runner = snap_runner(self.module) self.runner = snap_runner(self.module)
self.vars.set("snap_status", self.snap_status(self.vars.name, self.vars.channel), output=False) # if state=present there might be file names passed in 'name', in
# which case they must be converted to their actual snap names, which
# is done using the names_from_snaps() method calling 'snap info'.
if self.vars.state == "present":
self.vars.set("snapinfo_run_info", [], output=(self.verbosity >= 4))
self.vars.set("snap_names", self.names_from_snaps(self.vars.name))
status_var = "snap_names"
else:
status_var = "name"
self.vars.set("snap_status", self.snap_status(self.vars[status_var], self.vars.channel), output=False)
self.vars.set("snap_status_map", dict(zip(self.vars.name, self.vars.snap_status)), output=False) self.vars.set("snap_status_map", dict(zip(self.vars.name, self.vars.snap_status)), output=False)
def _run_multiple_commands(self, commands, actionable_names, bundle=True, refresh=False): def _run_multiple_commands(self, commands, actionable_names, bundle=True, refresh=False):
@ -269,11 +291,44 @@ class Snap(StateModuleHelper):
try: try:
option_map = self.convert_json_to_map(out) option_map = self.convert_json_to_map(out)
return option_map
except Exception as e: except Exception as e:
self.do_raise( self.do_raise(
msg="Parsing option map returned by 'snap get {0}' triggers exception '{1}', output:\n'{2}'".format(snap_name, str(e), out)) msg="Parsing option map returned by 'snap get {0}' triggers exception '{1}', output:\n'{2}'".format(snap_name, str(e), out))
return option_map def names_from_snaps(self, snaps):
def process_one(rc, out, err):
res = [line for line in out.split("\n") if line.startswith("name:")]
name = res[0].split()[1]
return [name]
def process_many(rc, out, err):
outputs = out.split("---")
res = []
for sout in outputs:
res.extend(process_one(rc, sout, ""))
return res
def process(rc, out, err):
if len(snaps) == 1:
check_error = err
process_ = process_one
else:
check_error = out
process_ = process_many
if "warning: no snap found" in check_error:
self.do_raise("Snaps not found: {0}.".format([x.split()[-1]
for x in out.split('\n')
if x.startswith("warning: no snap found")]))
return process_(rc, out, err)
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
def snap_status(self, snap_name, channel): def snap_status(self, snap_name, channel):
def _status_check(name, channel, installed): def _status_check(name, channel, installed):
@ -287,14 +342,14 @@ class Snap(StateModuleHelper):
with self.runner("_list") as ctx: with self.runner("_list") as ctx:
rc, out, err = ctx.run(check_rc=True) rc, out, err = ctx.run(check_rc=True)
out = out.split('\n')[1:] list_out = out.split('\n')[1:]
out = [self.__list_re.match(x) for x in out] list_out = [self.__list_re.match(x) for x in list_out]
out = [(m.group('name'), m.group('channel')) for m in out if m] list_out = [(m.group('name'), m.group('channel')) for m in list_out if m]
if self.verbosity >= 4: if self.verbosity >= 4:
self.vars.status_out = out self.vars.status_out = list_out
self.vars.status_run_info = ctx.run_info self.vars.status_run_info = ctx.run_info
return [_status_check(n, channel, out) for n in snap_name] return [_status_check(n, channel, list_out) for n in snap_name]
def is_snap_enabled(self, snap_name): def is_snap_enabled(self, snap_name):
with self.runner("_list name") as ctx: with self.runner("_list name") as ctx:
@ -315,7 +370,7 @@ class Snap(StateModuleHelper):
if self.check_mode: if self.check_mode:
return return
params = ['state', 'classic', 'channel'] # get base cmd parts params = ['state', 'classic', 'channel', 'dangerous'] # get base cmd parts
has_one_pkg_params = bool(self.vars.classic) or self.vars.channel != 'stable' has_one_pkg_params = bool(self.vars.classic) or self.vars.channel != 'stable'
has_multiple_snaps = len(actionable_snaps) > 1 has_multiple_snaps = len(actionable_snaps) > 1

View file

@ -5,3 +5,4 @@
dependencies: dependencies:
- setup_snap - setup_snap
- setup_remote_tmp_dir

View file

@ -15,3 +15,5 @@
ansible.builtin.include_tasks: test.yml ansible.builtin.include_tasks: test.yml
- name: Include test_channel - name: Include test_channel
ansible.builtin.include_tasks: test_channel.yml ansible.builtin.include_tasks: test_channel.yml
- name: Include test_dangerous
ansible.builtin.include_tasks: test_dangerous.yml

View file

@ -0,0 +1,51 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
- name: Make sure package is not installed (cider)
community.general.snap:
name: cider
state: absent
- name: Download cider snap
ansible.builtin.get_url:
url: https://github.com/ciderapp/cider-releases/releases/download/v1.6.0/cider_1.6.0_amd64.snap
dest: "{{ remote_tmp_dir }}/cider_1.6.0_amd64.snap"
mode: "0644"
# Test for https://github.com/ansible-collections/community.general/issues/5715
- name: Install package from file (check)
community.general.snap:
name: "{{ remote_tmp_dir }}/cider_1.6.0_amd64.snap"
dangerous: true
state: present
check_mode: true
register: install_dangerous_check
- name: Install package from file
community.general.snap:
name: "{{ remote_tmp_dir }}/cider_1.6.0_amd64.snap"
dangerous: true
state: present
register: install_dangerous
- name: Install package from file
community.general.snap:
name: "{{ remote_tmp_dir }}/cider_1.6.0_amd64.snap"
dangerous: true
state: present
register: install_dangerous_idempot
- name: Remove package
community.general.snap:
name: cider
state: absent
register: remove_dangerous
- assert:
that:
- install_dangerous_check is changed
- install_dangerous is changed
- install_dangerous_idempot is not changed
- remove_dangerous is changed

View file

@ -395,11 +395,46 @@ issue_6803_kubectl_out = (
) )
TEST_CASES = [ TEST_CASES = [
ModuleTestCase(
id="simple case",
input={"name": ["hello-world"]},
output=dict(changed=True, snaps_installed=["hello-world"]),
run_command_calls=[
RunCmdCall(
command=['/testbin/snap', 'info', 'hello-world'],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out='name: hello-world\n',
err="",
),
RunCmdCall(
command=['/testbin/snap', 'list'],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="",
err="",
),
RunCmdCall(
command=['/testbin/snap', 'install', 'hello-world'],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="hello-world (12345/stable) v12345 from Canonical** installed\n",
err="",
),
]
),
ModuleTestCase( ModuleTestCase(
id="issue_6803", id="issue_6803",
input={"name": ["microk8s", "kubectl"], "classic": True}, input={"name": ["microk8s", "kubectl"], "classic": True},
output=dict(changed=True, snaps_installed=["microk8s", "kubectl"]), output=dict(changed=True, snaps_installed=["microk8s", "kubectl"]),
run_command_calls=[ run_command_calls=[
RunCmdCall(
command=['/testbin/snap', 'info', 'microk8s', 'kubectl'],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out='name: microk8s\n---\nname: kubectl\n',
err="",
),
RunCmdCall( RunCmdCall(
command=['/testbin/snap', 'list'], command=['/testbin/snap', 'list'],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False}, environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},