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