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:
|
default:
|
||||||
type: str
|
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:
|
notes:
|
||||||
- When used with a C(loop:) each package will be processed individually,
|
- 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.
|
it is much more efficient to pass the list directly to the I(name) option.
|
||||||
|
@ -223,6 +239,20 @@ EXAMPLES = """
|
||||||
name: baz
|
name: baz
|
||||||
state: absent
|
state: absent
|
||||||
force: yes
|
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
|
import shlex
|
||||||
|
@ -331,7 +361,14 @@ class Pacman(object):
|
||||||
def install_packages(self, pkgs):
|
def install_packages(self, pkgs):
|
||||||
pkgs_to_install = []
|
pkgs_to_install = []
|
||||||
pkgs_to_install_from_url = []
|
pkgs_to_install_from_url = []
|
||||||
|
pkgs_to_set_reason = []
|
||||||
for p in pkgs:
|
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:
|
if p.source_is_URL:
|
||||||
# URL packages bypass the latest / upgradable_pkgs test
|
# URL packages bypass the latest / upgradable_pkgs test
|
||||||
# They go through the dry-run to let pacman decide if they will be installed
|
# 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)
|
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.exit_params["packages"] = []
|
||||||
self.add_exit_infos("package(s) already installed")
|
self.add_exit_infos("package(s) already installed")
|
||||||
return
|
return
|
||||||
|
@ -377,7 +414,12 @@ class Pacman(object):
|
||||||
continue
|
continue
|
||||||
name, version = p.split()
|
name, version = p.split()
|
||||||
if name in self.inventory["installed_pkgs"]:
|
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))
|
after.append("%s-%s" % (name, version))
|
||||||
to_be_installed.append(name)
|
to_be_installed.append(name)
|
||||||
|
|
||||||
|
@ -398,7 +440,7 @@ class Pacman(object):
|
||||||
before.extend(b)
|
before.extend(b)
|
||||||
after.extend(a)
|
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
|
# This can happen with URL packages if pacman decides there's nothing to do
|
||||||
self.exit_params["packages"] = []
|
self.exit_params["packages"] = []
|
||||||
self.add_exit_infos("package(s) already installed")
|
self.add_exit_infos("package(s) already installed")
|
||||||
|
@ -411,9 +453,11 @@ class Pacman(object):
|
||||||
"after": "\n".join(sorted(after)) + "\n" if after else "",
|
"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:
|
if self.m.check_mode:
|
||||||
self.add_exit_infos("Would have installed %d packages" % len(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)
|
self.exit_params["packages"] = sorted(installed_pkgs + changed_reason_pkgs)
|
||||||
return
|
return
|
||||||
|
|
||||||
# actually do it
|
# actually do it
|
||||||
|
@ -430,8 +474,22 @@ class Pacman(object):
|
||||||
if pkgs_to_install_from_url:
|
if pkgs_to_install_from_url:
|
||||||
_install_packages_for_real("--upgrade", pkgs_to_install_from_url)
|
_install_packages_for_real("--upgrade", pkgs_to_install_from_url)
|
||||||
|
|
||||||
self.exit_params["packages"] = installed_pkgs
|
# set reason
|
||||||
self.add_exit_infos("Installed %d package(s)" % len(installed_pkgs))
|
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):
|
def remove_packages(self, pkgs):
|
||||||
# filter out pkgs that are already absent
|
# filter out pkgs that are already absent
|
||||||
|
@ -631,6 +689,7 @@ class Pacman(object):
|
||||||
"available_pkgs": {pkgname: version},
|
"available_pkgs": {pkgname: version},
|
||||||
"available_groups": {groupname: set(pkgnames)},
|
"available_groups": {groupname: set(pkgnames)},
|
||||||
"upgradable_pkgs": {pkgname: (current_version,latest_version)},
|
"upgradable_pkgs": {pkgname: (current_version,latest_version)},
|
||||||
|
"pkg_reasons": {pkgname: reason},
|
||||||
}
|
}
|
||||||
|
|
||||||
Fails the module if a package requested for install cannot be found
|
Fails the module if a package requested for install cannot be found
|
||||||
|
@ -723,12 +782,31 @@ class Pacman(object):
|
||||||
rc=rc,
|
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(
|
return dict(
|
||||||
installed_pkgs=installed_pkgs,
|
installed_pkgs=installed_pkgs,
|
||||||
installed_groups=installed_groups,
|
installed_groups=installed_groups,
|
||||||
available_pkgs=available_pkgs,
|
available_pkgs=available_pkgs,
|
||||||
available_groups=available_groups,
|
available_groups=available_groups,
|
||||||
upgradable_pkgs=upgradable_pkgs,
|
upgradable_pkgs=upgradable_pkgs,
|
||||||
|
pkg_reasons=pkg_reasons,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -749,6 +827,8 @@ def setup_module():
|
||||||
upgrade_extra_args=dict(type="str", default=""),
|
upgrade_extra_args=dict(type="str", default=""),
|
||||||
update_cache=dict(type="bool"),
|
update_cache=dict(type="bool"),
|
||||||
update_cache_extra_args=dict(type="str", default=""),
|
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"]],
|
required_one_of=[["name", "update_cache", "upgrade"]],
|
||||||
mutually_exclusive=[["name", "upgrade"]],
|
mutually_exclusive=[["name", "upgrade"]],
|
||||||
|
|
|
@ -12,3 +12,4 @@
|
||||||
- include: 'remove_nosave.yml'
|
- include: 'remove_nosave.yml'
|
||||||
- include: 'update_cache.yml'
|
- include: 'update_cache.yml'
|
||||||
- include: 'locally_installed_package.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": {
|
"upgradable_pkgs": {
|
||||||
"sqlite": VersionTuple(current="3.36.0-1", latest="3.37.0-1"),
|
"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 = {
|
empty_inventory = {
|
||||||
|
@ -108,6 +121,7 @@ empty_inventory = {
|
||||||
"installed_groups": {},
|
"installed_groups": {},
|
||||||
"available_groups": {},
|
"available_groups": {},
|
||||||
"upgradable_pkgs": {},
|
"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,
|
None,
|
||||||
),
|
),
|
||||||
|
@ -272,6 +307,8 @@ class TestPacman:
|
||||||
"",
|
"",
|
||||||
"warning: config file /etc/pacman.conf, line 34: directive 'TotalDownload' in section 'options' not recognized.",
|
"warning: config file /etc/pacman.conf, line 34: directive 'TotalDownload' in section 'options' not recognized.",
|
||||||
),
|
),
|
||||||
|
(0, "", ""),
|
||||||
|
(0, "", ""),
|
||||||
],
|
],
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
|
@ -288,6 +325,8 @@ class TestPacman:
|
||||||
"partial\npkg\\nlist",
|
"partial\npkg\\nlist",
|
||||||
"some warning",
|
"some warning",
|
||||||
),
|
),
|
||||||
|
(0, "", ""),
|
||||||
|
(0, "", ""),
|
||||||
],
|
],
|
||||||
AnsibleFailJson,
|
AnsibleFailJson,
|
||||||
),
|
),
|
||||||
|
@ -375,6 +414,8 @@ class TestPacman:
|
||||||
(["pacman", "--query", "--groups"], {'check_rc': True}, 0, '', ''),
|
(["pacman", "--query", "--groups"], {'check_rc': True}, 0, '', ''),
|
||||||
(["pacman", "--sync", "--groups", "--groups"], {'check_rc': True}, 0, '', ''),
|
(["pacman", "--sync", "--groups", "--groups"], {'check_rc': True}, 0, '', ''),
|
||||||
(["pacman", "--query", "--upgrades"], {'check_rc': False}, 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,
|
False,
|
||||||
),
|
),
|
||||||
|
@ -843,7 +884,7 @@ class TestPacman:
|
||||||
],
|
],
|
||||||
"state": "present",
|
"state": "present",
|
||||||
},
|
},
|
||||||
["sudo", "somepackage", "otherpkg"],
|
["otherpkg", "somepackage", "sudo"],
|
||||||
[
|
[
|
||||||
Package("sudo", "sudo"),
|
Package("sudo", "sudo"),
|
||||||
Package("grep", "grep"),
|
Package("grep", "grep"),
|
||||||
|
|
Loading…
Reference in a new issue