Mini Shell
"""
A module to wrap pacman calls, since Arch is the best
(https://wiki.archlinux.org/index.php/Arch_is_the_best)
.. important::
If you feel that Salt should be using this module to manage packages on a
minion, and it is using a different module (or gives an error similar to
*'pkg.install' is not available*), see :ref:`here
<module-provider-override>`.
"""
import copy
import fnmatch
import logging
import os.path
import salt.utils.args
import salt.utils.data
import salt.utils.functools
import salt.utils.itertools
import salt.utils.pkg
import salt.utils.systemd
from salt.exceptions import CommandExecutionError, MinionError
from salt.utils.versions import LooseVersion
log = logging.getLogger(__name__)
# Define the module's virtual name
__virtualname__ = "pkg"
def __virtual__():
"""
Set the virtual pkg module if the os is Arch
"""
if __grains__["os_family"] == "Arch":
return __virtualname__
return (False, "The pacman module could not be loaded: unsupported OS family.")
def _list_removed(old, new):
"""
List the packages which have been removed between the two package objects
"""
return [x for x in old if x not in new]
def latest_version(*names, **kwargs):
"""
Return the latest version of the named package available for upgrade or
installation. If more than one package name is specified, a dict of
name/version pairs is returned.
If the latest version of a given package is already installed, an empty
string will be returned for that package.
CLI Example:
.. code-block:: bash
salt '*' pkg.latest_version <package name>
salt '*' pkg.latest_version <package1> <package2> <package3> ...
"""
refresh = salt.utils.data.is_true(kwargs.pop("refresh", False))
if not names:
return ""
# Refresh before looking for the latest version available
if refresh:
refresh_db()
ret = {}
# Initialize the dict with empty strings
for name in names:
ret[name] = ""
cmd = ["pacman", "-Sp", "--needed", "--print-format", "%n %v"]
cmd.extend(names)
if "root" in kwargs:
cmd.extend(("-r", kwargs["root"]))
out = __salt__["cmd.run_stdout"](cmd, output_loglevel="trace", python_shell=False)
for line in salt.utils.itertools.split(out, "\n"):
try:
name, version_num = line.split()
# Only add to return dict if package is in the list of packages
# passed, otherwise dependencies will make their way into the
# return data.
if name in names:
ret[name] = version_num
except (ValueError, IndexError):
pass
# Return a string if only one package name passed
if len(names) == 1:
return ret[names[0]]
return ret
# available_version is being deprecated
available_version = salt.utils.functools.alias_function(
latest_version, "available_version"
)
def upgrade_available(name, **kwargs):
"""
Check whether or not an upgrade is available for a given package
CLI Example:
.. code-block:: bash
salt '*' pkg.upgrade_available <package name>
"""
return latest_version(name) != ""
def list_upgrades(refresh=False, root=None, **kwargs): # pylint: disable=W0613
"""
List all available package upgrades on this system
CLI Example:
.. code-block:: bash
salt '*' pkg.list_upgrades
"""
upgrades = {}
cmd = ["pacman", "-S", "-p", "-u", "--print-format", "%n %v"]
if root is not None:
cmd.extend(("-r", root))
if refresh:
cmd.append("-y")
call = __salt__["cmd.run_all"](cmd, python_shell=False, output_loglevel="trace")
if call["retcode"] != 0:
comment = ""
if "stderr" in call:
comment += call["stderr"]
if "stdout" in call:
comment += call["stdout"]
if comment:
comment = ": " + comment
raise CommandExecutionError("Error listing upgrades" + comment)
else:
out = call["stdout"]
for line in salt.utils.itertools.split(out, "\n"):
try:
pkgname, pkgver = line.split()
except ValueError:
continue
if pkgname.lower() == "downloading" and ".db" in pkgver.lower():
# Antergos (and possibly other Arch derivatives) add lines when pkg
# metadata is being downloaded. Because these lines, when split,
# contain two columns (i.e. 'downloading community.db...'), we will
# skip this line to keep it from being interpreted as an upgrade.
continue
upgrades[pkgname] = pkgver
return upgrades
def version(*names, **kwargs):
"""
Returns a string representing the package version or an empty string if not
installed. If more than one package name is specified, a dict of
name/version pairs is returned.
CLI Example:
.. code-block:: bash
salt '*' pkg.version <package name>
salt '*' pkg.version <package1> <package2> <package3> ...
"""
return __salt__["pkg_resource.version"](*names, **kwargs)
def _list_pkgs_from_context(versions_as_list):
"""
Use pkg list from __context__
"""
if versions_as_list:
return __context__["pkg.list_pkgs"]
else:
ret = copy.deepcopy(__context__["pkg.list_pkgs"])
__salt__["pkg_resource.stringify"](ret)
return ret
def list_pkgs(versions_as_list=False, **kwargs):
"""
List the packages currently installed as a dict::
{'<package_name>': '<version>'}
CLI Example:
.. code-block:: bash
salt '*' pkg.list_pkgs
"""
versions_as_list = salt.utils.data.is_true(versions_as_list)
# not yet implemented or not applicable
if any(
[salt.utils.data.is_true(kwargs.get(x)) for x in ("removed", "purge_desired")]
):
return {}
if "pkg.list_pkgs" in __context__ and kwargs.get("use_context", True):
return _list_pkgs_from_context(versions_as_list)
cmd = ["pacman", "-Q"]
if "root" in kwargs:
cmd.extend(("-r", kwargs["root"]))
ret = {}
out = __salt__["cmd.run"](cmd, output_loglevel="trace", python_shell=False)
for line in salt.utils.itertools.split(out, "\n"):
if not line:
continue
try:
name, version_num = line.split()[0:2]
except ValueError:
log.error(
"Problem parsing pacman -Q: Unexpected formatting in line: '%s'",
line,
)
else:
__salt__["pkg_resource.add_pkg"](ret, name, version_num)
__salt__["pkg_resource.sort_pkglist"](ret)
__context__["pkg.list_pkgs"] = copy.deepcopy(ret)
if not versions_as_list:
__salt__["pkg_resource.stringify"](ret)
return ret
def group_list():
"""
.. versionadded:: 2016.11.0
Lists all groups known by pacman on this system
CLI Example:
.. code-block:: bash
salt '*' pkg.group_list
"""
ret = {"installed": [], "partially_installed": [], "available": []}
# find out what's available
cmd = ["pacman", "-Sgg"]
out = __salt__["cmd.run"](cmd, output_loglevel="trace", python_shell=False)
available = {}
for line in salt.utils.itertools.split(out, "\n"):
if not line:
continue
try:
group, pkg = line.split()[0:2]
except ValueError:
log.error(
"Problem parsing pacman -Sgg: Unexpected formatting in line: '%s'",
line,
)
else:
available.setdefault(group, []).append(pkg)
# now get what's installed
cmd = ["pacman", "-Qg"]
out = __salt__["cmd.run"](cmd, output_loglevel="trace", python_shell=False)
installed = {}
for line in salt.utils.itertools.split(out, "\n"):
if not line:
continue
try:
group, pkg = line.split()[0:2]
except ValueError:
log.error(
"Problem parsing pacman -Qg: Unexpected formatting in line: '%s'",
line,
)
else:
installed.setdefault(group, []).append(pkg)
# move installed and partially-installed items from available to appropriate other places
for group in installed:
if group not in available:
log.error(
"Pacman reports group %s installed, but it is not in the "
"available list (%s)!",
group,
available,
)
continue
if len(installed[group]) == len(available[group]):
ret["installed"].append(group)
else:
ret["partially_installed"].append(group)
available.pop(group)
ret["installed"].sort()
ret["partially_installed"].sort()
# Now installed and partially installed are set, whatever is left is the available list.
# In Python 3, .keys() returns an iterable view instead of a list. sort() cannot be
# called on views. Use sorted() instead. Plus it's just as efficient as sort().
ret["available"] = sorted(available.keys())
return ret
def group_info(name):
"""
.. versionadded:: 2016.11.0
Lists all packages in the specified group
CLI Example:
.. code-block:: bash
salt '*' pkg.group_info 'xorg'
"""
pkgtypes = ("mandatory", "optional", "default", "conditional")
ret = {}
for pkgtype in pkgtypes:
ret[pkgtype] = set()
cmd = ["pacman", "-Sgg", name]
out = __salt__["cmd.run"](cmd, output_loglevel="trace", python_shell=False)
for line in salt.utils.itertools.split(out, "\n"):
if not line:
continue
try:
pkg = line.split()[1]
except ValueError:
log.error(
"Problem parsing pacman -Sgg: Unexpected formatting in line: '%s'",
line,
)
else:
ret["default"].add(pkg)
for pkgtype in pkgtypes:
ret[pkgtype] = sorted(ret[pkgtype])
return ret
def group_diff(name):
"""
.. versionadded:: 2016.11.0
Lists which of a group's packages are installed and which are not
installed
Compatible with yumpkg.group_diff for easy support of state.pkg.group_installed
CLI Example:
.. code-block:: bash
salt '*' pkg.group_diff 'xorg'
"""
# Use a compatible structure with yum, so we can leverage the existing state.group_installed
# In pacmanworld, everything is the default, but nothing is mandatory
pkgtypes = ("mandatory", "optional", "default", "conditional")
ret = {}
for pkgtype in pkgtypes:
ret[pkgtype] = {"installed": [], "not installed": []}
# use indirect references to simplify unit testing
pkgs = __salt__["pkg.list_pkgs"]()
group_pkgs = __salt__["pkg.group_info"](name)
for pkgtype in pkgtypes:
for member in group_pkgs.get(pkgtype, []):
if member in pkgs:
ret[pkgtype]["installed"].append(member)
else:
ret[pkgtype]["not installed"].append(member)
return ret
def refresh_db(root=None, **kwargs):
"""
Just run a ``pacman -Sy``, return a dict::
{'<database name>': Bool}
CLI Example:
.. code-block:: bash
salt '*' pkg.refresh_db
"""
# Remove rtag file to keep multiple refreshes from happening in pkg states
salt.utils.pkg.clear_rtag(__opts__)
cmd = ["pacman", "-Sy"]
if root is not None:
cmd.extend(("-r", root))
ret = {}
call = __salt__["cmd.run_all"](
cmd, output_loglevel="trace", env={"LANG": "C"}, python_shell=False
)
if call["retcode"] != 0:
comment = ""
if "stderr" in call:
comment += ": " + call["stderr"]
raise CommandExecutionError("Error refreshing package database" + comment)
else:
out = call["stdout"]
for line in salt.utils.itertools.split(out, "\n"):
if line.strip().startswith("::"):
continue
if not line:
continue
key = line.strip().split()[0]
if "is up to date" in line:
ret[key] = False
elif "downloading" in line:
key = line.strip().split()[1].split(".")[0]
ret[key] = True
return ret
def install(
name=None, refresh=False, sysupgrade=None, pkgs=None, sources=None, **kwargs
):
"""
.. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
On minions running systemd>=205, `systemd-run(1)`_ is now used to
isolate commands which modify installed packages from the
``salt-minion`` daemon's control group. This is done to keep systemd
from killing any pacman commands spawned by Salt when the
``salt-minion`` service is restarted. (see ``KillMode`` in the
`systemd.kill(5)`_ manpage for more information). If desired, usage of
`systemd-run(1)`_ can be suppressed by setting a :mod:`config option
<salt.modules.config.get>` called ``systemd.scope``, with a value of
``False`` (no quotes).
.. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html
.. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html
Install (``pacman -S``) the specified packag(s). Add ``refresh=True`` to
install with ``-y``, add ``sysupgrade=True`` to install with ``-u``.
name
The name of the package to be installed. Note that this parameter is
ignored if either ``pkgs`` or ``sources`` is passed. Additionally,
please note that this option can only be used to install packages from
a software repository. To install a package file manually, use the
``sources`` option.
CLI Example:
.. code-block:: bash
salt '*' pkg.install <package name>
refresh
Whether or not to refresh the package database before installing.
sysupgrade
Whether or not to upgrade the system packages before installing.
If refresh is set to ``True`` but sysupgrade is not specified, ``-u`` will be
applied
Multiple Package Installation Options:
pkgs
A list of packages to install from a software repository. Must be
passed as a python list. A specific version number can be specified
by using a single-element dict representing the package and its
version. As with the ``version`` parameter above, comparison operators
can be used to target a specific version of a package.
CLI Examples:
.. code-block:: bash
salt '*' pkg.install pkgs='["foo", "bar"]'
salt '*' pkg.install pkgs='["foo", {"bar": "1.2.3-4"}]'
salt '*' pkg.install pkgs='["foo", {"bar": "<1.2.3-4"}]'
sources
A list of packages to install. Must be passed as a list of dicts,
with the keys being package names, and the values being the source URI
or local path to the package.
CLI Example:
.. code-block:: bash
salt '*' pkg.install \
sources='[{"foo": "salt://foo.pkg.tar.xz"}, \
{"bar": "salt://bar.pkg.tar.xz"}]'
Returns a dict containing the new package names and versions::
{'<package>': {'old': '<old-version>',
'new': '<new-version>'}}
"""
try:
pkg_params, pkg_type = __salt__["pkg_resource.parse_targets"](
name, pkgs, sources, **kwargs
)
except MinionError as exc:
raise CommandExecutionError(exc)
if not pkg_params:
return {}
if "root" in kwargs:
pkg_params["-r"] = kwargs["root"]
cmd = []
if salt.utils.systemd.has_scope(__context__) and __salt__["config.get"](
"systemd.scope", True
):
cmd.extend(["systemd-run", "--scope"])
cmd.append("pacman")
errors = []
targets = []
if pkg_type == "file":
cmd.extend(["-U", "--noprogressbar", "--noconfirm"])
cmd.extend(pkg_params)
elif pkg_type == "repository":
cmd.append("-S")
if refresh is True:
cmd.append("-y")
if sysupgrade is True:
cmd.append("-u")
cmd.extend(["--noprogressbar", "--noconfirm", "--needed"])
wildcards = []
for param, version_num in pkg_params.items():
if version_num is None:
targets.append(param)
else:
prefix, verstr = salt.utils.pkg.split_comparison(version_num)
if not prefix:
prefix = "="
if "*" in verstr:
if prefix == "=":
wildcards.append((param, verstr))
else:
errors.append(f"Invalid wildcard for {param}{prefix}{verstr}")
continue
targets.append(f"{param}{prefix}{verstr}")
if wildcards:
# Resolve wildcard matches
_available = list_repo_pkgs(*[x[0] for x in wildcards], refresh=refresh)
for pkgname, verstr in wildcards:
candidates = _available.get(pkgname, [])
match = salt.utils.itertools.fnmatch_multiple(candidates, verstr)
if match is not None:
targets.append("=".join((pkgname, match)))
else:
errors.append(
"No version matching '{}' found for package '{}' "
"(available: {})".format(
verstr,
pkgname,
", ".join(candidates) if candidates else "none",
)
)
if refresh:
try:
# Prevent a second refresh when we run the install command
cmd.remove("-y")
except ValueError:
# Shouldn't happen since we only add -y when refresh is True,
# but just in case that code above is inadvertently changed,
# don't let this result in a traceback.
pass
if not errors:
cmd.extend(targets)
old = list_pkgs()
out = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
if out["retcode"] != 0 and out["stderr"]:
errors = [out["stderr"]]
else:
errors = []
__context__.pop("pkg.list_pkgs", None)
new = list_pkgs()
ret = salt.utils.data.compare_dicts(old, new)
if errors:
try:
changes = ret
except UnboundLocalError:
# We ran into errors before we attempted to install anything, so
# there are no changes.
changes = {}
raise CommandExecutionError(
"Problem encountered installing package(s)",
info={"errors": errors, "changes": changes},
)
return ret
def upgrade(refresh=False, root=None, **kwargs):
"""
.. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
On minions running systemd>=205, `systemd-run(1)`_ is now used to
isolate commands which modify installed packages from the
``salt-minion`` daemon's control group. This is done to keep systemd
from killing any pacman commands spawned by Salt when the
``salt-minion`` service is restarted. (see ``KillMode`` in the
`systemd.kill(5)`_ manpage for more information). If desired, usage of
`systemd-run(1)`_ can be suppressed by setting a :mod:`config option
<salt.modules.config.get>` called ``systemd.scope``, with a value of
``False`` (no quotes).
.. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html
.. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html
Run a full system upgrade, a pacman -Syu
refresh
Whether or not to refresh the package database before installing.
Returns a dictionary containing the changes:
.. code-block:: python
{'<package>': {'old': '<old-version>',
'new': '<new-version>'}}
CLI Example:
.. code-block:: bash
salt '*' pkg.upgrade
"""
ret = {"changes": {}, "result": True, "comment": ""}
old = list_pkgs()
cmd = []
if salt.utils.systemd.has_scope(__context__) and __salt__["config.get"](
"systemd.scope", True
):
cmd.extend(["systemd-run", "--scope"])
cmd.extend(["pacman", "-Su", "--noprogressbar", "--noconfirm"])
if salt.utils.data.is_true(refresh):
cmd.append("-y")
if root is not None:
cmd.extend(("-r", root))
result = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
__context__.pop("pkg.list_pkgs", None)
new = list_pkgs()
ret = salt.utils.data.compare_dicts(old, new)
if result["retcode"] != 0:
raise CommandExecutionError(
"Problem encountered upgrading packages",
info={"changes": ret, "result": result},
)
return ret
def _uninstall(action="remove", name=None, pkgs=None, **kwargs):
"""
remove and purge do identical things but with different pacman commands,
this function performs the common logic.
"""
try:
pkg_params = __salt__["pkg_resource.parse_targets"](name, pkgs)[0]
except MinionError as exc:
raise CommandExecutionError(exc)
old = list_pkgs()
targets = [x for x in pkg_params if x in old]
if not targets:
return {}
remove_arg = "-Rsc" if action == "purge" else "-R"
cmd = []
if salt.utils.systemd.has_scope(__context__) and __salt__["config.get"](
"systemd.scope", True
):
cmd.extend(["systemd-run", "--scope"])
cmd.extend(["pacman", remove_arg, "--noprogressbar", "--noconfirm"])
cmd.extend(targets)
if "root" in kwargs:
cmd.extend(("-r", kwargs["root"]))
out = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
if out["retcode"] != 0 and out["stderr"]:
errors = [out["stderr"]]
else:
errors = []
__context__.pop("pkg.list_pkgs", None)
new = list_pkgs()
ret = salt.utils.data.compare_dicts(old, new)
if errors:
raise CommandExecutionError(
"Problem encountered removing package(s)",
info={"errors": errors, "changes": ret},
)
return ret
def remove(name=None, pkgs=None, **kwargs):
"""
.. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
On minions running systemd>=205, `systemd-run(1)`_ is now used to
isolate commands which modify installed packages from the
``salt-minion`` daemon's control group. This is done to keep systemd
from killing any pacman commands spawned by Salt when the
``salt-minion`` service is restarted. (see ``KillMode`` in the
`systemd.kill(5)`_ manpage for more information). If desired, usage of
`systemd-run(1)`_ can be suppressed by setting a :mod:`config option
<salt.modules.config.get>` called ``systemd.scope``, with a value of
``False`` (no quotes).
.. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html
.. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html
Remove packages with ``pacman -R``.
name
The name of the package to be deleted.
Multiple Package Options:
pkgs
A list of packages to delete. Must be passed as a python list. The
``name`` parameter will be ignored if this option is passed.
.. versionadded:: 0.16.0
Returns a dict containing the changes.
CLI Example:
.. code-block:: bash
salt '*' pkg.remove <package name>
salt '*' pkg.remove <package1>,<package2>,<package3>
salt '*' pkg.remove pkgs='["foo", "bar"]'
"""
return _uninstall(action="remove", name=name, pkgs=pkgs)
def purge(name=None, pkgs=None, **kwargs):
"""
.. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
On minions running systemd>=205, `systemd-run(1)`_ is now used to
isolate commands which modify installed packages from the
``salt-minion`` daemon's control group. This is done to keep systemd
from killing any pacman commands spawned by Salt when the
``salt-minion`` service is restarted. (see ``KillMode`` in the
`systemd.kill(5)`_ manpage for more information). If desired, usage of
`systemd-run(1)`_ can be suppressed by setting a :mod:`config option
<salt.modules.config.get>` called ``systemd.scope``, with a value of
``False`` (no quotes).
.. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html
.. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html
Recursively remove a package and all dependencies which were installed
with it, this will call a ``pacman -Rsc``
name
The name of the package to be deleted.
Multiple Package Options:
pkgs
A list of packages to delete. Must be passed as a python list. The
``name`` parameter will be ignored if this option is passed.
.. versionadded:: 0.16.0
Returns a dict containing the changes.
CLI Example:
.. code-block:: bash
salt '*' pkg.purge <package name>
salt '*' pkg.purge <package1>,<package2>,<package3>
salt '*' pkg.purge pkgs='["foo", "bar"]'
"""
return _uninstall(action="purge", name=name, pkgs=pkgs)
def file_list(*packages, **kwargs):
"""
List the files that belong to a package. Not specifying any packages will
return a list of _every_ file on the system's package database (not
generally recommended).
CLI Examples:
.. code-block:: bash
salt '*' pkg.file_list httpd
salt '*' pkg.file_list httpd postfix
salt '*' pkg.file_list
"""
errors = []
ret = []
cmd = ["pacman", "-Ql"]
if packages and os.path.exists(packages[0]):
packages = list(packages)
cmd.extend(("-r", packages.pop(0)))
cmd.extend(packages)
out = __salt__["cmd.run"](cmd, output_loglevel="trace", python_shell=False)
for line in salt.utils.itertools.split(out, "\n"):
if line.startswith("error"):
errors.append(line)
else:
comps = line.split()
ret.append(" ".join(comps[1:]))
return {"errors": errors, "files": ret}
def file_dict(*packages, **kwargs):
"""
List the files that belong to a package, grouped by package. Not
specifying any packages will return a list of _every_ file on the system's
package database (not generally recommended).
CLI Examples:
.. code-block:: bash
salt '*' pkg.file_list httpd
salt '*' pkg.file_list httpd postfix
salt '*' pkg.file_list
"""
errors = []
ret = {}
cmd = ["pacman", "-Ql"]
if packages and os.path.exists(packages[0]):
packages = list(packages)
cmd.extend(("-r", packages.pop(0)))
cmd.extend(packages)
out = __salt__["cmd.run"](cmd, output_loglevel="trace", python_shell=False)
for line in salt.utils.itertools.split(out, "\n"):
if line.startswith("error"):
errors.append(line)
else:
comps = line.split()
if not comps[0] in ret:
ret[comps[0]] = []
ret[comps[0]].append(" ".join(comps[1:]))
return {"errors": errors, "packages": ret}
def owner(*paths, **kwargs):
"""
.. versionadded:: 2014.7.0
Return the name of the package that owns the file. Multiple file paths can
be passed. Like :mod:`pkg.version <salt.modules.yumpkg.version>`, if a
single path is passed, a string will be returned, and if multiple paths are
passed, a dictionary of file/package name pairs will be returned.
If the file is not owned by a package, or is not present on the minion,
then an empty string will be returned for that path.
CLI Example:
.. code-block:: bash
salt '*' pkg.owner /usr/bin/apachectl
salt '*' pkg.owner /usr/bin/apachectl /usr/bin/zsh
"""
if not paths:
return ""
ret = {}
cmd_prefix = ["pacman", "-Qqo"]
for path in paths:
ret[path] = __salt__["cmd.run_stdout"](cmd_prefix + [path], python_shell=False)
if len(ret) == 1:
return next(iter(ret.values()))
return ret
def list_repo_pkgs(*args, **kwargs):
"""
Returns all available packages. Optionally, package names (and name globs)
can be passed and the results will be filtered to packages matching those
names.
This function can be helpful in discovering the version or repo to specify
in a :mod:`pkg.installed <salt.states.pkg.installed>` state.
The return data will be a dictionary mapping package names to a list of
version numbers, ordered from newest to oldest. If ``byrepo`` is set to
``True``, then the return dictionary will contain repository names at the
top level, and each repository will map packages to lists of version
numbers. For example:
.. code-block:: python
# With byrepo=False (default)
{
'bash': ['4.4.005-2'],
'nginx': ['1.10.2-2']
}
# With byrepo=True
{
'core': {
'bash': ['4.4.005-2']
},
'extra': {
'nginx': ['1.10.2-2']
}
}
fromrepo : None
Only include results from the specified repo(s). Multiple repos can be
specified, comma-separated.
byrepo : False
When ``True``, the return data for each package will be organized by
repository.
refresh : False
When ``True``, the package database will be refreshed (i.e. ``pacman
-Sy``) before checking for available versions.
CLI Examples:
.. code-block:: bash
salt '*' pkg.list_repo_pkgs
salt '*' pkg.list_repo_pkgs foo bar baz
salt '*' pkg.list_repo_pkgs 'samba4*' fromrepo=base,updates
salt '*' pkg.list_repo_pkgs 'python2-*' byrepo=True
"""
kwargs = salt.utils.args.clean_kwargs(**kwargs)
fromrepo = kwargs.pop("fromrepo", "") or ""
byrepo = kwargs.pop("byrepo", False)
refresh = kwargs.pop("refresh", False)
if kwargs:
salt.utils.args.invalid_kwargs(kwargs)
if fromrepo:
try:
repos = [x.strip() for x in fromrepo.split(",")]
except AttributeError:
repos = [x.strip() for x in str(fromrepo).split(",")]
else:
repos = []
if refresh:
refresh_db()
out = __salt__["cmd.run_all"](
["pacman", "-Sl"],
output_loglevel="trace",
ignore_retcode=True,
python_shell=False,
)
ret = {}
for line in salt.utils.itertools.split(out["stdout"], "\n"):
try:
repo, pkg_name, pkg_ver = line.strip().split()[:3]
except ValueError:
continue
if repos and repo not in repos:
continue
if args:
for arg in args:
if fnmatch.fnmatch(pkg_name, arg):
skip_pkg = False
break
else:
# Package doesn't match any of the passed args, skip it
continue
ret.setdefault(repo, {}).setdefault(pkg_name, []).append(pkg_ver)
if byrepo:
for reponame in ret:
# Sort versions newest to oldest
for pkgname in ret[reponame]:
sorted_versions = sorted(
(LooseVersion(x) for x in ret[reponame][pkgname]), reverse=True
)
ret[reponame][pkgname] = [x.vstring for x in sorted_versions]
return ret
else:
byrepo_ret = {}
for reponame in ret:
for pkgname in ret[reponame]:
byrepo_ret.setdefault(pkgname, []).extend(ret[reponame][pkgname])
for pkgname in byrepo_ret:
sorted_versions = sorted(
(LooseVersion(x) for x in byrepo_ret[pkgname]), reverse=True
)
byrepo_ret[pkgname] = [x.vstring for x in sorted_versions]
return byrepo_ret
Zerion Mini Shell 1.0