mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Pacman: Add support for install reason (#4956)
* Pacman: Add support for setting install reason * Improved description * Fix documentation * Add changelog fragment * Use source for installation * Get all reasons at once * Removed default for reason * Added version info to documentation * Fix NameError * Moved determination of reason to _build_inventory * Fix duplication and sanity errors * adjust tests for changed inventory * Documentation: remove empty default for reason * mention packages with changed reason in exit params/info * Added integration tests for reason and reason_for Inspired by the integration tests for url packages * Correct indentation * Fix indentation * Also sort changed packages in normal mode * Also sort result in unit test * Apply suggestions from code review Co-authored-by: Felix Fontein <felix@fontein.de> Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
d214f49be7
commit
9f3841703f
5 changed files with 230 additions and 9 deletions
2
changelogs/fragments/4956-pacman-install-reason.yaml
Normal file
2
changelogs/fragments/4956-pacman-install-reason.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- pacman - added parameters ``reason`` and ``reason_for`` to set/change the install reason of packages (https://github.com/ansible-collections/community.general/pull/4956).
|
|
@ -104,6 +104,22 @@ options:
|
|||
default:
|
||||
type: str
|
||||
|
||||
reason:
|
||||
description:
|
||||
- The install reason to set for the packages.
|
||||
choices: [ dependency, explicit ]
|
||||
type: str
|
||||
version_added: 5.4.0
|
||||
|
||||
reason_for:
|
||||
description:
|
||||
- Set the install reason for C(all) packages or only for C(new) packages.
|
||||
- In case of C(state=latest) already installed packages which will be updated to a newer version are not counted as C(new).
|
||||
default: new
|
||||
choices: [ all, new ]
|
||||
type: str
|
||||
version_added: 5.4.0
|
||||
|
||||
notes:
|
||||
- When used with a C(loop:) each package will be processed individually,
|
||||
it is much more efficient to pass the list directly to the I(name) option.
|
||||
|
@ -223,6 +239,20 @@ EXAMPLES = """
|
|||
name: baz
|
||||
state: absent
|
||||
force: yes
|
||||
|
||||
- name: Install foo as dependency and leave reason untouched if already installed
|
||||
community.general.pacman:
|
||||
name: foo
|
||||
state: present
|
||||
reason: dependency
|
||||
reason_for: new
|
||||
|
||||
- name: Run the equivalent of "pacman -S --asexplicit", mark foo as explicit and install it if not present
|
||||
community.general.pacman:
|
||||
name: foo
|
||||
state: present
|
||||
reason: explicit
|
||||
reason_for: all
|
||||
"""
|
||||
|
||||
import shlex
|
||||
|
@ -331,7 +361,14 @@ class Pacman(object):
|
|||
def install_packages(self, pkgs):
|
||||
pkgs_to_install = []
|
||||
pkgs_to_install_from_url = []
|
||||
pkgs_to_set_reason = []
|
||||
for p in pkgs:
|
||||
if self.m.params["reason"] and (
|
||||
p.name not in self.inventory["pkg_reasons"]
|
||||
or self.m.params["reason_for"] == "all"
|
||||
and self.inventory["pkg_reasons"][p.name] != self.m.params["reason"]
|
||||
):
|
||||
pkgs_to_set_reason.append(p.name)
|
||||
if p.source_is_URL:
|
||||
# URL packages bypass the latest / upgradable_pkgs test
|
||||
# They go through the dry-run to let pacman decide if they will be installed
|
||||
|
@ -344,7 +381,7 @@ class Pacman(object):
|
|||
):
|
||||
pkgs_to_install.append(p)
|
||||
|
||||
if len(pkgs_to_install) == 0 and len(pkgs_to_install_from_url) == 0:
|
||||
if len(pkgs_to_install) == 0 and len(pkgs_to_install_from_url) == 0 and len(pkgs_to_set_reason) == 0:
|
||||
self.exit_params["packages"] = []
|
||||
self.add_exit_infos("package(s) already installed")
|
||||
return
|
||||
|
@ -377,7 +414,12 @@ class Pacman(object):
|
|||
continue
|
||||
name, version = p.split()
|
||||
if name in self.inventory["installed_pkgs"]:
|
||||
before.append("%s-%s" % (name, self.inventory["installed_pkgs"][name]))
|
||||
before.append("%s-%s-%s" % (name, self.inventory["installed_pkgs"][name], self.inventory["pkg_reasons"][name]))
|
||||
if name in pkgs_to_set_reason:
|
||||
after.append("%s-%s-%s" % (name, version, self.m.params["reason"]))
|
||||
elif name in self.inventory["pkg_reasons"]:
|
||||
after.append("%s-%s-%s" % (name, version, self.inventory["pkg_reasons"][name]))
|
||||
else:
|
||||
after.append("%s-%s" % (name, version))
|
||||
to_be_installed.append(name)
|
||||
|
||||
|
@ -398,7 +440,7 @@ class Pacman(object):
|
|||
before.extend(b)
|
||||
after.extend(a)
|
||||
|
||||
if len(installed_pkgs) == 0:
|
||||
if len(installed_pkgs) == 0 and len(pkgs_to_set_reason) == 0:
|
||||
# This can happen with URL packages if pacman decides there's nothing to do
|
||||
self.exit_params["packages"] = []
|
||||
self.add_exit_infos("package(s) already installed")
|
||||
|
@ -411,9 +453,11 @@ class Pacman(object):
|
|||
"after": "\n".join(sorted(after)) + "\n" if after else "",
|
||||
}
|
||||
|
||||
changed_reason_pkgs = [p for p in pkgs_to_set_reason if p not in installed_pkgs]
|
||||
|
||||
if self.m.check_mode:
|
||||
self.add_exit_infos("Would have installed %d packages" % len(installed_pkgs))
|
||||
self.exit_params["packages"] = sorted(installed_pkgs)
|
||||
self.add_exit_infos("Would have installed %d packages" % (len(installed_pkgs) + len(changed_reason_pkgs)))
|
||||
self.exit_params["packages"] = sorted(installed_pkgs + changed_reason_pkgs)
|
||||
return
|
||||
|
||||
# actually do it
|
||||
|
@ -430,8 +474,22 @@ class Pacman(object):
|
|||
if pkgs_to_install_from_url:
|
||||
_install_packages_for_real("--upgrade", pkgs_to_install_from_url)
|
||||
|
||||
self.exit_params["packages"] = installed_pkgs
|
||||
self.add_exit_infos("Installed %d package(s)" % len(installed_pkgs))
|
||||
# set reason
|
||||
if pkgs_to_set_reason:
|
||||
cmd = [self.pacman_path, "--noconfirm", "--database"]
|
||||
if self.m.params["reason"] == "dependency":
|
||||
cmd.append("--asdeps")
|
||||
else:
|
||||
cmd.append("--asexplicit")
|
||||
cmd.extend(pkgs_to_set_reason)
|
||||
|
||||
rc, stdout, stderr = self.m.run_command(cmd, check_rc=False)
|
||||
if rc != 0:
|
||||
self.fail("Failed to install package(s)", cmd=cmd, stdout=stdout, stderr=stderr)
|
||||
self.add_exit_infos(stdout=stdout, stderr=stderr)
|
||||
|
||||
self.exit_params["packages"] = sorted(installed_pkgs + changed_reason_pkgs)
|
||||
self.add_exit_infos("Installed %d package(s)" % (len(installed_pkgs) + len(changed_reason_pkgs)))
|
||||
|
||||
def remove_packages(self, pkgs):
|
||||
# filter out pkgs that are already absent
|
||||
|
@ -631,6 +689,7 @@ class Pacman(object):
|
|||
"available_pkgs": {pkgname: version},
|
||||
"available_groups": {groupname: set(pkgnames)},
|
||||
"upgradable_pkgs": {pkgname: (current_version,latest_version)},
|
||||
"pkg_reasons": {pkgname: reason},
|
||||
}
|
||||
|
||||
Fails the module if a package requested for install cannot be found
|
||||
|
@ -723,12 +782,31 @@ class Pacman(object):
|
|||
rc=rc,
|
||||
)
|
||||
|
||||
pkg_reasons = {}
|
||||
dummy, stdout, dummy = self.m.run_command([self.pacman_path, "--query", "--explicit"], check_rc=True)
|
||||
# Format of a line: "pacman 6.0.1-2"
|
||||
for l in stdout.splitlines():
|
||||
l = l.strip()
|
||||
if not l:
|
||||
continue
|
||||
pkg = l.split()[0]
|
||||
pkg_reasons[pkg] = "explicit"
|
||||
dummy, stdout, dummy = self.m.run_command([self.pacman_path, "--query", "--deps"], check_rc=True)
|
||||
# Format of a line: "pacman 6.0.1-2"
|
||||
for l in stdout.splitlines():
|
||||
l = l.strip()
|
||||
if not l:
|
||||
continue
|
||||
pkg = l.split()[0]
|
||||
pkg_reasons[pkg] = "dependency"
|
||||
|
||||
return dict(
|
||||
installed_pkgs=installed_pkgs,
|
||||
installed_groups=installed_groups,
|
||||
available_pkgs=available_pkgs,
|
||||
available_groups=available_groups,
|
||||
upgradable_pkgs=upgradable_pkgs,
|
||||
pkg_reasons=pkg_reasons,
|
||||
)
|
||||
|
||||
|
||||
|
@ -749,6 +827,8 @@ def setup_module():
|
|||
upgrade_extra_args=dict(type="str", default=""),
|
||||
update_cache=dict(type="bool"),
|
||||
update_cache_extra_args=dict(type="str", default=""),
|
||||
reason=dict(type="str", choices=["explicit", "dependency"]),
|
||||
reason_for=dict(type="str", default="new", choices=["new", "all"]),
|
||||
),
|
||||
required_one_of=[["name", "update_cache", "upgrade"]],
|
||||
mutually_exclusive=[["name", "upgrade"]],
|
||||
|
|
|
@ -12,3 +12,4 @@
|
|||
- include: 'remove_nosave.yml'
|
||||
- include: 'update_cache.yml'
|
||||
- include: 'locally_installed_package.yml'
|
||||
- include: 'reason.yml'
|
||||
|
|
97
tests/integration/targets/pacman/tasks/reason.yml
Normal file
97
tests/integration/targets/pacman/tasks/reason.yml
Normal file
|
@ -0,0 +1,97 @@
|
|||
---
|
||||
- vars:
|
||||
reg_pkg: ed
|
||||
url_pkg: lemon
|
||||
file_pkg: hdparm
|
||||
file_pkg_path: /tmp/pkg.zst
|
||||
extra_pkg: core/sdparm
|
||||
extra_pkg_outfmt: sdparm
|
||||
block:
|
||||
- name: Make sure that test packages are not installed
|
||||
pacman:
|
||||
name:
|
||||
- '{{reg_pkg}}'
|
||||
- '{{url_pkg}}'
|
||||
- '{{file_pkg}}'
|
||||
- '{{extra_pkg}}'
|
||||
state: absent
|
||||
|
||||
- name: Get URL for {{url_pkg}}
|
||||
command:
|
||||
cmd: pacman --sync --print-format "%l" {{url_pkg}}
|
||||
register: url_pkg_url
|
||||
|
||||
- name: Get URL for {{file_pkg}}
|
||||
command:
|
||||
cmd: pacman --sync --print-format "%l" {{file_pkg}}
|
||||
register: file_pkg_url
|
||||
- name: Download {{file_pkg}} pkg
|
||||
get_url:
|
||||
url: '{{file_pkg_url.stdout}}'
|
||||
dest: '{{file_pkg_path}}'
|
||||
|
||||
- name: Install packages from mixed sources as dependency (check mode)
|
||||
pacman:
|
||||
name:
|
||||
- '{{reg_pkg}}'
|
||||
- '{{url_pkg_url.stdout}}'
|
||||
- '{{file_pkg_path}}'
|
||||
reason: dependency
|
||||
check_mode: True
|
||||
register: install_1
|
||||
|
||||
- name: Install packages from mixed sources as explicit
|
||||
pacman:
|
||||
name:
|
||||
- '{{reg_pkg}}'
|
||||
- '{{url_pkg_url.stdout}}'
|
||||
- '{{file_pkg_path}}'
|
||||
reason: explicit
|
||||
register: install_2
|
||||
|
||||
- name: Install packages from mixed sources with new packages being installed as dependency - (idempotency)
|
||||
pacman:
|
||||
name:
|
||||
- '{{reg_pkg}}'
|
||||
- '{{url_pkg_url.stdout}}'
|
||||
- '{{file_pkg_path}}'
|
||||
reason: dependency
|
||||
register: install_3
|
||||
|
||||
- name: Install new package with already installed packages from mixed sources as dependency
|
||||
pacman:
|
||||
name:
|
||||
- '{{reg_pkg}}'
|
||||
- '{{url_pkg_url.stdout}}'
|
||||
- '{{file_pkg_path}}'
|
||||
- '{{extra_pkg}}'
|
||||
reason: dependency
|
||||
register: install_4
|
||||
|
||||
- name: Set install reason for all packages to dependency
|
||||
pacman:
|
||||
name:
|
||||
- '{{reg_pkg}}'
|
||||
- '{{url_pkg_url.stdout}}'
|
||||
- '{{file_pkg_path}}'
|
||||
- '{{extra_pkg}}'
|
||||
reason: dependency
|
||||
reason_for: all
|
||||
register: install_5
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- install_1 is changed
|
||||
- install_1.msg == 'Would have installed 3 packages'
|
||||
- install_1.packages|sort() == [reg_pkg, url_pkg, file_pkg]|sort()
|
||||
- install_2 is changed
|
||||
- install_2.msg == 'Installed 3 package(s)'
|
||||
- install_2.packages|sort() == [reg_pkg, url_pkg, file_pkg]|sort()
|
||||
- install_3 is not changed
|
||||
- install_3.msg == 'package(s) already installed'
|
||||
- install_4 is changed
|
||||
- install_4.msg == 'Installed 1 package(s)'
|
||||
- install_4.packages == [extra_pkg_outfmt]
|
||||
- install_5 is changed
|
||||
- install_5.msg == 'Installed 3 package(s)'
|
||||
- install_5.packages|sort() == [reg_pkg, url_pkg, file_pkg]|sort()
|
|
@ -100,6 +100,19 @@ valid_inventory = {
|
|||
"upgradable_pkgs": {
|
||||
"sqlite": VersionTuple(current="3.36.0-1", latest="3.37.0-1"),
|
||||
},
|
||||
"pkg_reasons": {
|
||||
"file": "explicit",
|
||||
"filesystem": "explicit",
|
||||
"findutils": "explicit",
|
||||
"gawk": "explicit",
|
||||
"gettext": "explicit",
|
||||
"grep": "explicit",
|
||||
"gzip": "explicit",
|
||||
"pacman": "explicit",
|
||||
"pacman-mirrorlist": "dependency",
|
||||
"sed": "explicit",
|
||||
"sqlite": "explicit",
|
||||
},
|
||||
}
|
||||
|
||||
empty_inventory = {
|
||||
|
@ -108,6 +121,7 @@ empty_inventory = {
|
|||
"installed_groups": {},
|
||||
"available_groups": {},
|
||||
"upgradable_pkgs": {},
|
||||
"pkg_reasons": {},
|
||||
}
|
||||
|
||||
|
||||
|
@ -255,6 +269,27 @@ class TestPacman:
|
|||
""",
|
||||
"",
|
||||
),
|
||||
( # pacman --query --explicit
|
||||
0,
|
||||
"""file 5.41-1
|
||||
filesystem 2021.11.11-1
|
||||
findutils 4.8.0-1
|
||||
gawk 5.1.1-1
|
||||
gettext 0.21-1
|
||||
grep 3.7-1
|
||||
gzip 1.11-1
|
||||
pacman 6.0.1-2
|
||||
sed 4.8-1
|
||||
sqlite 3.36.0-1
|
||||
""",
|
||||
"",
|
||||
),
|
||||
( # pacman --query --deps
|
||||
0,
|
||||
"""pacman-mirrorlist 20211114-1
|
||||
""",
|
||||
"",
|
||||
),
|
||||
],
|
||||
None,
|
||||
),
|
||||
|
@ -272,6 +307,8 @@ class TestPacman:
|
|||
"",
|
||||
"warning: config file /etc/pacman.conf, line 34: directive 'TotalDownload' in section 'options' not recognized.",
|
||||
),
|
||||
(0, "", ""),
|
||||
(0, "", ""),
|
||||
],
|
||||
None,
|
||||
),
|
||||
|
@ -288,6 +325,8 @@ class TestPacman:
|
|||
"partial\npkg\\nlist",
|
||||
"some warning",
|
||||
),
|
||||
(0, "", ""),
|
||||
(0, "", ""),
|
||||
],
|
||||
AnsibleFailJson,
|
||||
),
|
||||
|
@ -375,6 +414,8 @@ class TestPacman:
|
|||
(["pacman", "--query", "--groups"], {'check_rc': True}, 0, '', ''),
|
||||
(["pacman", "--sync", "--groups", "--groups"], {'check_rc': True}, 0, '', ''),
|
||||
(["pacman", "--query", "--upgrades"], {'check_rc': False}, 0, '', ''),
|
||||
(["pacman", "--query", "--explicit"], {'check_rc': True}, 0, 'foo 1.0.0-1', ''),
|
||||
(["pacman", "--query", "--deps"], {'check_rc': True}, 0, '', ''),
|
||||
],
|
||||
False,
|
||||
),
|
||||
|
@ -843,7 +884,7 @@ class TestPacman:
|
|||
],
|
||||
"state": "present",
|
||||
},
|
||||
["sudo", "somepackage", "otherpkg"],
|
||||
["otherpkg", "somepackage", "sudo"],
|
||||
[
|
||||
Package("sudo", "sudo"),
|
||||
Package("grep", "grep"),
|
||||
|
|
Loading…
Reference in a new issue