"Fossies" - the Fresh Open Source Software Archive

Member "salt-3002.2/salt/modules/aptpkg.py" (18 Nov 2020, 99043 Bytes) of package /linux/misc/salt-3002.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "aptpkg.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3002.1_vs_3002.2.

    1 """
    2 Support for APT (Advanced Packaging Tool)
    3 
    4 .. important::
    5     If you feel that Salt should be using this module to manage packages on a
    6     minion, and it is using a different module (or gives an error similar to
    7     *'pkg.install' is not available*), see :ref:`here
    8     <module-provider-override>`.
    9 
   10     For repository management, the ``python-apt`` package must be installed.
   11 """
   12 
   13 
   14 import copy
   15 import datetime
   16 import fnmatch
   17 import logging
   18 import os
   19 import re
   20 import time
   21 from urllib.error import HTTPError
   22 from urllib.request import Request as _Request
   23 from urllib.request import urlopen as _urlopen
   24 
   25 import salt.config
   26 import salt.syspaths
   27 import salt.utils.args
   28 import salt.utils.data
   29 import salt.utils.environment
   30 import salt.utils.files
   31 import salt.utils.functools
   32 import salt.utils.itertools
   33 import salt.utils.json
   34 import salt.utils.path
   35 import salt.utils.pkg
   36 import salt.utils.pkg.deb
   37 import salt.utils.stringutils
   38 import salt.utils.systemd
   39 import salt.utils.versions
   40 import salt.utils.yaml
   41 from salt.exceptions import CommandExecutionError, MinionError, SaltInvocationError
   42 from salt.modules.cmdmod import _parse_env
   43 
   44 log = logging.getLogger(__name__)
   45 
   46 # pylint: disable=import-error
   47 try:
   48     import apt.cache
   49     import apt.debfile
   50     from aptsources import sourceslist
   51 
   52     HAS_APT = True
   53 except ImportError:
   54     HAS_APT = False
   55 
   56 try:
   57     import apt_pkg
   58 
   59     HAS_APTPKG = True
   60 except ImportError:
   61     HAS_APTPKG = False
   62 
   63 try:
   64     import softwareproperties.ppa
   65 
   66     HAS_SOFTWAREPROPERTIES = True
   67 except ImportError:
   68     HAS_SOFTWAREPROPERTIES = False
   69 # pylint: enable=import-error
   70 
   71 APT_LISTS_PATH = "/var/lib/apt/lists"
   72 PKG_ARCH_SEPARATOR = ":"
   73 
   74 # Source format for urllib fallback on PPA handling
   75 LP_SRC_FORMAT = "deb http://ppa.launchpad.net/{0}/{1}/ubuntu {2} main"
   76 LP_PVT_SRC_FORMAT = (
   77     "deb https://{0}private-ppa.launchpad.net/{1}/{2}/ubuntu" " {3} main"
   78 )
   79 
   80 _MODIFY_OK = frozenset(["uri", "comps", "architectures", "disabled", "file", "dist"])
   81 DPKG_ENV_VARS = {
   82     "APT_LISTBUGS_FRONTEND": "none",
   83     "APT_LISTCHANGES_FRONTEND": "none",
   84     "DEBIAN_FRONTEND": "noninteractive",
   85     "UCF_FORCE_CONFFOLD": "1",
   86 }
   87 
   88 # Define the module's virtual name
   89 __virtualname__ = "pkg"
   90 
   91 
   92 def __virtual__():
   93     """
   94     Confirm this module is on a Debian-based system
   95     """
   96     # If your minion is running an OS which is Debian-based but does not have
   97     # an "os_family" grain of Debian, then the proper fix is NOT to check for
   98     # the minion's "os_family" grain here in the __virtual__. The correct fix
   99     # is to add the value from the minion's "os" grain to the _OS_FAMILY_MAP
  100     # dict in salt/grains/core.py, so that we assign the correct "os_family"
  101     # grain to the minion.
  102     if __grains__.get("os_family") == "Debian":
  103         return __virtualname__
  104     return False, "The pkg module could not be loaded: unsupported OS family"
  105 
  106 
  107 def __init__(opts):
  108     """
  109     For Debian and derivative systems, set up
  110     a few env variables to keep apt happy and
  111     non-interactive.
  112     """
  113     if __virtual__() == __virtualname__:
  114         # Export these puppies so they persist
  115         os.environ.update(DPKG_ENV_VARS)
  116 
  117 
  118 def _get_ppa_info_from_launchpad(owner_name, ppa_name):
  119     """
  120     Idea from softwareproperties.ppa.
  121     Uses urllib2 which sacrifices server cert verification.
  122 
  123     This is used as fall-back code or for secure PPAs
  124 
  125     :param owner_name:
  126     :param ppa_name:
  127     :return:
  128     """
  129 
  130     lp_url = "https://launchpad.net/api/1.0/~{}/+archive/{}".format(
  131         owner_name, ppa_name
  132     )
  133     request = _Request(lp_url, headers={"Accept": "application/json"})
  134     lp_page = _urlopen(request)
  135     return salt.utils.json.load(lp_page)
  136 
  137 
  138 def _reconstruct_ppa_name(owner_name, ppa_name):
  139     """
  140     Stringify PPA name from args.
  141     """
  142     return "ppa:{}/{}".format(owner_name, ppa_name)
  143 
  144 
  145 def _check_apt():
  146     """
  147     Abort if python-apt is not installed
  148     """
  149     if not HAS_APT:
  150         raise CommandExecutionError("Error: 'python-apt' package not installed")
  151 
  152 
  153 def _call_apt(args, scope=True, **kwargs):
  154     """
  155     Call apt* utilities.
  156     """
  157     cmd = []
  158     if (
  159         scope
  160         and salt.utils.systemd.has_scope(__context__)
  161         and __salt__["config.get"]("systemd.scope", True)
  162     ):
  163         cmd.extend(["systemd-run", "--scope", "--description", '"{}"'.format(__name__)])
  164     cmd.extend(args)
  165 
  166     params = {
  167         "output_loglevel": "trace",
  168         "python_shell": False,
  169         "env": salt.utils.environment.get_module_environment(globals()),
  170     }
  171     params.update(kwargs)
  172 
  173     cmd_ret = __salt__["cmd.run_all"](cmd, **params)
  174     count = 0
  175     while "Could not get lock" in cmd_ret.get("stderr", "") and count < 10:
  176         count += 1
  177         log.warning("Waiting for dpkg lock release: retrying... %s/100", count)
  178         time.sleep(2 ** count)
  179         cmd_ret = __salt__["cmd.run_all"](cmd, **params)
  180     return cmd_ret
  181 
  182 
  183 def _warn_software_properties(repo):
  184     """
  185     Warn of missing python-software-properties package.
  186     """
  187     log.warning(
  188         "The 'python-software-properties' package is not installed. "
  189         "For more accurate support of PPA repositories, you should "
  190         "install this package."
  191     )
  192     log.warning("Best guess at ppa format: %s", repo)
  193 
  194 
  195 def normalize_name(name):
  196     """
  197     Strips the architecture from the specified package name, if necessary.
  198 
  199     CLI Example:
  200 
  201     .. code-block:: bash
  202 
  203         salt '*' pkg.normalize_name zsh:amd64
  204     """
  205     try:
  206         pkgname, pkgarch = name.rsplit(PKG_ARCH_SEPARATOR, 1)
  207     except ValueError:
  208         pkgname = name
  209         pkgarch = __grains__["osarch"]
  210 
  211     return pkgname if pkgarch in (__grains__["osarch"], "any") else name
  212 
  213 
  214 def parse_arch(name):
  215     """
  216     Parse name and architecture from the specified package name.
  217 
  218     CLI Example:
  219 
  220     .. code-block:: bash
  221 
  222         salt '*' pkg.parse_arch zsh:amd64
  223     """
  224     try:
  225         _name, _arch = name.rsplit(PKG_ARCH_SEPARATOR, 1)
  226     except ValueError:
  227         _name, _arch = name, None
  228     return {"name": _name, "arch": _arch}
  229 
  230 
  231 def latest_version(*names, **kwargs):
  232     """
  233     Return the latest version of the named package available for upgrade or
  234     installation. If more than one package name is specified, a dict of
  235     name/version pairs is returned.
  236 
  237     If the latest version of a given package is already installed, an empty
  238     string will be returned for that package.
  239 
  240     A specific repo can be requested using the ``fromrepo`` keyword argument.
  241 
  242     cache_valid_time
  243 
  244         .. versionadded:: 2016.11.0
  245 
  246         Skip refreshing the package database if refresh has already occurred within
  247         <value> seconds
  248 
  249     CLI Example:
  250 
  251     .. code-block:: bash
  252 
  253         salt '*' pkg.latest_version <package name>
  254         salt '*' pkg.latest_version <package name> fromrepo=unstable
  255         salt '*' pkg.latest_version <package1> <package2> <package3> ...
  256     """
  257     refresh = salt.utils.data.is_true(kwargs.pop("refresh", True))
  258     show_installed = salt.utils.data.is_true(kwargs.pop("show_installed", False))
  259     if "repo" in kwargs:
  260         raise SaltInvocationError(
  261             "The 'repo' argument is invalid, use 'fromrepo' instead"
  262         )
  263     fromrepo = kwargs.pop("fromrepo", None)
  264     cache_valid_time = kwargs.pop("cache_valid_time", 0)
  265 
  266     if not names:
  267         return ""
  268     ret = {}
  269     # Initialize the dict with empty strings
  270     for name in names:
  271         ret[name] = ""
  272     pkgs = list_pkgs(versions_as_list=True)
  273     repo = ["-o", "APT::Default-Release={}".format(fromrepo)] if fromrepo else None
  274 
  275     # Refresh before looking for the latest version available
  276     if refresh:
  277         refresh_db(cache_valid_time)
  278 
  279     for name in names:
  280         cmd = ["apt-cache", "-q", "policy", name]
  281         if repo is not None:
  282             cmd.extend(repo)
  283         out = _call_apt(cmd, scope=False)
  284 
  285         candidate = ""
  286         for line in salt.utils.itertools.split(out["stdout"], "\n"):
  287             if "Candidate" in line:
  288                 comps = line.split()
  289                 if len(comps) >= 2:
  290                     candidate = comps[-1]
  291                     if candidate.lower() == "(none)":
  292                         candidate = ""
  293                 break
  294 
  295         installed = pkgs.get(name, [])
  296         if not installed:
  297             ret[name] = candidate
  298         elif installed and show_installed:
  299             ret[name] = candidate
  300         elif candidate:
  301             # If there are no installed versions that are greater than or equal
  302             # to the install candidate, then the candidate is an upgrade, so
  303             # add it to the return dict
  304             if not any(
  305                 salt.utils.versions.compare(
  306                     ver1=x, oper=">=", ver2=candidate, cmp_func=version_cmp
  307                 )
  308                 for x in installed
  309             ):
  310                 ret[name] = candidate
  311 
  312     # Return a string if only one package name passed
  313     if len(names) == 1:
  314         return ret[names[0]]
  315     return ret
  316 
  317 
  318 # available_version is being deprecated
  319 available_version = salt.utils.functools.alias_function(
  320     latest_version, "available_version"
  321 )
  322 
  323 
  324 def version(*names, **kwargs):
  325     """
  326     Returns a string representing the package version or an empty string if not
  327     installed. If more than one package name is specified, a dict of
  328     name/version pairs is returned.
  329 
  330     CLI Example:
  331 
  332     .. code-block:: bash
  333 
  334         salt '*' pkg.version <package name>
  335         salt '*' pkg.version <package1> <package2> <package3> ...
  336     """
  337     return __salt__["pkg_resource.version"](*names, **kwargs)
  338 
  339 
  340 def refresh_db(cache_valid_time=0, failhard=False, **kwargs):
  341     """
  342     Updates the APT database to latest packages based upon repositories
  343 
  344     Returns a dict, with the keys being package databases and the values being
  345     the result of the update attempt. Values can be one of the following:
  346 
  347     - ``True``: Database updated successfully
  348     - ``False``: Problem updating database
  349     - ``None``: Database already up-to-date
  350 
  351     cache_valid_time
  352 
  353         .. versionadded:: 2016.11.0
  354 
  355         Skip refreshing the package database if refresh has already occurred within
  356         <value> seconds
  357 
  358     failhard
  359 
  360         If False, return results of Err lines as ``False`` for the package database that
  361         encountered the error.
  362         If True, raise an error with a list of the package databases that encountered
  363         errors.
  364 
  365     CLI Example:
  366 
  367     .. code-block:: bash
  368 
  369         salt '*' pkg.refresh_db
  370     """
  371     # Remove rtag file to keep multiple refreshes from happening in pkg states
  372     salt.utils.pkg.clear_rtag(__opts__)
  373     failhard = salt.utils.data.is_true(failhard)
  374     ret = {}
  375     error_repos = list()
  376 
  377     if cache_valid_time:
  378         try:
  379             latest_update = os.stat(APT_LISTS_PATH).st_mtime
  380             now = time.time()
  381             log.debug(
  382                 "now: %s, last update time: %s, expire after: %s seconds",
  383                 now,
  384                 latest_update,
  385                 cache_valid_time,
  386             )
  387             if latest_update + cache_valid_time > now:
  388                 return ret
  389         except TypeError as exp:
  390             log.warning(
  391                 "expected integer for cache_valid_time parameter, failed with: %s", exp
  392             )
  393         except OSError as exp:
  394             log.warning("could not stat cache directory due to: %s", exp)
  395 
  396     call = _call_apt(["apt-get", "-q", "update"], scope=False)
  397     if call["retcode"] != 0:
  398         comment = ""
  399         if "stderr" in call:
  400             comment += call["stderr"]
  401 
  402         raise CommandExecutionError(comment)
  403     else:
  404         out = call["stdout"]
  405 
  406     for line in out.splitlines():
  407         cols = line.split()
  408         if not cols:
  409             continue
  410         ident = " ".join(cols[1:])
  411         if "Get" in cols[0]:
  412             # Strip filesize from end of line
  413             ident = re.sub(r" \[.+B\]$", "", ident)
  414             ret[ident] = True
  415         elif "Ign" in cols[0]:
  416             ret[ident] = False
  417         elif "Hit" in cols[0]:
  418             ret[ident] = None
  419         elif "Err" in cols[0]:
  420             ret[ident] = False
  421             error_repos.append(ident)
  422 
  423     if failhard and error_repos:
  424         raise CommandExecutionError(
  425             "Error getting repos: {}".format(", ".join(error_repos))
  426         )
  427 
  428     return ret
  429 
  430 
  431 def install(
  432     name=None,
  433     refresh=False,
  434     fromrepo=None,
  435     skip_verify=False,
  436     debconf=None,
  437     pkgs=None,
  438     sources=None,
  439     reinstall=False,
  440     downloadonly=False,
  441     ignore_epoch=False,
  442     **kwargs
  443 ):
  444     """
  445     .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
  446         On minions running systemd>=205, `systemd-run(1)`_ is now used to
  447         isolate commands which modify installed packages from the
  448         ``salt-minion`` daemon's control group. This is done to keep systemd
  449         from killing any apt-get/dpkg commands spawned by Salt when the
  450         ``salt-minion`` service is restarted. (see ``KillMode`` in the
  451         `systemd.kill(5)`_ manpage for more information). If desired, usage of
  452         `systemd-run(1)`_ can be suppressed by setting a :mod:`config option
  453         <salt.modules.config.get>` called ``systemd.scope``, with a value of
  454         ``False`` (no quotes).
  455 
  456     .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html
  457     .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html
  458 
  459     Install the passed package, add refresh=True to update the dpkg database.
  460 
  461     name
  462         The name of the package to be installed. Note that this parameter is
  463         ignored if either "pkgs" or "sources" is passed. Additionally, please
  464         note that this option can only be used to install packages from a
  465         software repository. To install a package file manually, use the
  466         "sources" option.
  467 
  468         32-bit packages can be installed on 64-bit systems by appending the
  469         architecture designation (``:i386``, etc.) to the end of the package
  470         name.
  471 
  472         CLI Example:
  473 
  474         .. code-block:: bash
  475 
  476             salt '*' pkg.install <package name>
  477 
  478     refresh
  479         Whether or not to refresh the package database before installing.
  480 
  481     cache_valid_time
  482 
  483         .. versionadded:: 2016.11.0
  484 
  485         Skip refreshing the package database if refresh has already occurred within
  486         <value> seconds
  487 
  488     fromrepo
  489         Specify a package repository to install from
  490         (e.g., ``apt-get -t unstable install somepackage``)
  491 
  492     skip_verify
  493         Skip the GPG verification check (e.g., ``--allow-unauthenticated``, or
  494         ``--force-bad-verify`` for install from package file).
  495 
  496     debconf
  497         Provide the path to a debconf answers file, processed before
  498         installation.
  499 
  500     version
  501         Install a specific version of the package, e.g. 1.2.3~0ubuntu0. Ignored
  502         if "pkgs" or "sources" is passed.
  503 
  504         .. versionchanged:: 2018.3.0
  505             version can now contain comparison operators (e.g. ``>1.2.3``,
  506             ``<=2.0``, etc.)
  507 
  508     reinstall : False
  509         Specifying reinstall=True will use ``apt-get install --reinstall``
  510         rather than simply ``apt-get install`` for requested packages that are
  511         already installed.
  512 
  513         If a version is specified with the requested package, then ``apt-get
  514         install --reinstall`` will only be used if the installed version
  515         matches the requested version.
  516 
  517         .. versionadded:: 2015.8.0
  518 
  519     ignore_epoch : False
  520         Only used when the version of a package is specified using a comparison
  521         operator (e.g. ``>4.1``). If set to ``True``, then the epoch will be
  522         ignored when comparing the currently-installed version to the desired
  523         version.
  524 
  525         .. versionadded:: 2018.3.0
  526 
  527     Multiple Package Installation Options:
  528 
  529     pkgs
  530         A list of packages to install from a software repository. Must be
  531         passed as a python list.
  532 
  533         CLI Example:
  534 
  535         .. code-block:: bash
  536 
  537             salt '*' pkg.install pkgs='["foo", "bar"]'
  538             salt '*' pkg.install pkgs='["foo", {"bar": "1.2.3-0ubuntu0"}]'
  539 
  540     sources
  541         A list of DEB packages to install. Must be passed as a list of dicts,
  542         with the keys being package names, and the values being the source URI
  543         or local path to the package.  Dependencies are automatically resolved
  544         and marked as auto-installed.
  545 
  546         32-bit packages can be installed on 64-bit systems by appending the
  547         architecture designation (``:i386``, etc.) to the end of the package
  548         name.
  549 
  550         .. versionchanged:: 2014.7.0
  551 
  552         CLI Example:
  553 
  554         .. code-block:: bash
  555 
  556             salt '*' pkg.install sources='[{"foo": "salt://foo.deb"},{"bar": "salt://bar.deb"}]'
  557 
  558     force_yes
  559         Passes ``--force-yes`` to the apt-get command.  Don't use this unless
  560         you know what you're doing.
  561 
  562         .. versionadded:: 0.17.4
  563 
  564     install_recommends
  565         Whether to install the packages marked as recommended.  Default is True.
  566 
  567         .. versionadded:: 2015.5.0
  568 
  569     only_upgrade
  570         Only upgrade the packages, if they are already installed. Default is False.
  571 
  572         .. versionadded:: 2015.5.0
  573 
  574     force_conf_new
  575         Always install the new version of any configuration files.
  576 
  577         .. versionadded:: 2015.8.0
  578 
  579     Returns a dict containing the new package names and versions::
  580 
  581         {'<package>': {'old': '<old-version>',
  582                        'new': '<new-version>'}}
  583     """
  584     _refresh_db = False
  585     if salt.utils.data.is_true(refresh):
  586         _refresh_db = True
  587         if "version" in kwargs and kwargs["version"]:
  588             _refresh_db = False
  589             _latest_version = latest_version(name, refresh=False, show_installed=True)
  590             _version = kwargs.get("version")
  591             # If the versions don't match, refresh is True, otherwise no need
  592             # to refresh
  593             if not _latest_version == _version:
  594                 _refresh_db = True
  595 
  596         if pkgs:
  597             _refresh_db = False
  598             for pkg in pkgs:
  599                 if isinstance(pkg, dict):
  600                     _name = next(iter(pkg.keys()))
  601                     _latest_version = latest_version(
  602                         _name, refresh=False, show_installed=True
  603                     )
  604                     _version = pkg[_name]
  605                     # If the versions don't match, refresh is True, otherwise
  606                     # no need to refresh
  607                     if not _latest_version == _version:
  608                         _refresh_db = True
  609                 else:
  610                     # No version specified, so refresh should be True
  611                     _refresh_db = True
  612 
  613     if debconf:
  614         __salt__["debconf.set_file"](debconf)
  615 
  616     try:
  617         pkg_params, pkg_type = __salt__["pkg_resource.parse_targets"](
  618             name, pkgs, sources, **kwargs
  619         )
  620     except MinionError as exc:
  621         raise CommandExecutionError(exc)
  622 
  623     # Support old "repo" argument
  624     repo = kwargs.get("repo", "")
  625     if not fromrepo and repo:
  626         fromrepo = repo
  627 
  628     if not pkg_params:
  629         return {}
  630 
  631     cmd_prefix = []
  632 
  633     old = list_pkgs()
  634     targets = []
  635     downgrade = []
  636     to_reinstall = {}
  637     errors = []
  638     if pkg_type == "repository":
  639         pkg_params_items = list(pkg_params.items())
  640         has_comparison = [
  641             x
  642             for x, y in pkg_params_items
  643             if y is not None and (y.startswith("<") or y.startswith(">"))
  644         ]
  645         _available = (
  646             list_repo_pkgs(*has_comparison, byrepo=False, **kwargs)
  647             if has_comparison
  648             else {}
  649         )
  650         # Build command prefix
  651         cmd_prefix.extend(["apt-get", "-q", "-y"])
  652         if kwargs.get("force_yes", False):
  653             cmd_prefix.append("--force-yes")
  654         if "force_conf_new" in kwargs and kwargs["force_conf_new"]:
  655             cmd_prefix.extend(["-o", "DPkg::Options::=--force-confnew"])
  656         else:
  657             cmd_prefix.extend(["-o", "DPkg::Options::=--force-confold"])
  658             cmd_prefix += ["-o", "DPkg::Options::=--force-confdef"]
  659         if "install_recommends" in kwargs:
  660             if not kwargs["install_recommends"]:
  661                 cmd_prefix.append("--no-install-recommends")
  662             else:
  663                 cmd_prefix.append("--install-recommends")
  664         if "only_upgrade" in kwargs and kwargs["only_upgrade"]:
  665             cmd_prefix.append("--only-upgrade")
  666         if skip_verify:
  667             cmd_prefix.append("--allow-unauthenticated")
  668         if fromrepo:
  669             cmd_prefix.extend(["-t", fromrepo])
  670         cmd_prefix.append("install")
  671     else:
  672         pkg_params_items = []
  673         for pkg_source in pkg_params:
  674             if "lowpkg.bin_pkg_info" in __salt__:
  675                 deb_info = __salt__["lowpkg.bin_pkg_info"](pkg_source)
  676             else:
  677                 deb_info = None
  678             if deb_info is None:
  679                 log.error(
  680                     "pkg.install: Unable to get deb information for %s. "
  681                     "Version comparisons will be unavailable.",
  682                     pkg_source,
  683                 )
  684                 pkg_params_items.append([pkg_source])
  685             else:
  686                 pkg_params_items.append(
  687                     [deb_info["name"], pkg_source, deb_info["version"]]
  688                 )
  689         # Build command prefix
  690         if "force_conf_new" in kwargs and kwargs["force_conf_new"]:
  691             cmd_prefix.extend(["dpkg", "-i", "--force-confnew"])
  692         else:
  693             cmd_prefix.extend(["dpkg", "-i", "--force-confold"])
  694         if skip_verify:
  695             cmd_prefix.append("--force-bad-verify")
  696         if HAS_APT:
  697             _resolve_deps(name, pkg_params, **kwargs)
  698 
  699     for pkg_item_list in pkg_params_items:
  700         if pkg_type == "repository":
  701             pkgname, version_num = pkg_item_list
  702             if name and pkgs is None and kwargs.get("version") and len(pkg_params) == 1:
  703                 # Only use the 'version' param if 'name' was not specified as a
  704                 # comma-separated list
  705                 version_num = kwargs["version"]
  706         else:
  707             try:
  708                 pkgname, pkgpath, version_num = pkg_item_list
  709             except ValueError:
  710                 pkgname = None
  711                 pkgpath = pkg_item_list[0]
  712                 version_num = None
  713 
  714         if version_num is None:
  715             if pkg_type == "repository":
  716                 if reinstall and pkgname in old:
  717                     to_reinstall[pkgname] = pkgname
  718                 else:
  719                     targets.append(pkgname)
  720             else:
  721                 targets.append(pkgpath)
  722         else:
  723             # If we are installing a package file and not one from the repo,
  724             # and version_num is not None, then we can assume that pkgname is
  725             # not None, since the only way version_num is not None is if DEB
  726             # metadata parsing was successful.
  727             if pkg_type == "repository":
  728                 # Remove leading equals sign(s) to keep from building a pkgstr
  729                 # with multiple equals (which would be invalid)
  730                 version_num = version_num.lstrip("=")
  731                 if pkgname in has_comparison:
  732                     candidates = _available.get(pkgname, [])
  733                     target = salt.utils.pkg.match_version(
  734                         version_num,
  735                         candidates,
  736                         cmp_func=version_cmp,
  737                         ignore_epoch=ignore_epoch,
  738                     )
  739                     if target is None:
  740                         errors.append(
  741                             "No version matching '{}{}' could be found "
  742                             "(available: {})".format(
  743                                 pkgname,
  744                                 version_num,
  745                                 ", ".join(candidates) if candidates else None,
  746                             )
  747                         )
  748                         continue
  749                     else:
  750                         version_num = target
  751                 pkgstr = "{}={}".format(pkgname, version_num)
  752             else:
  753                 pkgstr = pkgpath
  754 
  755             cver = old.get(pkgname, "")
  756             if (
  757                 reinstall
  758                 and cver
  759                 and salt.utils.versions.compare(
  760                     ver1=version_num, oper="==", ver2=cver, cmp_func=version_cmp
  761                 )
  762             ):
  763                 to_reinstall[pkgname] = pkgstr
  764             elif not cver or salt.utils.versions.compare(
  765                 ver1=version_num, oper=">=", ver2=cver, cmp_func=version_cmp
  766             ):
  767                 targets.append(pkgstr)
  768             else:
  769                 downgrade.append(pkgstr)
  770 
  771     if fromrepo and not sources:
  772         log.info("Targeting repo '%s'", fromrepo)
  773 
  774     cmds = []
  775     all_pkgs = []
  776     if targets:
  777         all_pkgs.extend(targets)
  778         cmd = copy.deepcopy(cmd_prefix)
  779         cmd.extend(targets)
  780         cmds.append(cmd)
  781 
  782     if downgrade:
  783         cmd = copy.deepcopy(cmd_prefix)
  784         if pkg_type == "repository" and "--force-yes" not in cmd:
  785             # Downgrading requires --force-yes. Insert this before 'install'
  786             cmd.insert(-1, "--force-yes")
  787         cmd.extend(downgrade)
  788         cmds.append(cmd)
  789 
  790     if downloadonly:
  791         cmd.append("--download-only")
  792 
  793     if to_reinstall:
  794         all_pkgs.extend(to_reinstall)
  795         cmd = copy.deepcopy(cmd_prefix)
  796         if not sources:
  797             cmd.append("--reinstall")
  798         cmd.extend([x for x in to_reinstall.values()])
  799         cmds.append(cmd)
  800 
  801     if not cmds:
  802         ret = {}
  803     else:
  804         cache_valid_time = kwargs.pop("cache_valid_time", 0)
  805         if _refresh_db:
  806             refresh_db(cache_valid_time)
  807 
  808         env = _parse_env(kwargs.get("env"))
  809         env.update(DPKG_ENV_VARS.copy())
  810 
  811         hold_pkgs = get_selections(state="hold").get("hold", [])
  812         # all_pkgs contains the argument to be passed to apt-get install, which
  813         # when a specific version is requested will be in the format
  814         # name=version.  Strip off the '=' if present so we can compare the
  815         # held package names against the packages we are trying to install.
  816         targeted_names = [x.split("=")[0] for x in all_pkgs]
  817         to_unhold = [x for x in hold_pkgs if x in targeted_names]
  818 
  819         if to_unhold:
  820             unhold(pkgs=to_unhold)
  821 
  822         for cmd in cmds:
  823             out = _call_apt(cmd)
  824             if out["retcode"] != 0 and out["stderr"]:
  825                 errors.append(out["stderr"])
  826 
  827         __context__.pop("pkg.list_pkgs", None)
  828         new = list_pkgs()
  829         ret = salt.utils.data.compare_dicts(old, new)
  830 
  831         for pkgname in to_reinstall:
  832             if pkgname not in ret or pkgname in old:
  833                 ret.update(
  834                     {
  835                         pkgname: {
  836                             "old": old.get(pkgname, ""),
  837                             "new": new.get(pkgname, ""),
  838                         }
  839                     }
  840                 )
  841 
  842         if to_unhold:
  843             hold(pkgs=to_unhold)
  844 
  845     if errors:
  846         raise CommandExecutionError(
  847             "Problem encountered installing package(s)",
  848             info={"errors": errors, "changes": ret},
  849         )
  850 
  851     return ret
  852 
  853 
  854 def _uninstall(action="remove", name=None, pkgs=None, **kwargs):
  855     """
  856     remove and purge do identical things but with different apt-get commands,
  857     this function performs the common logic.
  858     """
  859     try:
  860         pkg_params = __salt__["pkg_resource.parse_targets"](name, pkgs)[0]
  861     except MinionError as exc:
  862         raise CommandExecutionError(exc)
  863 
  864     old = list_pkgs()
  865     old_removed = list_pkgs(removed=True)
  866     targets = [x for x in pkg_params if x in old]
  867     if action == "purge":
  868         targets.extend([x for x in pkg_params if x in old_removed])
  869     if not targets:
  870         return {}
  871     cmd = ["apt-get", "-q", "-y", action]
  872     cmd.extend(targets)
  873     env = _parse_env(kwargs.get("env"))
  874     env.update(DPKG_ENV_VARS.copy())
  875     out = _call_apt(cmd, env=env)
  876     if out["retcode"] != 0 and out["stderr"]:
  877         errors = [out["stderr"]]
  878     else:
  879         errors = []
  880 
  881     __context__.pop("pkg.list_pkgs", None)
  882     new = list_pkgs()
  883     new_removed = list_pkgs(removed=True)
  884 
  885     changes = salt.utils.data.compare_dicts(old, new)
  886     if action == "purge":
  887         ret = {
  888             "removed": salt.utils.data.compare_dicts(old_removed, new_removed),
  889             "installed": changes,
  890         }
  891     else:
  892         ret = changes
  893 
  894     if errors:
  895         raise CommandExecutionError(
  896             "Problem encountered removing package(s)",
  897             info={"errors": errors, "changes": ret},
  898         )
  899 
  900     return ret
  901 
  902 
  903 def autoremove(list_only=False, purge=False):
  904     """
  905     .. versionadded:: 2015.5.0
  906 
  907     Remove packages not required by another package using ``apt-get
  908     autoremove``.
  909 
  910     list_only : False
  911         Only retrieve the list of packages to be auto-removed, do not actually
  912         perform the auto-removal.
  913 
  914     purge : False
  915         Also remove package config data when autoremoving packages.
  916 
  917         .. versionadded:: 2015.8.0
  918 
  919     CLI Example:
  920 
  921     .. code-block:: bash
  922 
  923         salt '*' pkg.autoremove
  924         salt '*' pkg.autoremove list_only=True
  925         salt '*' pkg.autoremove purge=True
  926     """
  927     cmd = []
  928     if list_only:
  929         ret = []
  930         cmd.extend(["apt-get", "--assume-no"])
  931         if purge:
  932             cmd.append("--purge")
  933         cmd.append("autoremove")
  934         out = _call_apt(cmd, ignore_retcode=True)["stdout"]
  935         found = False
  936         for line in out.splitlines():
  937             if found is True:
  938                 if line.startswith(" "):
  939                     ret.extend(line.split())
  940                 else:
  941                     found = False
  942             elif "The following packages will be REMOVED:" in line:
  943                 found = True
  944         ret.sort()
  945         return ret
  946     else:
  947         old = list_pkgs()
  948         cmd.extend(["apt-get", "--assume-yes"])
  949         if purge:
  950             cmd.append("--purge")
  951         cmd.append("autoremove")
  952         _call_apt(cmd, ignore_retcode=True)
  953         __context__.pop("pkg.list_pkgs", None)
  954         new = list_pkgs()
  955         return salt.utils.data.compare_dicts(old, new)
  956 
  957 
  958 def remove(name=None, pkgs=None, **kwargs):
  959     """
  960     .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
  961         On minions running systemd>=205, `systemd-run(1)`_ is now used to
  962         isolate commands which modify installed packages from the
  963         ``salt-minion`` daemon's control group. This is done to keep systemd
  964         from killing any apt-get/dpkg commands spawned by Salt when the
  965         ``salt-minion`` service is restarted. (see ``KillMode`` in the
  966         `systemd.kill(5)`_ manpage for more information). If desired, usage of
  967         `systemd-run(1)`_ can be suppressed by setting a :mod:`config option
  968         <salt.modules.config.get>` called ``systemd.scope``, with a value of
  969         ``False`` (no quotes).
  970 
  971     .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html
  972     .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html
  973 
  974     Remove packages using ``apt-get remove``.
  975 
  976     name
  977         The name of the package to be deleted.
  978 
  979 
  980     Multiple Package Options:
  981 
  982     pkgs
  983         A list of packages to delete. Must be passed as a python list. The
  984         ``name`` parameter will be ignored if this option is passed.
  985 
  986     .. versionadded:: 0.16.0
  987 
  988 
  989     Returns a dict containing the changes.
  990 
  991     CLI Example:
  992 
  993     .. code-block:: bash
  994 
  995         salt '*' pkg.remove <package name>
  996         salt '*' pkg.remove <package1>,<package2>,<package3>
  997         salt '*' pkg.remove pkgs='["foo", "bar"]'
  998     """
  999     return _uninstall(action="remove", name=name, pkgs=pkgs, **kwargs)
 1000 
 1001 
 1002 def purge(name=None, pkgs=None, **kwargs):
 1003     """
 1004     .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
 1005         On minions running systemd>=205, `systemd-run(1)`_ is now used to
 1006         isolate commands which modify installed packages from the
 1007         ``salt-minion`` daemon's control group. This is done to keep systemd
 1008         from killing any apt-get/dpkg commands spawned by Salt when the
 1009         ``salt-minion`` service is restarted. (see ``KillMode`` in the
 1010         `systemd.kill(5)`_ manpage for more information). If desired, usage of
 1011         `systemd-run(1)`_ can be suppressed by setting a :mod:`config option
 1012         <salt.modules.config.get>` called ``systemd.scope``, with a value of
 1013         ``False`` (no quotes).
 1014 
 1015     .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html
 1016     .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html
 1017 
 1018     Remove packages via ``apt-get purge`` along with all configuration files.
 1019 
 1020     name
 1021         The name of the package to be deleted.
 1022 
 1023 
 1024     Multiple Package Options:
 1025 
 1026     pkgs
 1027         A list of packages to delete. Must be passed as a python list. The
 1028         ``name`` parameter will be ignored if this option is passed.
 1029 
 1030     .. versionadded:: 0.16.0
 1031 
 1032 
 1033     Returns a dict containing the changes.
 1034 
 1035     CLI Example:
 1036 
 1037     .. code-block:: bash
 1038 
 1039         salt '*' pkg.purge <package name>
 1040         salt '*' pkg.purge <package1>,<package2>,<package3>
 1041         salt '*' pkg.purge pkgs='["foo", "bar"]'
 1042     """
 1043     return _uninstall(action="purge", name=name, pkgs=pkgs, **kwargs)
 1044 
 1045 
 1046 def upgrade(refresh=True, dist_upgrade=False, **kwargs):
 1047     """
 1048     .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
 1049         On minions running systemd>=205, `systemd-run(1)`_ is now used to
 1050         isolate commands which modify installed packages from the
 1051         ``salt-minion`` daemon's control group. This is done to keep systemd
 1052         from killing any apt-get/dpkg commands spawned by Salt when the
 1053         ``salt-minion`` service is restarted. (see ``KillMode`` in the
 1054         `systemd.kill(5)`_ manpage for more information). If desired, usage of
 1055         `systemd-run(1)`_ can be suppressed by setting a :mod:`config option
 1056         <salt.modules.config.get>` called ``systemd.scope``, with a value of
 1057         ``False`` (no quotes).
 1058 
 1059     .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html
 1060     .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html
 1061 
 1062     Upgrades all packages via ``apt-get upgrade`` or ``apt-get dist-upgrade``
 1063     if  ``dist_upgrade`` is ``True``.
 1064 
 1065     Returns a dictionary containing the changes:
 1066 
 1067     .. code-block:: python
 1068 
 1069         {'<package>':  {'old': '<old-version>',
 1070                         'new': '<new-version>'}}
 1071 
 1072     dist_upgrade
 1073         Whether to perform the upgrade using dist-upgrade vs upgrade.  Default
 1074         is to use upgrade.
 1075 
 1076         .. versionadded:: 2014.7.0
 1077 
 1078     refresh : True
 1079         If ``True``, the apt cache will be refreshed first. By default,
 1080         this is ``True`` and a refresh is performed.
 1081 
 1082     cache_valid_time
 1083 
 1084         .. versionadded:: 2016.11.0
 1085 
 1086         Skip refreshing the package database if refresh has already occurred within
 1087         <value> seconds
 1088 
 1089     download_only (or downloadonly)
 1090         Only download the packages, don't unpack or install them. Use
 1091         downloadonly to be in line with yum and zypper module.
 1092 
 1093         .. versionadded:: 2018.3.0
 1094 
 1095     force_conf_new
 1096         Always install the new version of any configuration files.
 1097 
 1098         .. versionadded:: 2015.8.0
 1099 
 1100     CLI Example:
 1101 
 1102     .. code-block:: bash
 1103 
 1104         salt '*' pkg.upgrade
 1105     """
 1106     cache_valid_time = kwargs.pop("cache_valid_time", 0)
 1107     if salt.utils.data.is_true(refresh):
 1108         refresh_db(cache_valid_time)
 1109 
 1110     old = list_pkgs()
 1111     if "force_conf_new" in kwargs and kwargs["force_conf_new"]:
 1112         dpkg_options = ["--force-confnew"]
 1113     else:
 1114         dpkg_options = ["--force-confold", "--force-confdef"]
 1115     cmd = [
 1116         "apt-get",
 1117         "-q",
 1118         "-y",
 1119     ]
 1120     for option in dpkg_options:
 1121         cmd.append("-o")
 1122         cmd.append("DPkg::Options::={}".format(option))
 1123 
 1124     if kwargs.get("force_yes", False):
 1125         cmd.append("--force-yes")
 1126     if kwargs.get("skip_verify", False):
 1127         cmd.append("--allow-unauthenticated")
 1128     if kwargs.get("download_only", False) or kwargs.get("downloadonly", False):
 1129         cmd.append("--download-only")
 1130 
 1131     cmd.append("dist-upgrade" if dist_upgrade else "upgrade")
 1132     result = _call_apt(cmd, env=DPKG_ENV_VARS.copy())
 1133     __context__.pop("pkg.list_pkgs", None)
 1134     new = list_pkgs()
 1135     ret = salt.utils.data.compare_dicts(old, new)
 1136 
 1137     if result["retcode"] != 0:
 1138         raise CommandExecutionError(
 1139             "Problem encountered upgrading packages",
 1140             info={"changes": ret, "result": result},
 1141         )
 1142 
 1143     return ret
 1144 
 1145 
 1146 def hold(name=None, pkgs=None, sources=None, **kwargs):  # pylint: disable=W0613
 1147     """
 1148     .. versionadded:: 2014.7.0
 1149 
 1150     Set package in 'hold' state, meaning it will not be upgraded.
 1151 
 1152     name
 1153         The name of the package, e.g., 'tmux'
 1154 
 1155         CLI Example:
 1156 
 1157         .. code-block:: bash
 1158 
 1159             salt '*' pkg.hold <package name>
 1160 
 1161     pkgs
 1162         A list of packages to hold. Must be passed as a python list.
 1163 
 1164         CLI Example:
 1165 
 1166         .. code-block:: bash
 1167 
 1168             salt '*' pkg.hold pkgs='["foo", "bar"]'
 1169     """
 1170     if not name and not pkgs and not sources:
 1171         raise SaltInvocationError("One of name, pkgs, or sources must be specified.")
 1172     if pkgs and sources:
 1173         raise SaltInvocationError("Only one of pkgs or sources can be specified.")
 1174 
 1175     targets = []
 1176     if pkgs:
 1177         targets.extend(pkgs)
 1178     elif sources:
 1179         for source in sources:
 1180             targets.append(next(iter(source)))
 1181     else:
 1182         targets.append(name)
 1183 
 1184     ret = {}
 1185     for target in targets:
 1186         if isinstance(target, dict):
 1187             target = next(iter(target))
 1188 
 1189         ret[target] = {"name": target, "changes": {}, "result": False, "comment": ""}
 1190 
 1191         state = get_selections(pattern=target, state="hold")
 1192         if not state:
 1193             ret[target]["comment"] = "Package {} not currently held.".format(target)
 1194         elif not salt.utils.data.is_true(state.get("hold", False)):
 1195             if "test" in __opts__ and __opts__["test"]:
 1196                 ret[target].update(result=None)
 1197                 ret[target]["comment"] = "Package {} is set to be held.".format(target)
 1198             else:
 1199                 result = set_selections(selection={"hold": [target]})
 1200                 ret[target].update(changes=result[target], result=True)
 1201                 ret[target]["comment"] = "Package {} is now being held.".format(target)
 1202         else:
 1203             ret[target].update(result=True)
 1204             ret[target]["comment"] = "Package {} is already set to be held.".format(
 1205                 target
 1206             )
 1207     return ret
 1208 
 1209 
 1210 def unhold(name=None, pkgs=None, sources=None, **kwargs):  # pylint: disable=W0613
 1211     """
 1212     .. versionadded:: 2014.7.0
 1213 
 1214     Set package current in 'hold' state to install state,
 1215     meaning it will be upgraded.
 1216 
 1217     name
 1218         The name of the package, e.g., 'tmux'
 1219 
 1220         CLI Example:
 1221 
 1222         .. code-block:: bash
 1223 
 1224             salt '*' pkg.unhold <package name>
 1225 
 1226     pkgs
 1227         A list of packages to unhold. Must be passed as a python list.
 1228 
 1229         CLI Example:
 1230 
 1231         .. code-block:: bash
 1232 
 1233             salt '*' pkg.unhold pkgs='["foo", "bar"]'
 1234     """
 1235     if not name and not pkgs and not sources:
 1236         raise SaltInvocationError("One of name, pkgs, or sources must be specified.")
 1237     if pkgs and sources:
 1238         raise SaltInvocationError("Only one of pkgs or sources can be specified.")
 1239 
 1240     targets = []
 1241     if pkgs:
 1242         targets.extend(pkgs)
 1243     elif sources:
 1244         for source in sources:
 1245             targets.append(next(iter(source)))
 1246     else:
 1247         targets.append(name)
 1248 
 1249     ret = {}
 1250     for target in targets:
 1251         if isinstance(target, dict):
 1252             target = next(iter(target))
 1253 
 1254         ret[target] = {"name": target, "changes": {}, "result": False, "comment": ""}
 1255 
 1256         state = get_selections(pattern=target)
 1257         if not state:
 1258             ret[target]["comment"] = "Package {} does not have a state.".format(target)
 1259         elif salt.utils.data.is_true(state.get("hold", False)):
 1260             if "test" in __opts__ and __opts__["test"]:
 1261                 ret[target].update(result=None)
 1262                 ret[target]["comment"] = "Package {} is set not to be " "held.".format(
 1263                     target
 1264                 )
 1265             else:
 1266                 result = set_selections(selection={"install": [target]})
 1267                 ret[target].update(changes=result[target], result=True)
 1268                 ret[target][
 1269                     "comment"
 1270                 ] = "Package {} is no longer being " "held.".format(target)
 1271         else:
 1272             ret[target].update(result=True)
 1273             ret[target][
 1274                 "comment"
 1275             ] = "Package {} is already set not to be " "held.".format(target)
 1276     return ret
 1277 
 1278 
 1279 def list_pkgs(
 1280     versions_as_list=False, removed=False, purge_desired=False, **kwargs
 1281 ):  # pylint: disable=W0613
 1282     """
 1283     List the packages currently installed in a dict::
 1284 
 1285         {'<package_name>': '<version>'}
 1286 
 1287     removed
 1288         If ``True``, then only packages which have been removed (but not
 1289         purged) will be returned.
 1290 
 1291     purge_desired
 1292         If ``True``, then only packages which have been marked to be purged,
 1293         but can't be purged due to their status as dependencies for other
 1294         installed packages, will be returned. Note that these packages will
 1295         appear in installed
 1296 
 1297         .. versionchanged:: 2014.1.1
 1298 
 1299             Packages in this state now correctly show up in the output of this
 1300             function.
 1301 
 1302 
 1303     CLI Example:
 1304 
 1305     .. code-block:: bash
 1306 
 1307         salt '*' pkg.list_pkgs
 1308         salt '*' pkg.list_pkgs versions_as_list=True
 1309     """
 1310     versions_as_list = salt.utils.data.is_true(versions_as_list)
 1311     removed = salt.utils.data.is_true(removed)
 1312     purge_desired = salt.utils.data.is_true(purge_desired)
 1313 
 1314     if "pkg.list_pkgs" in __context__:
 1315         if removed:
 1316             ret = copy.deepcopy(__context__["pkg.list_pkgs"]["removed"])
 1317         else:
 1318             ret = copy.deepcopy(__context__["pkg.list_pkgs"]["purge_desired"])
 1319             if not purge_desired:
 1320                 ret.update(__context__["pkg.list_pkgs"]["installed"])
 1321         if not versions_as_list:
 1322             __salt__["pkg_resource.stringify"](ret)
 1323         return ret
 1324 
 1325     ret = {"installed": {}, "removed": {}, "purge_desired": {}}
 1326     cmd = [
 1327         "dpkg-query",
 1328         "--showformat",
 1329         "${Status} ${Package} ${Version} ${Architecture}\n",
 1330         "-W",
 1331     ]
 1332 
 1333     out = __salt__["cmd.run_stdout"](cmd, output_loglevel="trace", python_shell=False)
 1334     # Typical lines of output:
 1335     # install ok installed zsh 4.3.17-1ubuntu1 amd64
 1336     # deinstall ok config-files mc 3:4.8.1-2ubuntu1 amd64
 1337     for line in out.splitlines():
 1338         cols = line.split()
 1339         try:
 1340             linetype, status, name, version_num, arch = [
 1341                 cols[x] for x in (0, 2, 3, 4, 5)
 1342             ]
 1343         except (ValueError, IndexError):
 1344             continue
 1345         if __grains__.get("cpuarch", "") == "x86_64":
 1346             osarch = __grains__.get("osarch", "")
 1347             if arch != "all" and osarch == "amd64" and osarch != arch:
 1348                 name += ":{}".format(arch)
 1349         if cols:
 1350             if ("install" in linetype or "hold" in linetype) and "installed" in status:
 1351                 __salt__["pkg_resource.add_pkg"](ret["installed"], name, version_num)
 1352             elif "deinstall" in linetype:
 1353                 __salt__["pkg_resource.add_pkg"](ret["removed"], name, version_num)
 1354             elif "purge" in linetype and status == "installed":
 1355                 __salt__["pkg_resource.add_pkg"](
 1356                     ret["purge_desired"], name, version_num
 1357                 )
 1358 
 1359     for pkglist_type in ("installed", "removed", "purge_desired"):
 1360         __salt__["pkg_resource.sort_pkglist"](ret[pkglist_type])
 1361 
 1362     __context__["pkg.list_pkgs"] = copy.deepcopy(ret)
 1363 
 1364     if removed:
 1365         ret = ret["removed"]
 1366     else:
 1367         ret = copy.deepcopy(__context__["pkg.list_pkgs"]["purge_desired"])
 1368         if not purge_desired:
 1369             ret.update(__context__["pkg.list_pkgs"]["installed"])
 1370     if not versions_as_list:
 1371         __salt__["pkg_resource.stringify"](ret)
 1372     return ret
 1373 
 1374 
 1375 def _get_upgradable(dist_upgrade=True, **kwargs):
 1376     """
 1377     Utility function to get upgradable packages
 1378 
 1379     Sample return data:
 1380     { 'pkgname': '1.2.3-45', ... }
 1381     """
 1382 
 1383     cmd = ["apt-get", "--just-print"]
 1384     if dist_upgrade:
 1385         cmd.append("dist-upgrade")
 1386     else:
 1387         cmd.append("upgrade")
 1388     try:
 1389         cmd.extend(["-o", "APT::Default-Release={}".format(kwargs["fromrepo"])])
 1390     except KeyError:
 1391         pass
 1392 
 1393     call = _call_apt(cmd)
 1394     if call["retcode"] != 0:
 1395         msg = "Failed to get upgrades"
 1396         for key in ("stderr", "stdout"):
 1397             if call[key]:
 1398                 msg += ": " + call[key]
 1399                 break
 1400         raise CommandExecutionError(msg)
 1401     else:
 1402         out = call["stdout"]
 1403 
 1404     # rexp parses lines that look like the following:
 1405     # Conf libxfont1 (1:1.4.5-1 Debian:testing [i386])
 1406     rexp = re.compile("(?m)^Conf " "([^ ]+) " r"\(([^ ]+)")  # Package name  # Version
 1407     keys = ["name", "version"]
 1408     _get = lambda l, k: l[keys.index(k)]
 1409 
 1410     upgrades = rexp.findall(out)
 1411 
 1412     ret = {}
 1413     for line in upgrades:
 1414         name = _get(line, "name")
 1415         version_num = _get(line, "version")
 1416         ret[name] = version_num
 1417 
 1418     return ret
 1419 
 1420 
 1421 def list_upgrades(refresh=True, dist_upgrade=True, **kwargs):
 1422     """
 1423     List all available package upgrades.
 1424 
 1425     refresh
 1426         Whether to refresh the package database before listing upgrades.
 1427         Default: True.
 1428 
 1429     cache_valid_time
 1430 
 1431         .. versionadded:: 2016.11.0
 1432 
 1433         Skip refreshing the package database if refresh has already occurred within
 1434         <value> seconds
 1435 
 1436     dist_upgrade
 1437         Whether to list the upgrades using dist-upgrade vs upgrade.  Default is
 1438         to use dist-upgrade.
 1439 
 1440     CLI Example:
 1441 
 1442     .. code-block:: bash
 1443 
 1444         salt '*' pkg.list_upgrades
 1445     """
 1446     cache_valid_time = kwargs.pop("cache_valid_time", 0)
 1447     if salt.utils.data.is_true(refresh):
 1448         refresh_db(cache_valid_time)
 1449     return _get_upgradable(dist_upgrade, **kwargs)
 1450 
 1451 
 1452 def upgrade_available(name, **kwargs):
 1453     """
 1454     Check whether or not an upgrade is available for a given package
 1455 
 1456     CLI Example:
 1457 
 1458     .. code-block:: bash
 1459 
 1460         salt '*' pkg.upgrade_available <package name>
 1461     """
 1462     return latest_version(name) != ""
 1463 
 1464 
 1465 def version_cmp(pkg1, pkg2, ignore_epoch=False, **kwargs):
 1466     """
 1467     Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
 1468     pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
 1469     making the comparison.
 1470 
 1471     ignore_epoch : False
 1472         Set to ``True`` to ignore the epoch when comparing versions
 1473 
 1474         .. versionadded:: 2015.8.10,2016.3.2
 1475 
 1476     CLI Example:
 1477 
 1478     .. code-block:: bash
 1479 
 1480         salt '*' pkg.version_cmp '0.2.4-0ubuntu1' '0.2.4.1-0ubuntu1'
 1481     """
 1482     normalize = lambda x: str(x).split(":", 1)[-1] if ignore_epoch else str(x)
 1483     # both apt_pkg.version_compare and _cmd_quote need string arguments.
 1484     pkg1 = normalize(pkg1)
 1485     pkg2 = normalize(pkg2)
 1486 
 1487     # if we have apt_pkg, this will be quickier this way
 1488     # and also do not rely on shell.
 1489     if HAS_APTPKG:
 1490         try:
 1491             # the apt_pkg module needs to be manually initialized
 1492             apt_pkg.init_system()
 1493 
 1494             # if there is a difference in versions, apt_pkg.version_compare will
 1495             # return an int representing the difference in minor versions, or
 1496             # 1/-1 if the difference is smaller than minor versions. normalize
 1497             # to -1, 0 or 1.
 1498             try:
 1499                 ret = apt_pkg.version_compare(pkg1, pkg2)
 1500             except TypeError:
 1501                 ret = apt_pkg.version_compare(str(pkg1), str(pkg2))
 1502             return 1 if ret > 0 else -1 if ret < 0 else 0
 1503         except Exception:  # pylint: disable=broad-except
 1504             # Try to use shell version in case of errors w/python bindings
 1505             pass
 1506     try:
 1507         for oper, ret in (("lt", -1), ("eq", 0), ("gt", 1)):
 1508             cmd = ["dpkg", "--compare-versions", pkg1, oper, pkg2]
 1509             retcode = __salt__["cmd.retcode"](
 1510                 cmd, output_loglevel="trace", python_shell=False, ignore_retcode=True
 1511             )
 1512             if retcode == 0:
 1513                 return ret
 1514     except Exception as exc:  # pylint: disable=broad-except
 1515         log.error(exc)
 1516     return None
 1517 
 1518 
 1519 def _split_repo_str(repo):
 1520     """
 1521     Return APT source entry as a tuple.
 1522     """
 1523     split = sourceslist.SourceEntry(repo)
 1524     return split.type, split.architectures, split.uri, split.dist, split.comps
 1525 
 1526 
 1527 def _consolidate_repo_sources(sources):
 1528     """
 1529     Consolidate APT sources.
 1530     """
 1531     if not isinstance(sources, sourceslist.SourcesList):
 1532         raise TypeError(
 1533             "'{}' not a '{}'".format(type(sources), sourceslist.SourcesList)
 1534         )
 1535 
 1536     consolidated = {}
 1537     delete_files = set()
 1538     base_file = sourceslist.SourceEntry("").file
 1539 
 1540     repos = [s for s in sources.list if not s.invalid]
 1541 
 1542     for repo in repos:
 1543         # future lint: disable=blacklisted-function
 1544         key = str(
 1545             (
 1546                 getattr(repo, "architectures", []),
 1547                 repo.disabled,
 1548                 repo.type,
 1549                 repo.uri,
 1550                 repo.dist,
 1551             )
 1552         )
 1553         # future lint: enable=blacklisted-function
 1554         if key in consolidated:
 1555             combined = consolidated[key]
 1556             combined_comps = set(repo.comps).union(set(combined.comps))
 1557             consolidated[key].comps = list(combined_comps)
 1558         else:
 1559             consolidated[key] = sourceslist.SourceEntry(repo.line)
 1560 
 1561         if repo.file != base_file:
 1562             delete_files.add(repo.file)
 1563 
 1564     sources.list = list(consolidated.values())
 1565     sources.save()
 1566     for file_ in delete_files:
 1567         try:
 1568             os.remove(file_)
 1569         except OSError:
 1570             pass
 1571     return sources
 1572 
 1573 
 1574 def list_repo_pkgs(*args, **kwargs):  # pylint: disable=unused-import
 1575     """
 1576     .. versionadded:: 2017.7.0
 1577 
 1578     Returns all available packages. Optionally, package names (and name globs)
 1579     can be passed and the results will be filtered to packages matching those
 1580     names.
 1581 
 1582     This function can be helpful in discovering the version or repo to specify
 1583     in a :mod:`pkg.installed <salt.states.pkg.installed>` state.
 1584 
 1585     The return data will be a dictionary mapping package names to a list of
 1586     version numbers, ordered from newest to oldest. For example:
 1587 
 1588     .. code-block:: python
 1589 
 1590         {
 1591             'bash': ['4.3-14ubuntu1.1',
 1592                      '4.3-14ubuntu1'],
 1593             'nginx': ['1.10.0-0ubuntu0.16.04.4',
 1594                       '1.9.15-0ubuntu1']
 1595         }
 1596 
 1597     CLI Examples:
 1598 
 1599     .. code-block:: bash
 1600 
 1601         salt '*' pkg.list_repo_pkgs
 1602         salt '*' pkg.list_repo_pkgs foo bar baz
 1603     """
 1604     if args:
 1605         # Get only information about packages in args
 1606         cmd = ["apt-cache", "show"] + [arg for arg in args]
 1607     else:
 1608         # Get information about all available packages
 1609         cmd = ["apt-cache", "dump"]
 1610 
 1611     out = _call_apt(cmd, scope=False, ignore_retcode=True)
 1612 
 1613     ret = {}
 1614     pkg_name = None
 1615     skip_pkg = False
 1616     new_pkg = re.compile("^Package: (.+)")
 1617     for line in salt.utils.itertools.split(out["stdout"], "\n"):
 1618         if not line.strip():
 1619             continue
 1620         try:
 1621             cur_pkg = new_pkg.match(line).group(1)
 1622         except AttributeError:
 1623             pass
 1624         else:
 1625             if cur_pkg != pkg_name:
 1626                 pkg_name = cur_pkg
 1627                 continue
 1628         comps = line.strip().split(None, 1)
 1629         if comps[0] == "Version:":
 1630             ret.setdefault(pkg_name, []).append(comps[1])
 1631 
 1632     return ret
 1633 
 1634 
 1635 def _skip_source(source):
 1636     """
 1637     Decide to skip source or not.
 1638 
 1639     :param source:
 1640     :return:
 1641     """
 1642     if source.invalid:
 1643         if (
 1644             source.uri
 1645             and source.type
 1646             and source.type in ("deb", "deb-src", "rpm", "rpm-src")
 1647         ):
 1648             pieces = source.mysplit(source.line)
 1649             if pieces[1].strip()[0] == "[":
 1650                 options = pieces.pop(1).strip("[]").split()
 1651                 if len(options) > 0:
 1652                     log.debug(
 1653                         "Source %s will be included although is marked invalid",
 1654                         source.uri,
 1655                     )
 1656                     return False
 1657             return True
 1658         else:
 1659             return True
 1660     return False
 1661 
 1662 
 1663 def list_repos(**kwargs):
 1664     """
 1665     Lists all repos in the sources.list (and sources.lists.d) files
 1666 
 1667     CLI Example:
 1668 
 1669     .. code-block:: bash
 1670 
 1671        salt '*' pkg.list_repos
 1672        salt '*' pkg.list_repos disabled=True
 1673     """
 1674     _check_apt()
 1675     repos = {}
 1676     sources = sourceslist.SourcesList()
 1677     for source in sources.list:
 1678         if _skip_source(source):
 1679             continue
 1680         repo = {}
 1681         repo["file"] = source.file
 1682         repo["comps"] = getattr(source, "comps", [])
 1683         repo["disabled"] = source.disabled
 1684         repo["dist"] = source.dist
 1685         repo["type"] = source.type
 1686         repo["uri"] = source.uri
 1687         repo["line"] = source.line.strip()
 1688         repo["architectures"] = getattr(source, "architectures", [])
 1689         repos.setdefault(source.uri, []).append(repo)
 1690     return repos
 1691 
 1692 
 1693 def get_repo(repo, **kwargs):
 1694     """
 1695     Display a repo from the sources.list / sources.list.d
 1696 
 1697     The repo passed in needs to be a complete repo entry.
 1698 
 1699     CLI Examples:
 1700 
 1701     .. code-block:: bash
 1702 
 1703         salt '*' pkg.get_repo "myrepo definition"
 1704     """
 1705     _check_apt()
 1706     ppa_auth = kwargs.get("ppa_auth", None)
 1707     # we have to be clever about this since the repo definition formats
 1708     # are a bit more "loose" than in some other distributions
 1709     if repo.startswith("ppa:") and __grains__["os"] in ("Ubuntu", "Mint", "neon"):
 1710         # This is a PPA definition meaning special handling is needed
 1711         # to derive the name.
 1712         dist = __grains__["lsb_distrib_codename"]
 1713         owner_name, ppa_name = repo[4:].split("/")
 1714         if ppa_auth:
 1715             auth_info = "{}@".format(ppa_auth)
 1716             repo = LP_PVT_SRC_FORMAT.format(auth_info, owner_name, ppa_name, dist)
 1717         else:
 1718             if HAS_SOFTWAREPROPERTIES:
 1719                 try:
 1720                     if hasattr(softwareproperties.ppa, "PPAShortcutHandler"):
 1721                         repo = softwareproperties.ppa.PPAShortcutHandler(repo).expand(
 1722                             dist
 1723                         )[0]
 1724                     else:
 1725                         repo = softwareproperties.ppa.expand_ppa_line(repo, dist)[0]
 1726                 except NameError as name_error:
 1727                     raise CommandExecutionError(
 1728                         "Could not find ppa {}: {}".format(repo, name_error)
 1729                     )
 1730             else:
 1731                 repo = LP_SRC_FORMAT.format(owner_name, ppa_name, dist)
 1732 
 1733     repos = list_repos()
 1734 
 1735     if repos:
 1736         try:
 1737             (
 1738                 repo_type,
 1739                 repo_architectures,
 1740                 repo_uri,
 1741                 repo_dist,
 1742                 repo_comps,
 1743             ) = _split_repo_str(repo)
 1744             if ppa_auth:
 1745                 uri_match = re.search("(http[s]?://)(.+)", repo_uri)
 1746                 if uri_match:
 1747                     if not uri_match.group(2).startswith(ppa_auth):
 1748                         repo_uri = "{}{}@{}".format(
 1749                             uri_match.group(1), ppa_auth, uri_match.group(2)
 1750                         )
 1751         except SyntaxError:
 1752             raise CommandExecutionError(
 1753                 "Error: repo '{}' is not a well formatted definition".format(repo)
 1754             )
 1755 
 1756         for source in repos.values():
 1757             for sub in source:
 1758                 if (
 1759                     sub["type"] == repo_type
 1760                     and sub["uri"] == repo_uri
 1761                     and sub["dist"] == repo_dist
 1762                 ):
 1763                     if not repo_comps:
 1764                         return sub
 1765                     for comp in repo_comps:
 1766                         if comp in sub.get("comps", []):
 1767                             return sub
 1768     return {}
 1769 
 1770 
 1771 def del_repo(repo, **kwargs):
 1772     """
 1773     Delete a repo from the sources.list / sources.list.d
 1774 
 1775     If the .list file is in the sources.list.d directory
 1776     and the file that the repo exists in does not contain any other
 1777     repo configuration, the file itself will be deleted.
 1778 
 1779     The repo passed in must be a fully formed repository definition
 1780     string.
 1781 
 1782     CLI Examples:
 1783 
 1784     .. code-block:: bash
 1785 
 1786         salt '*' pkg.del_repo "myrepo definition"
 1787     """
 1788     _check_apt()
 1789     is_ppa = False
 1790     if repo.startswith("ppa:") and __grains__["os"] in ("Ubuntu", "Mint", "neon"):
 1791         # This is a PPA definition meaning special handling is needed
 1792         # to derive the name.
 1793         is_ppa = True
 1794         dist = __grains__["lsb_distrib_codename"]
 1795         if not HAS_SOFTWAREPROPERTIES:
 1796             _warn_software_properties(repo)
 1797             owner_name, ppa_name = repo[4:].split("/")
 1798             if "ppa_auth" in kwargs:
 1799                 auth_info = "{}@".format(kwargs["ppa_auth"])
 1800                 repo = LP_PVT_SRC_FORMAT.format(auth_info, dist, owner_name, ppa_name)
 1801             else:
 1802                 repo = LP_SRC_FORMAT.format(owner_name, ppa_name, dist)
 1803         else:
 1804             if hasattr(softwareproperties.ppa, "PPAShortcutHandler"):
 1805                 repo = softwareproperties.ppa.PPAShortcutHandler(repo).expand(dist)[0]
 1806             else:
 1807                 repo = softwareproperties.ppa.expand_ppa_line(repo, dist)[0]
 1808 
 1809     sources = sourceslist.SourcesList()
 1810     repos = [s for s in sources.list if not s.invalid]
 1811     if repos:
 1812         deleted_from = dict()
 1813         try:
 1814             (
 1815                 repo_type,
 1816                 repo_architectures,
 1817                 repo_uri,
 1818                 repo_dist,
 1819                 repo_comps,
 1820             ) = _split_repo_str(repo)
 1821         except SyntaxError:
 1822             raise SaltInvocationError(
 1823                 "Error: repo '{}' not a well formatted definition".format(repo)
 1824             )
 1825 
 1826         for source in repos:
 1827             if (
 1828                 source.type == repo_type
 1829                 and source.architectures == repo_architectures
 1830                 and source.uri == repo_uri
 1831                 and source.dist == repo_dist
 1832             ):
 1833 
 1834                 s_comps = set(source.comps)
 1835                 r_comps = set(repo_comps)
 1836                 if s_comps.intersection(r_comps):
 1837                     deleted_from[source.file] = 0
 1838                     source.comps = list(s_comps.difference(r_comps))
 1839                     if not source.comps:
 1840                         try:
 1841                             sources.remove(source)
 1842                         except ValueError:
 1843                             pass
 1844             # PPAs are special and can add deb-src where expand_ppa_line
 1845             # doesn't always reflect this.  Lets just cleanup here for good
 1846             # measure
 1847             if (
 1848                 is_ppa
 1849                 and repo_type == "deb"
 1850                 and source.type == "deb-src"
 1851                 and source.uri == repo_uri
 1852                 and source.dist == repo_dist
 1853             ):
 1854 
 1855                 s_comps = set(source.comps)
 1856                 r_comps = set(repo_comps)
 1857                 if s_comps.intersection(r_comps):
 1858                     deleted_from[source.file] = 0
 1859                     source.comps = list(s_comps.difference(r_comps))
 1860                     if not source.comps:
 1861                         try:
 1862                             sources.remove(source)
 1863                         except ValueError:
 1864                             pass
 1865             sources.save()
 1866         if deleted_from:
 1867             ret = ""
 1868             for source in sources:
 1869                 if source.file in deleted_from:
 1870                     deleted_from[source.file] += 1
 1871             for repo_file, count in deleted_from.items():
 1872                 msg = "Repo '{0}' has been removed from {1}.\n"
 1873                 if count == 0 and "sources.list.d/" in repo_file:
 1874                     if os.path.isfile(repo_file):
 1875                         msg = "File {1} containing repo '{0}' has been " "removed."
 1876                         try:
 1877                             os.remove(repo_file)
 1878                         except OSError:
 1879                             pass
 1880                 ret += msg.format(repo, repo_file)
 1881             # explicit refresh after a repo is deleted
 1882             refresh_db()
 1883             return ret
 1884 
 1885     raise CommandExecutionError(
 1886         "Repo {} doesn't exist in the sources.list(s)".format(repo)
 1887     )
 1888 
 1889 
 1890 def _convert_if_int(value):
 1891     """
 1892     .. versionadded:: 2017.7.0
 1893 
 1894     Convert to an int if necessary.
 1895 
 1896     :param str value: The value to check/convert.
 1897 
 1898     :return: The converted or passed value.
 1899     :rtype: bool|int|str
 1900     """
 1901     try:
 1902         value = int(str(value))  # future lint: disable=blacklisted-function
 1903     except ValueError:
 1904         pass
 1905     return value
 1906 
 1907 
 1908 def get_repo_keys():
 1909     """
 1910     .. versionadded:: 2017.7.0
 1911 
 1912     List known repo key details.
 1913 
 1914     :return: A dictionary containing the repo keys.
 1915     :rtype: dict
 1916 
 1917     CLI Examples:
 1918 
 1919     .. code-block:: bash
 1920 
 1921         salt '*' pkg.get_repo_keys
 1922     """
 1923     ret = dict()
 1924     repo_keys = list()
 1925 
 1926     # The double usage of '--with-fingerprint' is necessary in order to
 1927     # retrieve the fingerprint of the subkey.
 1928     cmd = [
 1929         "apt-key",
 1930         "adv",
 1931         "--batch",
 1932         "--list-public-keys",
 1933         "--with-fingerprint",
 1934         "--with-fingerprint",
 1935         "--with-colons",
 1936         "--fixed-list-mode",
 1937     ]
 1938 
 1939     cmd_ret = _call_apt(cmd, scope=False)
 1940 
 1941     if cmd_ret["retcode"] != 0:
 1942         log.error(cmd_ret["stderr"])
 1943         return ret
 1944 
 1945     lines = [line for line in cmd_ret["stdout"].splitlines() if line.strip()]
 1946 
 1947     # Reference for the meaning of each item in the colon-separated
 1948     # record can be found here: https://goo.gl/KIZbvp
 1949     for line in lines:
 1950         items = [
 1951             _convert_if_int(item.strip()) if item.strip() else None
 1952             for item in line.split(":")
 1953         ]
 1954         key_props = dict()
 1955 
 1956         if len(items) < 2:
 1957             log.debug("Skipping line: %s", line)
 1958             continue
 1959 
 1960         if items[0] in ("pub", "sub"):
 1961             key_props.update(
 1962                 {
 1963                     "algorithm": items[3],
 1964                     "bits": items[2],
 1965                     "capability": items[11],
 1966                     "date_creation": items[5],
 1967                     "date_expiration": items[6],
 1968                     "keyid": items[4],
 1969                     "validity": items[1],
 1970                 }
 1971             )
 1972 
 1973             if items[0] == "pub":
 1974                 repo_keys.append(key_props)
 1975             else:
 1976                 repo_keys[-1]["subkey"] = key_props
 1977         elif items[0] == "fpr":
 1978             if repo_keys[-1].get("subkey", False):
 1979                 repo_keys[-1]["subkey"].update({"fingerprint": items[9]})
 1980             else:
 1981                 repo_keys[-1].update({"fingerprint": items[9]})
 1982         elif items[0] == "uid":
 1983             repo_keys[-1].update({"uid": items[9], "uid_hash": items[7]})
 1984 
 1985     for repo_key in repo_keys:
 1986         ret[repo_key["keyid"]] = repo_key
 1987     return ret
 1988 
 1989 
 1990 def add_repo_key(path=None, text=None, keyserver=None, keyid=None, saltenv="base"):
 1991     """
 1992     .. versionadded:: 2017.7.0
 1993 
 1994     Add a repo key using ``apt-key add``.
 1995 
 1996     :param str path: The path of the key file to import.
 1997     :param str text: The key data to import, in string form.
 1998     :param str keyserver: The server to download the repo key specified by the keyid.
 1999     :param str keyid: The key id of the repo key to add.
 2000     :param str saltenv: The environment the key file resides in.
 2001 
 2002     :return: A boolean representing whether the repo key was added.
 2003     :rtype: bool
 2004 
 2005     CLI Examples:
 2006 
 2007     .. code-block:: bash
 2008 
 2009         salt '*' pkg.add_repo_key 'salt://apt/sources/test.key'
 2010 
 2011         salt '*' pkg.add_repo_key text="'$KEY1'"
 2012 
 2013         salt '*' pkg.add_repo_key keyserver='keyserver.example' keyid='0000AAAA'
 2014     """
 2015     cmd = ["apt-key"]
 2016     kwargs = {}
 2017 
 2018     current_repo_keys = get_repo_keys()
 2019 
 2020     if path:
 2021         cached_source_path = __salt__["cp.cache_file"](path, saltenv)
 2022 
 2023         if not cached_source_path:
 2024             log.error("Unable to get cached copy of file: %s", path)
 2025             return False
 2026 
 2027         cmd.extend(["add", cached_source_path])
 2028     elif text:
 2029         log.debug("Received value: %s", text)
 2030 
 2031         cmd.extend(["add", "-"])
 2032         kwargs.update({"stdin": text})
 2033     elif keyserver:
 2034         if not keyid:
 2035             error_msg = "No keyid or keyid too short for keyserver: {}".format(
 2036                 keyserver
 2037             )
 2038             raise SaltInvocationError(error_msg)
 2039 
 2040         cmd.extend(["adv", "--batch", "--keyserver", keyserver, "--recv", keyid])
 2041     elif keyid:
 2042         error_msg = "No keyserver specified for keyid: {}".format(keyid)
 2043         raise SaltInvocationError(error_msg)
 2044     else:
 2045         raise TypeError(
 2046             "{}() takes at least 1 argument (0 given)".format(add_repo_key.__name__)
 2047         )
 2048 
 2049     # If the keyid is provided or determined, check it against the existing
 2050     # repo key ids to determine whether it needs to be imported.
 2051     if keyid:
 2052         for current_keyid in current_repo_keys:
 2053             if current_keyid[-(len(keyid)) :] == keyid:
 2054                 log.debug("The keyid '%s' already present: %s", keyid, current_keyid)
 2055                 return True
 2056 
 2057     cmd_ret = _call_apt(cmd, **kwargs)
 2058 
 2059     if cmd_ret["retcode"] == 0:
 2060         return True
 2061     log.error("Unable to add repo key: %s", cmd_ret["stderr"])
 2062     return False
 2063 
 2064 
 2065 def del_repo_key(name=None, **kwargs):
 2066     """
 2067     .. versionadded:: 2015.8.0
 2068 
 2069     Remove a repo key using ``apt-key del``
 2070 
 2071     name
 2072         Repo from which to remove the key. Unnecessary if ``keyid`` is passed.
 2073 
 2074     keyid
 2075         The KeyID of the GPG key to remove
 2076 
 2077     keyid_ppa : False
 2078         If set to ``True``, the repo's GPG key ID will be looked up from
 2079         ppa.launchpad.net and removed.
 2080 
 2081         .. note::
 2082 
 2083             Setting this option to ``True`` requires that the ``name`` param
 2084             also be passed.
 2085 
 2086     CLI Examples:
 2087 
 2088     .. code-block:: bash
 2089 
 2090         salt '*' pkg.del_repo_key keyid=0123ABCD
 2091         salt '*' pkg.del_repo_key name='ppa:foo/bar' keyid_ppa=True
 2092     """
 2093     if kwargs.get("keyid_ppa", False):
 2094         if isinstance(name, str) and name.startswith("ppa:"):
 2095             owner_name, ppa_name = name[4:].split("/")
 2096             ppa_info = _get_ppa_info_from_launchpad(owner_name, ppa_name)
 2097             keyid = ppa_info["signing_key_fingerprint"][-8:]
 2098         else:
 2099             raise SaltInvocationError("keyid_ppa requires that a PPA be passed")
 2100     else:
 2101         if "keyid" in kwargs:
 2102             keyid = kwargs.get("keyid")
 2103         else:
 2104             raise SaltInvocationError("keyid or keyid_ppa and PPA name must be passed")
 2105 
 2106     result = _call_apt(["apt-key", "del", keyid], scope=False)
 2107     if result["retcode"] != 0:
 2108         msg = "Failed to remove keyid {0}"
 2109         if result["stderr"]:
 2110             msg += ": {}".format(result["stderr"])
 2111         raise CommandExecutionError(msg)
 2112     return keyid
 2113 
 2114 
 2115 def mod_repo(repo, saltenv="base", **kwargs):
 2116     """
 2117     Modify one or more values for a repo.  If the repo does not exist, it will
 2118     be created, so long as the definition is well formed.  For Ubuntu the
 2119     ``ppa:<project>/repo`` format is acceptable. ``ppa:`` format can only be
 2120     used to create a new repository.
 2121 
 2122     The following options are available to modify a repo definition:
 2123 
 2124     architectures
 2125         A comma-separated list of supported architectures, e.g. ``amd64`` If
 2126         this option is not set, all architectures (configured in the system)
 2127         will be used.
 2128 
 2129     comps
 2130         A comma separated list of components for the repo, e.g. ``main``
 2131 
 2132     file
 2133         A file name to be used
 2134 
 2135     keyserver
 2136         Keyserver to get gpg key from
 2137 
 2138     keyid
 2139         Key ID or a list of key IDs to load with the ``keyserver`` argument
 2140 
 2141     key_url
 2142         URL to a GPG key to add to the APT GPG keyring
 2143 
 2144     key_text
 2145         GPG key in string form to add to the APT GPG keyring
 2146 
 2147         .. versionadded:: 2018.3.0
 2148 
 2149     consolidate : False
 2150         If ``True``, will attempt to de-duplicate and consolidate sources
 2151 
 2152     comments
 2153         Sometimes you want to supply additional information, but not as
 2154         enabled configuration. All comments provided here will be joined
 2155         into a single string and appended to the repo configuration with a
 2156         comment marker (#) before it.
 2157 
 2158         .. versionadded:: 2015.8.9
 2159 
 2160     refresh : True
 2161         Enable or disable (True or False) refreshing of the apt package
 2162         database. The previous ``refresh_db`` argument was deprecated in
 2163         favor of ``refresh```. The ``refresh_db`` argument will still
 2164         continue to work to ensure backwards compatibility, but please
 2165         change to using the preferred ``refresh``.
 2166 
 2167     .. note::
 2168         Due to the way keys are stored for APT, there is a known issue where
 2169         the key won't be updated unless another change is made at the same
 2170         time. Keys should be properly added on initial configuration.
 2171 
 2172     CLI Examples:
 2173 
 2174     .. code-block:: bash
 2175 
 2176         salt '*' pkg.mod_repo 'myrepo definition' uri=http://new/uri
 2177         salt '*' pkg.mod_repo 'myrepo definition' comps=main,universe
 2178     """
 2179     if "refresh_db" in kwargs:
 2180         refresh = kwargs["refresh_db"]
 2181     else:
 2182         refresh = kwargs.get("refresh", True)
 2183 
 2184     _check_apt()
 2185     # to ensure no one sets some key values that _shouldn't_ be changed on the
 2186     # object itself, this is just a white-list of "ok" to set properties
 2187     if repo.startswith("ppa:"):
 2188         if __grains__["os"] in ("Ubuntu", "Mint", "neon"):
 2189             # secure PPAs cannot be supported as of the time of this code
 2190             # implementation via apt-add-repository.  The code path for
 2191             # secure PPAs should be the same as urllib method
 2192             if salt.utils.path.which("apt-add-repository") and "ppa_auth" not in kwargs:
 2193                 repo_info = get_repo(repo)
 2194                 if repo_info:
 2195                     return {repo: repo_info}
 2196                 else:
 2197                     env = None
 2198                     http_proxy_url = _get_http_proxy_url()
 2199                     if http_proxy_url:
 2200                         env = {
 2201                             "http_proxy": http_proxy_url,
 2202                             "https_proxy": http_proxy_url,
 2203                         }
 2204                     if float(__grains__["osrelease"]) < 12.04:
 2205                         cmd = ["apt-add-repository", repo]
 2206                     else:
 2207                         cmd = ["apt-add-repository", "-y", repo]
 2208                     out = _call_apt(cmd, env=env, scope=False, **kwargs)
 2209                     if out["retcode"]:
 2210                         raise CommandExecutionError(
 2211                             "Unable to add PPA '{}'. '{}' exited with "
 2212                             "status {!s}: '{}' ".format(
 2213                                 repo[4:], cmd, out["retcode"], out["stderr"]
 2214                             )
 2215                         )
 2216                     # explicit refresh when a repo is modified.
 2217                     if refresh:
 2218                         refresh_db()
 2219                     return {repo: out}
 2220             else:
 2221                 if not HAS_SOFTWAREPROPERTIES:
 2222                     _warn_software_properties(repo)
 2223                 else:
 2224                     log.info("Falling back to urllib method for private PPA")
 2225 
 2226                 # fall back to urllib style
 2227                 try:
 2228                     owner_name, ppa_name = repo[4:].split("/", 1)
 2229                 except ValueError:
 2230                     raise CommandExecutionError(
 2231                         "Unable to get PPA info from argument. "
 2232                         'Expected format "<PPA_OWNER>/<PPA_NAME>" '
 2233                         "(e.g. saltstack/salt) not found.  Received "
 2234                         "'{}' instead.".format(repo[4:])
 2235                     )
 2236                 dist = __grains__["lsb_distrib_codename"]
 2237                 # ppa has a lot of implicit arguments. Make them explicit.
 2238                 # These will defer to any user-defined variants
 2239                 kwargs["dist"] = dist
 2240                 ppa_auth = ""
 2241                 if "file" not in kwargs:
 2242                     filename = "/etc/apt/sources.list.d/{0}-{1}-{2}.list"
 2243                     kwargs["file"] = filename.format(owner_name, ppa_name, dist)
 2244                 try:
 2245                     launchpad_ppa_info = _get_ppa_info_from_launchpad(
 2246                         owner_name, ppa_name
 2247                     )
 2248                     if "ppa_auth" not in kwargs:
 2249                         kwargs["keyid"] = launchpad_ppa_info["signing_key_fingerprint"]
 2250                     else:
 2251                         if "keyid" not in kwargs:
 2252                             error_str = (
 2253                                 "Private PPAs require a "
 2254                                 "keyid to be specified: {0}/{1}"
 2255                             )
 2256                             raise CommandExecutionError(
 2257                                 error_str.format(owner_name, ppa_name)
 2258                             )
 2259                 except HTTPError as exc:
 2260                     raise CommandExecutionError(
 2261                         "Launchpad does not know about {}/{}: {}".format(
 2262                             owner_name, ppa_name, exc
 2263                         )
 2264                     )
 2265                 except IndexError as exc:
 2266                     raise CommandExecutionError(
 2267                         "Launchpad knows about {}/{} but did not "
 2268                         "return a fingerprint. Please set keyid "
 2269                         "manually: {}".format(owner_name, ppa_name, exc)
 2270                     )
 2271 
 2272                 if "keyserver" not in kwargs:
 2273                     kwargs["keyserver"] = "keyserver.ubuntu.com"
 2274                 if "ppa_auth" in kwargs:
 2275                     if not launchpad_ppa_info["private"]:
 2276                         raise CommandExecutionError(
 2277                             "PPA is not private but auth credentials "
 2278                             "passed: {}".format(repo)
 2279                         )
 2280                 # assign the new repo format to the "repo" variable
 2281                 # so we can fall through to the "normal" mechanism
 2282                 # here.
 2283                 if "ppa_auth" in kwargs:
 2284                     ppa_auth = "{}@".format(kwargs["ppa_auth"])
 2285                     repo = LP_PVT_SRC_FORMAT.format(
 2286                         ppa_auth, owner_name, ppa_name, dist
 2287                     )
 2288                 else:
 2289                     repo = LP_SRC_FORMAT.format(owner_name, ppa_name, dist)
 2290         else:
 2291             raise CommandExecutionError(
 2292                 'cannot parse "ppa:" style repo definitions: {}'.format(repo)
 2293             )
 2294 
 2295     sources = sourceslist.SourcesList()
 2296     if kwargs.get("consolidate", False):
 2297         # attempt to de-dup and consolidate all sources
 2298         # down to entries in sources.list
 2299         # this option makes it easier to keep the sources
 2300         # list in a "sane" state.
 2301         #
 2302         # this should remove duplicates, consolidate comps
 2303         # for a given source down to one line
 2304         # and eliminate "invalid" and comment lines
 2305         #
 2306         # the second side effect is removal of files
 2307         # that are not the main sources.list file
 2308         sources = _consolidate_repo_sources(sources)
 2309 
 2310     repos = [s for s in sources if not s.invalid]
 2311     mod_source = None
 2312     try:
 2313         (
 2314             repo_type,
 2315             repo_architectures,
 2316             repo_uri,
 2317             repo_dist,
 2318             repo_comps,
 2319         ) = _split_repo_str(repo)
 2320     except SyntaxError:
 2321         raise SyntaxError(
 2322             "Error: repo '{}' not a well formatted definition".format(repo)
 2323         )
 2324 
 2325     full_comp_list = {comp.strip() for comp in repo_comps}
 2326     no_proxy = __salt__["config.option"]("no_proxy")
 2327 
 2328     if "keyid" in kwargs:
 2329         keyid = kwargs.pop("keyid", None)
 2330         keyserver = kwargs.pop("keyserver", None)
 2331         if not keyid or not keyserver:
 2332             error_str = "both keyserver and keyid options required."
 2333             raise NameError(error_str)
 2334         if not isinstance(keyid, list):
 2335             keyid = [keyid]
 2336 
 2337         for key in keyid:
 2338             if isinstance(
 2339                 key, int
 2340             ):  # yaml can make this an int, we need the hex version
 2341                 key = hex(key)
 2342             cmd = ["apt-key", "export", key]
 2343             output = __salt__["cmd.run_stdout"](cmd, python_shell=False, **kwargs)
 2344             imported = output.startswith("-----BEGIN PGP")
 2345             if keyserver:
 2346                 if not imported:
 2347                     http_proxy_url = _get_http_proxy_url()
 2348                     if http_proxy_url and keyserver not in no_proxy:
 2349                         cmd = [
 2350                             "apt-key",
 2351                             "adv",
 2352                             "--batch",
 2353                             "--keyserver-options",
 2354                             "http-proxy={}".format(http_proxy_url),
 2355                             "--keyserver",
 2356                             keyserver,
 2357                             "--logger-fd",
 2358                             "1",
 2359                             "--recv-keys",
 2360                             key,
 2361                         ]
 2362                     else:
 2363                         cmd = [
 2364                             "apt-key",
 2365                             "adv",
 2366                             "--batch",
 2367                             "--keyserver",
 2368                             keyserver,
 2369                             "--logger-fd",
 2370                             "1",
 2371                             "--recv-keys",
 2372                             key,
 2373                         ]
 2374                     ret = _call_apt(cmd, scope=False, **kwargs)
 2375                     if ret["retcode"] != 0:
 2376                         raise CommandExecutionError(
 2377                             "Error: key retrieval failed: {}".format(ret["stdout"])
 2378                         )
 2379 
 2380     elif "key_url" in kwargs:
 2381         key_url = kwargs["key_url"]
 2382         fn_ = __salt__["cp.cache_file"](key_url, saltenv)
 2383         if not fn_:
 2384             raise CommandExecutionError("Error: file not found: {}".format(key_url))
 2385         cmd = ["apt-key", "add", fn_]
 2386         out = __salt__["cmd.run_stdout"](cmd, python_shell=False, **kwargs)
 2387         if not out.upper().startswith("OK"):
 2388             raise CommandExecutionError(
 2389                 "Error: failed to add key from {}".format(key_url)
 2390             )
 2391 
 2392     elif "key_text" in kwargs:
 2393         key_text = kwargs["key_text"]
 2394         cmd = ["apt-key", "add", "-"]
 2395         out = __salt__["cmd.run_stdout"](
 2396             cmd, stdin=key_text, python_shell=False, **kwargs
 2397         )
 2398         if not out.upper().startswith("OK"):
 2399             raise CommandExecutionError(
 2400                 "Error: failed to add key:\n{}".format(key_text)
 2401             )
 2402 
 2403     if "comps" in kwargs:
 2404         kwargs["comps"] = [comp.strip() for comp in kwargs["comps"].split(",")]
 2405         full_comp_list |= set(kwargs["comps"])
 2406     else:
 2407         kwargs["comps"] = list(full_comp_list)
 2408 
 2409     if "architectures" in kwargs:
 2410         kwargs["architectures"] = kwargs["architectures"].split(",")
 2411     else:
 2412         kwargs["architectures"] = repo_architectures
 2413 
 2414     if "disabled" in kwargs:
 2415         kwargs["disabled"] = salt.utils.data.is_true(kwargs["disabled"])
 2416     elif "enabled" in kwargs:
 2417         kwargs["disabled"] = not salt.utils.data.is_true(kwargs["enabled"])
 2418 
 2419     kw_type = kwargs.get("type")
 2420     kw_dist = kwargs.get("dist")
 2421 
 2422     for source in repos:
 2423         # This series of checks will identify the starting source line
 2424         # and the resulting source line.  The idea here is to ensure
 2425         # we are not returning bogus data because the source line
 2426         # has already been modified on a previous run.
 2427         repo_matches = (
 2428             source.type == repo_type
 2429             and source.uri == repo_uri
 2430             and source.dist == repo_dist
 2431         )
 2432         kw_matches = source.dist == kw_dist and source.type == kw_type
 2433 
 2434         if repo_matches or kw_matches:
 2435             for comp in full_comp_list:
 2436                 if comp in getattr(source, "comps", []):
 2437                     mod_source = source
 2438             if not source.comps:
 2439                 mod_source = source
 2440             if kwargs["architectures"] != source.architectures:
 2441                 mod_source = source
 2442             if mod_source:
 2443                 break
 2444 
 2445     if "comments" in kwargs:
 2446         kwargs["comments"] = salt.utils.pkg.deb.combine_comments(kwargs["comments"])
 2447 
 2448     if not mod_source:
 2449         mod_source = sourceslist.SourceEntry(repo)
 2450         if "comments" in kwargs:
 2451             mod_source.comment = kwargs["comments"]
 2452         sources.list.append(mod_source)
 2453     elif "comments" in kwargs:
 2454         mod_source.comment = kwargs["comments"]
 2455 
 2456     for key in kwargs:
 2457         if key in _MODIFY_OK and hasattr(mod_source, key):
 2458             setattr(mod_source, key, kwargs[key])
 2459     sources.save()
 2460     # on changes, explicitly refresh
 2461     if refresh:
 2462         refresh_db()
 2463     return {
 2464         repo: {
 2465             "architectures": getattr(mod_source, "architectures", []),
 2466             "comps": mod_source.comps,
 2467             "disabled": mod_source.disabled,
 2468             "file": mod_source.file,
 2469             "type": mod_source.type,
 2470             "uri": mod_source.uri,
 2471             "line": mod_source.line,
 2472         }
 2473     }
 2474 
 2475 
 2476 def file_list(*packages, **kwargs):
 2477     """
 2478     List the files that belong to a package. Not specifying any packages will
 2479     return a list of _every_ file on the system's package database (not
 2480     generally recommended).
 2481 
 2482     CLI Examples:
 2483 
 2484     .. code-block:: bash
 2485 
 2486         salt '*' pkg.file_list httpd
 2487         salt '*' pkg.file_list httpd postfix
 2488         salt '*' pkg.file_list
 2489     """
 2490     return __salt__["lowpkg.file_list"](*packages)
 2491 
 2492 
 2493 def file_dict(*packages, **kwargs):
 2494     """
 2495     List the files that belong to a package, grouped by package. Not
 2496     specifying any packages will return a list of _every_ file on the system's
 2497     package database (not generally recommended).
 2498 
 2499     CLI Examples:
 2500 
 2501     .. code-block:: bash
 2502 
 2503         salt '*' pkg.file_dict httpd
 2504         salt '*' pkg.file_dict httpd postfix
 2505         salt '*' pkg.file_dict
 2506     """
 2507     return __salt__["lowpkg.file_dict"](*packages)
 2508 
 2509 
 2510 def expand_repo_def(**kwargs):
 2511     """
 2512     Take a repository definition and expand it to the full pkg repository dict
 2513     that can be used for comparison.  This is a helper function to make
 2514     the Debian/Ubuntu apt sources sane for comparison in the pkgrepo states.
 2515 
 2516     This is designed to be called from pkgrepo states and will have little use
 2517     being called on the CLI.
 2518     """
 2519     if "repo" not in kwargs:
 2520         raise SaltInvocationError("missing 'repo' argument")
 2521 
 2522     _check_apt()
 2523 
 2524     sanitized = {}
 2525     repo = kwargs["repo"]
 2526     if repo.startswith("ppa:") and __grains__["os"] in ("Ubuntu", "Mint", "neon"):
 2527         dist = __grains__["lsb_distrib_codename"]
 2528         owner_name, ppa_name = repo[4:].split("/", 1)
 2529         if "ppa_auth" in kwargs:
 2530             auth_info = "{}@".format(kwargs["ppa_auth"])
 2531             repo = LP_PVT_SRC_FORMAT.format(auth_info, owner_name, ppa_name, dist)
 2532         else:
 2533             if HAS_SOFTWAREPROPERTIES:
 2534                 if hasattr(softwareproperties.ppa, "PPAShortcutHandler"):
 2535                     repo = softwareproperties.ppa.PPAShortcutHandler(repo).expand(dist)[
 2536                         0
 2537                     ]
 2538                 else:
 2539                     repo = softwareproperties.ppa.expand_ppa_line(repo, dist)[0]
 2540             else:
 2541                 repo = LP_SRC_FORMAT.format(owner_name, ppa_name, dist)
 2542 
 2543         if "file" not in kwargs:
 2544             filename = "/etc/apt/sources.list.d/{0}-{1}-{2}.list"
 2545             kwargs["file"] = filename.format(owner_name, ppa_name, dist)
 2546 
 2547     source_entry = sourceslist.SourceEntry(repo)
 2548     for list_args in ("architectures", "comps"):
 2549         if list_args in kwargs:
 2550             kwargs[list_args] = [
 2551                 kwarg.strip() for kwarg in kwargs[list_args].split(",")
 2552             ]
 2553     for kwarg in _MODIFY_OK:
 2554         if kwarg in kwargs:
 2555             setattr(source_entry, kwarg, kwargs[kwarg])
 2556 
 2557     sanitized["file"] = source_entry.file
 2558     sanitized["comps"] = getattr(source_entry, "comps", [])
 2559     sanitized["disabled"] = source_entry.disabled
 2560     sanitized["dist"] = source_entry.dist
 2561     sanitized["type"] = source_entry.type
 2562     sanitized["uri"] = source_entry.uri
 2563     sanitized["line"] = source_entry.line.strip()
 2564     sanitized["architectures"] = getattr(source_entry, "architectures", [])
 2565 
 2566     return sanitized
 2567 
 2568 
 2569 def _parse_selections(dpkgselection):
 2570     """
 2571     Parses the format from ``dpkg --get-selections`` and return a format that
 2572     pkg.get_selections and pkg.set_selections work with.
 2573     """
 2574     ret = {}
 2575     if isinstance(dpkgselection, str):
 2576         dpkgselection = dpkgselection.split("\n")
 2577     for line in dpkgselection:
 2578         if line:
 2579             _pkg, _state = line.split()
 2580             if _state in ret:
 2581                 ret[_state].append(_pkg)
 2582             else:
 2583                 ret[_state] = [_pkg]
 2584     return ret
 2585 
 2586 
 2587 def get_selections(pattern=None, state=None):
 2588     """
 2589     View package state from the dpkg database.
 2590 
 2591     Returns a dict of dicts containing the state, and package names:
 2592 
 2593     .. code-block:: python
 2594 
 2595         {'<host>':
 2596             {'<state>': ['pkg1',
 2597                          ...
 2598                         ]
 2599             },
 2600             ...
 2601         }
 2602 
 2603     CLI Example:
 2604 
 2605     .. code-block:: bash
 2606 
 2607         salt '*' pkg.get_selections
 2608         salt '*' pkg.get_selections 'python-*'
 2609         salt '*' pkg.get_selections state=hold
 2610         salt '*' pkg.get_selections 'openssh*' state=hold
 2611     """
 2612     ret = {}
 2613     cmd = ["dpkg", "--get-selections"]
 2614     cmd.append(pattern if pattern else "*")
 2615     stdout = __salt__["cmd.run_stdout"](
 2616         cmd, output_loglevel="trace", python_shell=False
 2617     )
 2618     ret = _parse_selections(stdout)
 2619     if state:
 2620         return {state: ret.get(state, [])}
 2621     return ret
 2622 
 2623 
 2624 # TODO: allow state=None to be set, and that *args will be set to that state
 2625 # TODO: maybe use something similar to pkg_resources.pack_pkgs to allow a list
 2626 # passed to selection, with the default state set to whatever is passed by the
 2627 # above, but override that if explicitly specified
 2628 # TODO: handle path to selection file from local fs as well as from salt file
 2629 # server
 2630 def set_selections(path=None, selection=None, clear=False, saltenv="base"):
 2631     """
 2632     Change package state in the dpkg database.
 2633 
 2634     The state can be any one of, documented in ``dpkg(1)``:
 2635 
 2636     - install
 2637     - hold
 2638     - deinstall
 2639     - purge
 2640 
 2641     This command is commonly used to mark specific packages to be held from
 2642     being upgraded, that is, to be kept at a certain version. When a state is
 2643     changed to anything but being held, then it is typically followed by
 2644     ``apt-get -u dselect-upgrade``.
 2645 
 2646     Note: Be careful with the ``clear`` argument, since it will start
 2647     with setting all packages to deinstall state.
 2648 
 2649     Returns a dict of dicts containing the package names, and the new and old
 2650     versions:
 2651 
 2652     .. code-block:: python
 2653 
 2654         {'<host>':
 2655             {'<package>': {'new': '<new-state>',
 2656                            'old': '<old-state>'}
 2657             },
 2658             ...
 2659         }
 2660 
 2661     CLI Example:
 2662 
 2663     .. code-block:: bash
 2664 
 2665         salt '*' pkg.set_selections selection='{"install": ["netcat"]}'
 2666         salt '*' pkg.set_selections selection='{"hold": ["openssh-server", "openssh-client"]}'
 2667         salt '*' pkg.set_selections salt://path/to/file
 2668         salt '*' pkg.set_selections salt://path/to/file clear=True
 2669     """
 2670     ret = {}
 2671     if not path and not selection:
 2672         return ret
 2673     if path and selection:
 2674         err = (
 2675             "The 'selection' and 'path' arguments to "
 2676             "pkg.set_selections are mutually exclusive, and cannot be "
 2677             "specified together"
 2678         )
 2679         raise SaltInvocationError(err)
 2680 
 2681     if isinstance(selection, str):
 2682         try:
 2683             selection = salt.utils.yaml.safe_load(selection)
 2684         except (
 2685             salt.utils.yaml.parser.ParserError,
 2686             salt.utils.yaml.scanner.ScannerError,
 2687         ) as exc:
 2688             raise SaltInvocationError("Improperly-formatted selection: {}".format(exc))
 2689 
 2690     if path:
 2691         path = __salt__["cp.cache_file"](path, saltenv)
 2692         with salt.utils.files.fopen(path, "r") as ifile:
 2693             content = [salt.utils.stringutils.to_unicode(x) for x in ifile.readlines()]
 2694         selection = _parse_selections(content)
 2695 
 2696     if selection:
 2697         valid_states = ("install", "hold", "deinstall", "purge")
 2698         bad_states = [x for x in selection if x not in valid_states]
 2699         if bad_states:
 2700             raise SaltInvocationError(
 2701                 "Invalid state(s): {}".format(", ".join(bad_states))
 2702             )
 2703 
 2704         if clear:
 2705             cmd = ["dpkg", "--clear-selections"]
 2706             if not __opts__["test"]:
 2707                 result = _call_apt(cmd, scope=False)
 2708                 if result["retcode"] != 0:
 2709                     err = "Running dpkg --clear-selections failed: " "{}".format(
 2710                         result["stderr"]
 2711                     )
 2712                     log.error(err)
 2713                     raise CommandExecutionError(err)
 2714 
 2715         sel_revmap = {}
 2716         for _state, _pkgs in get_selections().items():
 2717             sel_revmap.update({_pkg: _state for _pkg in _pkgs})
 2718 
 2719         for _state, _pkgs in selection.items():
 2720             for _pkg in _pkgs:
 2721                 if _state == sel_revmap.get(_pkg):
 2722                     continue
 2723                 cmd = ["dpkg", "--set-selections"]
 2724                 cmd_in = "{} {}".format(_pkg, _state)
 2725                 if not __opts__["test"]:
 2726                     result = _call_apt(cmd, scope=False, stdin=cmd_in)
 2727                     if result["retcode"] != 0:
 2728                         log.error("failed to set state %s for package %s", _state, _pkg)
 2729                     else:
 2730                         ret[_pkg] = {"old": sel_revmap.get(_pkg), "new": _state}
 2731     return ret
 2732 
 2733 
 2734 def _resolve_deps(name, pkgs, **kwargs):
 2735     """
 2736     Installs missing dependencies and marks them as auto installed so they
 2737     are removed when no more manually installed packages depend on them.
 2738 
 2739     .. versionadded:: 2014.7.0
 2740 
 2741     :depends:   - python-apt module
 2742     """
 2743     missing_deps = []
 2744     for pkg_file in pkgs:
 2745         deb = apt.debfile.DebPackage(filename=pkg_file, cache=apt.Cache())
 2746         if deb.check():
 2747             missing_deps.extend(deb.missing_deps)
 2748 
 2749     if missing_deps:
 2750         cmd = ["apt-get", "-q", "-y"]
 2751         cmd = cmd + ["-o", "DPkg::Options::=--force-confold"]
 2752         cmd = cmd + ["-o", "DPkg::Options::=--force-confdef"]
 2753         cmd.append("install")
 2754         cmd.extend(missing_deps)
 2755 
 2756         ret = __salt__["cmd.retcode"](cmd, env=kwargs.get("env"), python_shell=False)
 2757 
 2758         if ret != 0:
 2759             raise CommandExecutionError(
 2760                 "Error: unable to resolve dependencies for: {}".format(name)
 2761             )
 2762         else:
 2763             try:
 2764                 cmd = ["apt-mark", "auto"] + missing_deps
 2765                 __salt__["cmd.run"](cmd, env=kwargs.get("env"), python_shell=False)
 2766             except MinionError as exc:
 2767                 raise CommandExecutionError(exc)
 2768     return
 2769 
 2770 
 2771 def owner(*paths, **kwargs):
 2772     """
 2773     .. versionadded:: 2014.7.0
 2774 
 2775     Return the name of the package that owns the file. Multiple file paths can
 2776     be passed. Like :mod:`pkg.version <salt.modules.aptpkg.version>`, if a
 2777     single path is passed, a string will be returned, and if multiple paths are
 2778     passed, a dictionary of file/package name pairs will be returned.
 2779 
 2780     If the file is not owned by a package, or is not present on the minion,
 2781     then an empty string will be returned for that path.
 2782 
 2783     CLI Example:
 2784 
 2785     .. code-block:: bash
 2786 
 2787         salt '*' pkg.owner /usr/bin/apachectl
 2788         salt '*' pkg.owner /usr/bin/apachectl /usr/bin/basename
 2789     """
 2790     if not paths:
 2791         return ""
 2792     ret = {}
 2793     for path in paths:
 2794         cmd = ["dpkg", "-S", path]
 2795         output = __salt__["cmd.run_stdout"](
 2796             cmd, output_loglevel="trace", python_shell=False
 2797         )
 2798         ret[path] = output.split(":")[0]
 2799         if "no path found" in ret[path].lower():
 2800             ret[path] = ""
 2801     if len(ret) == 1:
 2802         return next(iter(ret.values()))
 2803     return ret
 2804 
 2805 
 2806 def show(*names, **kwargs):
 2807     """
 2808     .. versionadded:: 2019.2.0
 2809 
 2810     Runs an ``apt-cache show`` on the passed package names, and returns the
 2811     results in a nested dictionary. The top level of the return data will be
 2812     the package name, with each package name mapping to a dictionary of version
 2813     numbers to any additional information returned by ``apt-cache show``.
 2814 
 2815     filter
 2816         An optional comma-separated list (or quoted Python list) of
 2817         case-insensitive keys on which to filter. This allows one to restrict
 2818         the information returned for each package to a smaller selection of
 2819         pertinent items.
 2820 
 2821     refresh : False
 2822         If ``True``, the apt cache will be refreshed first. By default, no
 2823         refresh is performed.
 2824 
 2825 
 2826     CLI Examples:
 2827 
 2828     .. code-block:: bash
 2829 
 2830         salt myminion pkg.show gawk
 2831         salt myminion pkg.show 'nginx-*'
 2832         salt myminion pkg.show 'nginx-*' filter=description,provides
 2833     """
 2834     kwargs = salt.utils.args.clean_kwargs(**kwargs)
 2835     refresh = kwargs.pop("refresh", False)
 2836     filter_ = salt.utils.args.split_input(
 2837         kwargs.pop("filter", []),
 2838         lambda x: str(x) if not isinstance(x, str) else x.lower(),
 2839     )
 2840     if kwargs:
 2841         salt.utils.args.invalid_kwargs(kwargs)
 2842 
 2843     if refresh:
 2844         refresh_db()
 2845 
 2846     if not names:
 2847         return {}
 2848 
 2849     result = _call_apt(["apt-cache", "show"] + list(names), scope=False)
 2850 
 2851     def _add(ret, pkginfo):
 2852         name = pkginfo.pop("Package", None)
 2853         version = pkginfo.pop("Version", None)
 2854         if name is not None and version is not None:
 2855             ret.setdefault(name, {}).setdefault(version, {}).update(pkginfo)
 2856 
 2857     def _check_filter(key):
 2858         key = key.lower()
 2859         return True if key in ("package", "version") or not filter_ else key in filter_
 2860 
 2861     ret = {}
 2862     pkginfo = {}
 2863     for line in salt.utils.itertools.split(result["stdout"], "\n"):
 2864         line = line.strip()
 2865         if line:
 2866             try:
 2867                 key, val = [x.strip() for x in line.split(":", 1)]
 2868             except ValueError:
 2869                 pass
 2870             else:
 2871                 if _check_filter(key):
 2872                     pkginfo[key] = val
 2873         else:
 2874             # We've reached a blank line, which separates packages
 2875             _add(ret, pkginfo)
 2876             # Clear out the pkginfo dict for the next package
 2877             pkginfo = {}
 2878             continue
 2879 
 2880     # Make sure to add whatever was in the pkginfo dict when we reached the end
 2881     # of the output.
 2882     _add(ret, pkginfo)
 2883 
 2884     return ret
 2885 
 2886 
 2887 def info_installed(*names, **kwargs):
 2888     """
 2889     Return the information of the named package(s) installed on the system.
 2890 
 2891     .. versionadded:: 2015.8.1
 2892 
 2893     names
 2894         The names of the packages for which to return information.
 2895 
 2896     failhard
 2897         Whether to throw an exception if none of the packages are installed.
 2898         Defaults to True.
 2899 
 2900         .. versionadded:: 2016.11.3
 2901 
 2902     CLI example:
 2903 
 2904     .. code-block:: bash
 2905 
 2906         salt '*' pkg.info_installed <package1>
 2907         salt '*' pkg.info_installed <package1> <package2> <package3> ...
 2908         salt '*' pkg.info_installed <package1> failhard=false
 2909     """
 2910     kwargs = salt.utils.args.clean_kwargs(**kwargs)
 2911     failhard = kwargs.pop("failhard", True)
 2912     if kwargs:
 2913         salt.utils.args.invalid_kwargs(kwargs)
 2914 
 2915     ret = dict()
 2916     for pkg_name, pkg_nfo in __salt__["lowpkg.info"](*names, failhard=failhard).items():
 2917         t_nfo = dict()
 2918         if pkg_nfo.get("status", "ii")[1] != "i":
 2919             continue  # return only packages that are really installed
 2920         # Translate dpkg-specific keys to a common structure
 2921         for key, value in pkg_nfo.items():
 2922             if key == "package":
 2923                 t_nfo["name"] = value
 2924             elif key == "origin":
 2925                 t_nfo["vendor"] = value
 2926             elif key == "section":
 2927                 t_nfo["group"] = value
 2928             elif key == "maintainer":
 2929                 t_nfo["packager"] = value
 2930             elif key == "homepage":
 2931                 t_nfo["url"] = value
 2932             elif key == "status":
 2933                 continue  # only installed pkgs are returned, no need for status
 2934             else:
 2935                 t_nfo[key] = value
 2936 
 2937         ret[pkg_name] = t_nfo
 2938 
 2939     return ret
 2940 
 2941 
 2942 def _get_http_proxy_url():
 2943     """
 2944     Returns the http_proxy_url if proxy_username, proxy_password, proxy_host, and proxy_port
 2945     config values are set.
 2946 
 2947     Returns a string.
 2948     """
 2949     http_proxy_url = ""
 2950     host = __salt__["config.option"]("proxy_host")
 2951     port = __salt__["config.option"]("proxy_port")
 2952     username = __salt__["config.option"]("proxy_username")
 2953     password = __salt__["config.option"]("proxy_password")
 2954 
 2955     # Set http_proxy_url for use in various internet facing actions...eg apt-key adv
 2956     if host and port:
 2957         if username and password:
 2958             http_proxy_url = "http://{}:{}@{}:{}".format(username, password, host, port)
 2959         else:
 2960             http_proxy_url = "http://{}:{}".format(host, port)
 2961 
 2962     return http_proxy_url
 2963 
 2964 
 2965 def list_downloaded(root=None, **kwargs):
 2966     """
 2967     .. versionadded:: 3000?
 2968 
 2969     List prefetched packages downloaded by apt in the local disk.
 2970 
 2971     root
 2972         operate on a different root directory.
 2973 
 2974     CLI example:
 2975 
 2976     .. code-block:: bash
 2977 
 2978         salt '*' pkg.list_downloaded
 2979     """
 2980     CACHE_DIR = "/var/cache/apt"
 2981     if root:
 2982         CACHE_DIR = os.path.join(root, os.path.relpath(CACHE_DIR, os.path.sep))
 2983 
 2984     ret = {}
 2985     for root, dirnames, filenames in salt.utils.path.os_walk(CACHE_DIR):
 2986         for filename in fnmatch.filter(filenames, "*.deb"):
 2987             package_path = os.path.join(root, filename)
 2988             pkg_info = __salt__["lowpkg.bin_pkg_info"](package_path)
 2989             pkg_timestamp = int(os.path.getctime(package_path))
 2990             ret.setdefault(pkg_info["name"], {})[pkg_info["version"]] = {
 2991                 "path": package_path,
 2992                 "size": os.path.getsize(package_path),
 2993                 "creation_date_time_t": pkg_timestamp,
 2994                 "creation_date_time": datetime.datetime.utcfromtimestamp(
 2995                     pkg_timestamp
 2996                 ).isoformat(),
 2997             }
 2998     return ret