1""" 2Support for Opkg 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.. versionadded:: 2016.3.0 11 12.. note:: 13 14 For version comparison support on opkg < 0.3.4, the ``opkg-utils`` package 15 must be installed. 16 17""" 18 19import copy 20import errno 21import logging 22import os 23import pathlib 24import re 25import shlex 26 27import salt.utils.args 28import salt.utils.data 29import salt.utils.files 30import salt.utils.itertools 31import salt.utils.path 32import salt.utils.pkg 33import salt.utils.stringutils 34import salt.utils.versions 35from salt.exceptions import CommandExecutionError, MinionError, SaltInvocationError 36 37REPO_REGEXP = r'^#?\s*(src|src/gz)\s+([^\s<>]+|"[^<>]+")\s+[^\s<>]+' 38OPKG_CONFDIR = "/etc/opkg" 39ATTR_MAP = { 40 "Architecture": "arch", 41 "Homepage": "url", 42 "Installed-Time": "install_date_time_t", 43 "Maintainer": "packager", 44 "Package": "name", 45 "Section": "group", 46} 47 48log = logging.getLogger(__name__) 49 50# Define the module's virtual name 51__virtualname__ = "pkg" 52 53NILRT_RESTARTCHECK_STATE_PATH = "/var/lib/salt/restartcheck_state" 54 55 56def _get_nisysapi_conf_d_path(): 57 return "/usr/lib/{}/nisysapi/conf.d/experts/".format( 58 "arm-linux-gnueabi" 59 if "arm" in __grains__.get("cpuarch") 60 else "x86_64-linux-gnu" 61 ) 62 63 64def _update_nilrt_restart_state(): 65 """ 66 NILRT systems determine whether to reboot after various package operations 67 including but not limited to kernel module installs/removals by checking 68 specific file md5sums & timestamps. These files are touched/modified by 69 the post-install/post-remove functions of their respective packages. 70 71 The opkg module uses this function to store/update those file timestamps 72 and checksums to be used later by the restartcheck module. 73 74 """ 75 # TODO: This stat & md5sum should be replaced with _fingerprint_file call -W. Werner, 2020-08-18 76 uname = __salt__["cmd.run_stdout"]("uname -r") 77 __salt__["cmd.shell"]( 78 "stat -c %Y /lib/modules/{}/modules.dep >{}/modules.dep.timestamp".format( 79 uname, NILRT_RESTARTCHECK_STATE_PATH 80 ) 81 ) 82 __salt__["cmd.shell"]( 83 "md5sum /lib/modules/{}/modules.dep >{}/modules.dep.md5sum".format( 84 uname, NILRT_RESTARTCHECK_STATE_PATH 85 ) 86 ) 87 88 # We can't assume nisysapi.ini always exists like modules.dep 89 nisysapi_path = "/usr/local/natinst/share/nisysapi.ini" 90 if os.path.exists(nisysapi_path): 91 # TODO: This stat & md5sum should be replaced with _fingerprint_file call -W. Werner, 2020-08-18 92 __salt__["cmd.shell"]( 93 "stat -c %Y {} >{}/nisysapi.ini.timestamp".format( 94 nisysapi_path, NILRT_RESTARTCHECK_STATE_PATH 95 ) 96 ) 97 __salt__["cmd.shell"]( 98 "md5sum {} >{}/nisysapi.ini.md5sum".format( 99 nisysapi_path, NILRT_RESTARTCHECK_STATE_PATH 100 ) 101 ) 102 103 # Expert plugin files get added to a conf.d dir, so keep track of the total 104 # no. of files, their timestamps and content hashes 105 nisysapi_conf_d_path = _get_nisysapi_conf_d_path() 106 107 if os.path.exists(nisysapi_conf_d_path): 108 with salt.utils.files.fopen( 109 "{}/sysapi.conf.d.count".format(NILRT_RESTARTCHECK_STATE_PATH), "w" 110 ) as fcount: 111 fcount.write(str(len(os.listdir(nisysapi_conf_d_path)))) 112 113 for fexpert in os.listdir(nisysapi_conf_d_path): 114 _fingerprint_file( 115 filename=pathlib.Path(nisysapi_conf_d_path, fexpert), 116 fingerprint_dir=pathlib.Path(NILRT_RESTARTCHECK_STATE_PATH), 117 ) 118 119 120def _fingerprint_file(*, filename, fingerprint_dir): 121 """ 122 Compute stat & md5sum hash of provided ``filename``. Store 123 the hash and timestamp in ``fingerprint_dir``. 124 125 filename 126 ``Path`` to the file to stat & hash. 127 128 fingerprint_dir 129 ``Path`` of the directory to store the stat and hash output files. 130 """ 131 __salt__["cmd.shell"]( 132 "stat -c %Y {} > {}/{}.timestamp".format( 133 filename, fingerprint_dir, filename.name 134 ) 135 ) 136 __salt__["cmd.shell"]( 137 "md5sum {} > {}/{}.md5sum".format(filename, fingerprint_dir, filename.name) 138 ) 139 140 141def _get_restartcheck_result(errors): 142 """ 143 Return restartcheck result and append errors (if any) to ``errors`` 144 """ 145 rs_result = __salt__["restartcheck.restartcheck"](verbose=False) 146 if isinstance(rs_result, dict) and "comment" in rs_result: 147 errors.append(rs_result["comment"]) 148 return rs_result 149 150 151def _process_restartcheck_result(rs_result): 152 """ 153 Check restartcheck output to see if system/service restarts were requested 154 and take appropriate action. 155 """ 156 if "No packages seem to need to be restarted" in rs_result: 157 return 158 for rstr in rs_result: 159 if "System restart required" in rstr: 160 _update_nilrt_restart_state() 161 __salt__["system.set_reboot_required_witnessed"]() 162 else: 163 service = os.path.join("/etc/init.d", rstr) 164 if os.path.exists(service): 165 __salt__["cmd.run"]([service, "restart"]) 166 167 168def __virtual__(): 169 """ 170 Confirm this module is on a nilrt based system 171 """ 172 if __grains__.get("os_family") == "NILinuxRT": 173 try: 174 os.makedirs(NILRT_RESTARTCHECK_STATE_PATH) 175 except OSError as exc: 176 if exc.errno != errno.EEXIST: 177 return ( 178 False, 179 "Error creating {} (-{}): {}".format( 180 NILRT_RESTARTCHECK_STATE_PATH, exc.errno, exc.strerror 181 ), 182 ) 183 # populate state dir if empty 184 if not os.listdir(NILRT_RESTARTCHECK_STATE_PATH): 185 _update_nilrt_restart_state() 186 return __virtualname__ 187 188 if os.path.isdir(OPKG_CONFDIR): 189 return __virtualname__ 190 return False, "Module opkg only works on OpenEmbedded based systems" 191 192 193def latest_version(*names, **kwargs): 194 """ 195 Return the latest version of the named package available for upgrade or 196 installation. If more than one package name is specified, a dict of 197 name/version pairs is returned. 198 199 If the latest version of a given package is already installed, an empty 200 string will be returned for that package. 201 202 CLI Example: 203 204 .. code-block:: bash 205 206 salt '*' pkg.latest_version <package name> 207 salt '*' pkg.latest_version <package name> 208 salt '*' pkg.latest_version <package1> <package2> <package3> ... 209 """ 210 refresh = salt.utils.data.is_true(kwargs.pop("refresh", True)) 211 212 if len(names) == 0: 213 return "" 214 215 ret = {} 216 for name in names: 217 ret[name] = "" 218 219 # Refresh before looking for the latest version available 220 if refresh: 221 refresh_db() 222 223 cmd = ["opkg", "list-upgradable"] 224 out = __salt__["cmd.run_stdout"](cmd, output_loglevel="trace", python_shell=False) 225 for line in salt.utils.itertools.split(out, "\n"): 226 try: 227 name, _oldversion, newversion = line.split(" - ") 228 if name in names: 229 ret[name] = newversion 230 except ValueError: 231 pass 232 233 # Return a string if only one package name passed 234 if len(names) == 1: 235 return ret[names[0]] 236 return ret 237 238 239# available_version is being deprecated 240available_version = latest_version 241 242 243def version(*names, **kwargs): 244 """ 245 Returns a string representing the package version or an empty string if not 246 installed. If more than one package name is specified, a dict of 247 name/version pairs is returned. 248 249 CLI Example: 250 251 .. code-block:: bash 252 253 salt '*' pkg.version <package name> 254 salt '*' pkg.version <package1> <package2> <package3> ... 255 """ 256 return __salt__["pkg_resource.version"](*names, **kwargs) 257 258 259def refresh_db(failhard=False, **kwargs): # pylint: disable=unused-argument 260 """ 261 Updates the opkg database to latest packages based upon repositories 262 263 Returns a dict, with the keys being package databases and the values being 264 the result of the update attempt. Values can be one of the following: 265 266 - ``True``: Database updated successfully 267 - ``False``: Problem updating database 268 269 failhard 270 If False, return results of failed lines as ``False`` for the package 271 database that encountered the error. 272 If True, raise an error with a list of the package databases that 273 encountered errors. 274 275 .. versionadded:: 2018.3.0 276 277 CLI Example: 278 279 .. code-block:: bash 280 281 salt '*' pkg.refresh_db 282 """ 283 # Remove rtag file to keep multiple refreshes from happening in pkg states 284 salt.utils.pkg.clear_rtag(__opts__) 285 ret = {} 286 error_repos = [] 287 cmd = ["opkg", "update"] 288 # opkg returns a non-zero retcode when there is a failure to refresh 289 # from one or more repos. Due to this, ignore the retcode. 290 call = __salt__["cmd.run_all"]( 291 cmd, 292 output_loglevel="trace", 293 python_shell=False, 294 ignore_retcode=True, 295 redirect_stderr=True, 296 ) 297 298 out = call["stdout"] 299 prev_line = "" 300 for line in salt.utils.itertools.split(out, "\n"): 301 if "Inflating" in line: 302 key = line.strip().split()[1][:-1] 303 ret[key] = True 304 elif "Updated source" in line: 305 # Use the previous line. 306 key = prev_line.strip().split()[1][:-1] 307 ret[key] = True 308 elif "Failed to download" in line: 309 key = line.strip().split()[5].split(",")[0] 310 ret[key] = False 311 error_repos.append(key) 312 prev_line = line 313 314 if failhard and error_repos: 315 raise CommandExecutionError( 316 "Error getting repos: {}".format(", ".join(error_repos)) 317 ) 318 319 # On a non-zero exit code where no failed repos were found, raise an 320 # exception because this appears to be a different kind of error. 321 if call["retcode"] != 0 and not error_repos: 322 raise CommandExecutionError(out) 323 324 return ret 325 326 327def _is_testmode(**kwargs): 328 """ 329 Returns whether a test mode (noaction) operation was requested. 330 """ 331 return bool(kwargs.get("test") or __opts__.get("test")) 332 333 334def _append_noaction_if_testmode(cmd, **kwargs): 335 """ 336 Adds the --noaction flag to the command if it's running in the test mode. 337 """ 338 if _is_testmode(**kwargs): 339 cmd.append("--noaction") 340 341 342def _build_install_command_list(cmd_prefix, to_install, to_downgrade, to_reinstall): 343 """ 344 Builds a list of install commands to be executed in sequence in order to process 345 each of the to_install, to_downgrade, and to_reinstall lists. 346 """ 347 cmds = [] 348 if to_install: 349 cmd = copy.deepcopy(cmd_prefix) 350 cmd.extend(to_install) 351 cmds.append(cmd) 352 if to_downgrade: 353 cmd = copy.deepcopy(cmd_prefix) 354 cmd.append("--force-downgrade") 355 cmd.extend(to_downgrade) 356 cmds.append(cmd) 357 if to_reinstall: 358 cmd = copy.deepcopy(cmd_prefix) 359 cmd.append("--force-reinstall") 360 cmd.extend(to_reinstall) 361 cmds.append(cmd) 362 363 return cmds 364 365 366def _parse_reported_packages_from_install_output(output): 367 """ 368 Parses the output of "opkg install" to determine what packages would have been 369 installed by an operation run with the --noaction flag. 370 371 We are looking for lines like: 372 Installing <package> (<version>) on <target> 373 or 374 Upgrading <package> from <oldVersion> to <version> on root 375 """ 376 reported_pkgs = {} 377 install_pattern = re.compile( 378 r"Installing\s(?P<package>.*?)\s\((?P<version>.*?)\)\son\s(?P<target>.*?)" 379 ) 380 upgrade_pattern = re.compile( 381 r"Upgrading\s(?P<package>.*?)\sfrom\s(?P<oldVersion>.*?)\sto\s(?P<version>.*?)\son\s(?P<target>.*?)" 382 ) 383 for line in salt.utils.itertools.split(output, "\n"): 384 match = install_pattern.match(line) 385 if match is None: 386 match = upgrade_pattern.match(line) 387 if match: 388 reported_pkgs[match.group("package")] = match.group("version") 389 390 return reported_pkgs 391 392 393def _execute_install_command(cmd, parse_output, errors, parsed_packages): 394 """ 395 Executes a command for the install operation. 396 If the command fails, its error output will be appended to the errors list. 397 If the command succeeds and parse_output is true, updated packages will be appended 398 to the parsed_packages dictionary. 399 """ 400 out = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False) 401 if out["retcode"] != 0: 402 if out["stderr"]: 403 errors.append(out["stderr"]) 404 else: 405 errors.append(out["stdout"]) 406 elif parse_output: 407 parsed_packages.update( 408 _parse_reported_packages_from_install_output(out["stdout"]) 409 ) 410 411 412def install( 413 name=None, refresh=False, pkgs=None, sources=None, reinstall=False, **kwargs 414): 415 """ 416 Install the passed package, add refresh=True to update the opkg database. 417 418 name 419 The name of the package to be installed. Note that this parameter is 420 ignored if either "pkgs" or "sources" is passed. Additionally, please 421 note that this option can only be used to install packages from a 422 software repository. To install a package file manually, use the 423 "sources" option. 424 425 CLI Example: 426 427 .. code-block:: bash 428 429 salt '*' pkg.install <package name> 430 431 refresh 432 Whether or not to refresh the package database before installing. 433 434 version 435 Install a specific version of the package, e.g. 1.2.3~0ubuntu0. Ignored 436 if "pkgs" or "sources" is passed. 437 438 .. versionadded:: 2017.7.0 439 440 reinstall : False 441 Specifying reinstall=True will use ``opkg install --force-reinstall`` 442 rather than simply ``opkg install`` for requested packages that are 443 already installed. 444 445 If a version is specified with the requested package, then ``opkg 446 install --force-reinstall`` will only be used if the installed version 447 matches the requested version. 448 449 .. versionadded:: 2017.7.0 450 451 452 Multiple Package Installation Options: 453 454 pkgs 455 A list of packages to install from a software repository. Must be 456 passed as a python list. 457 458 CLI Example: 459 460 .. code-block:: bash 461 462 salt '*' pkg.install pkgs='["foo", "bar"]' 463 salt '*' pkg.install pkgs='["foo", {"bar": "1.2.3-0ubuntu0"}]' 464 465 sources 466 A list of IPK packages to install. Must be passed as a list of dicts, 467 with the keys being package names, and the values being the source URI 468 or local path to the package. Dependencies are automatically resolved 469 and marked as auto-installed. 470 471 CLI Example: 472 473 .. code-block:: bash 474 475 salt '*' pkg.install sources='[{"foo": "salt://foo.deb"},{"bar": "salt://bar.deb"}]' 476 477 install_recommends 478 Whether to install the packages marked as recommended. Default is True. 479 480 only_upgrade 481 Only upgrade the packages (disallow downgrades), if they are already 482 installed. Default is False. 483 484 .. versionadded:: 2017.7.0 485 486 Returns a dict containing the new package names and versions:: 487 488 {'<package>': {'old': '<old-version>', 489 'new': '<new-version>'}} 490 """ 491 refreshdb = salt.utils.data.is_true(refresh) 492 493 try: 494 pkg_params, pkg_type = __salt__["pkg_resource.parse_targets"]( 495 name, pkgs, sources, **kwargs 496 ) 497 except MinionError as exc: 498 raise CommandExecutionError(exc) 499 500 old = list_pkgs() 501 cmd_prefix = ["opkg", "install"] 502 to_install = [] 503 to_reinstall = [] 504 to_downgrade = [] 505 506 _append_noaction_if_testmode(cmd_prefix, **kwargs) 507 if pkg_params is None or len(pkg_params) == 0: 508 return {} 509 elif pkg_type == "file": 510 if reinstall: 511 cmd_prefix.append("--force-reinstall") 512 if not kwargs.get("only_upgrade", False): 513 cmd_prefix.append("--force-downgrade") 514 to_install.extend(pkg_params) 515 elif pkg_type == "repository": 516 if not kwargs.get("install_recommends", True): 517 cmd_prefix.append("--no-install-recommends") 518 for pkgname, pkgversion in pkg_params.items(): 519 if name and pkgs is None and kwargs.get("version") and len(pkg_params) == 1: 520 # Only use the 'version' param if 'name' was not specified as a 521 # comma-separated list 522 version_num = kwargs["version"] 523 else: 524 version_num = pkgversion 525 526 if version_num is None: 527 # Don't allow downgrades if the version 528 # number is not specified. 529 if reinstall and pkgname in old: 530 to_reinstall.append(pkgname) 531 else: 532 to_install.append(pkgname) 533 else: 534 pkgstr = "{}={}".format(pkgname, version_num) 535 cver = old.get(pkgname, "") 536 if ( 537 reinstall 538 and cver 539 and salt.utils.versions.compare( 540 ver1=version_num, oper="==", ver2=cver, cmp_func=version_cmp 541 ) 542 ): 543 to_reinstall.append(pkgstr) 544 elif not cver or salt.utils.versions.compare( 545 ver1=version_num, oper=">=", ver2=cver, cmp_func=version_cmp 546 ): 547 to_install.append(pkgstr) 548 else: 549 if not kwargs.get("only_upgrade", False): 550 to_downgrade.append(pkgstr) 551 else: 552 # This should cause the command to fail. 553 to_install.append(pkgstr) 554 555 cmds = _build_install_command_list( 556 cmd_prefix, to_install, to_downgrade, to_reinstall 557 ) 558 559 if not cmds: 560 return {} 561 562 if refreshdb: 563 refresh_db() 564 565 errors = [] 566 is_testmode = _is_testmode(**kwargs) 567 test_packages = {} 568 for cmd in cmds: 569 _execute_install_command(cmd, is_testmode, errors, test_packages) 570 571 __context__.pop("pkg.list_pkgs", None) 572 new = list_pkgs() 573 if is_testmode: 574 new = copy.deepcopy(new) 575 new.update(test_packages) 576 577 ret = salt.utils.data.compare_dicts(old, new) 578 579 if pkg_type == "file" and reinstall: 580 # For file-based packages, prepare 'to_reinstall' to have a list 581 # of all the package names that may have been reinstalled. 582 # This way, we could include reinstalled packages in 'ret'. 583 for pkgfile in to_install: 584 # Convert from file name to package name. 585 cmd = ["opkg", "info", pkgfile] 586 out = __salt__["cmd.run_all"]( 587 cmd, output_loglevel="trace", python_shell=False 588 ) 589 if out["retcode"] == 0: 590 # Just need the package name. 591 pkginfo_dict = _process_info_installed_output(out["stdout"], []) 592 if pkginfo_dict: 593 to_reinstall.append(next(iter(pkginfo_dict))) 594 595 for pkgname in to_reinstall: 596 if pkgname not in ret or pkgname in old: 597 ret.update( 598 {pkgname: {"old": old.get(pkgname, ""), "new": new.get(pkgname, "")}} 599 ) 600 601 rs_result = _get_restartcheck_result(errors) 602 603 if errors: 604 raise CommandExecutionError( 605 "Problem encountered installing package(s)", 606 info={"errors": errors, "changes": ret}, 607 ) 608 609 _process_restartcheck_result(rs_result) 610 611 return ret 612 613 614def _parse_reported_packages_from_remove_output(output): 615 """ 616 Parses the output of "opkg remove" to determine what packages would have been 617 removed by an operation run with the --noaction flag. 618 619 We are looking for lines like 620 Removing <package> (<version>) from <Target>... 621 """ 622 reported_pkgs = {} 623 remove_pattern = re.compile( 624 r"Removing\s(?P<package>.*?)\s\((?P<version>.*?)\)\sfrom\s(?P<target>.*?)..." 625 ) 626 for line in salt.utils.itertools.split(output, "\n"): 627 match = remove_pattern.match(line) 628 if match: 629 reported_pkgs[match.group("package")] = "" 630 631 return reported_pkgs 632 633 634def remove(name=None, pkgs=None, **kwargs): # pylint: disable=unused-argument 635 """ 636 Remove packages using ``opkg remove``. 637 638 name 639 The name of the package to be deleted. 640 641 642 Multiple Package Options: 643 644 pkgs 645 A list of packages to delete. Must be passed as a python list. The 646 ``name`` parameter will be ignored if this option is passed. 647 648 remove_dependencies 649 Remove package and all dependencies 650 651 .. versionadded:: 2019.2.0 652 653 auto_remove_deps 654 Remove packages that were installed automatically to satisfy dependencies 655 656 .. versionadded:: 2019.2.0 657 658 Returns a dict containing the changes. 659 660 CLI Example: 661 662 .. code-block:: bash 663 664 salt '*' pkg.remove <package name> 665 salt '*' pkg.remove <package1>,<package2>,<package3> 666 salt '*' pkg.remove pkgs='["foo", "bar"]' 667 salt '*' pkg.remove pkgs='["foo", "bar"]' remove_dependencies=True auto_remove_deps=True 668 """ 669 try: 670 pkg_params = __salt__["pkg_resource.parse_targets"](name, pkgs)[0] 671 except MinionError as exc: 672 raise CommandExecutionError(exc) 673 674 old = list_pkgs() 675 targets = [x for x in pkg_params if x in old] 676 if not targets: 677 return {} 678 cmd = ["opkg", "remove"] 679 _append_noaction_if_testmode(cmd, **kwargs) 680 if kwargs.get("remove_dependencies", False): 681 cmd.append("--force-removal-of-dependent-packages") 682 if kwargs.get("auto_remove_deps", False): 683 cmd.append("--autoremove") 684 cmd.extend(targets) 685 686 out = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False) 687 if out["retcode"] != 0: 688 if out["stderr"]: 689 errors = [out["stderr"]] 690 else: 691 errors = [out["stdout"]] 692 else: 693 errors = [] 694 695 __context__.pop("pkg.list_pkgs", None) 696 new = list_pkgs() 697 if _is_testmode(**kwargs): 698 reportedPkgs = _parse_reported_packages_from_remove_output(out["stdout"]) 699 new = {k: v for k, v in new.items() if k not in reportedPkgs} 700 ret = salt.utils.data.compare_dicts(old, new) 701 702 rs_result = _get_restartcheck_result(errors) 703 704 if errors: 705 raise CommandExecutionError( 706 "Problem encountered removing package(s)", 707 info={"errors": errors, "changes": ret}, 708 ) 709 710 _process_restartcheck_result(rs_result) 711 712 return ret 713 714 715def purge(name=None, pkgs=None, **kwargs): # pylint: disable=unused-argument 716 """ 717 Package purges are not supported by opkg, this function is identical to 718 :mod:`pkg.remove <salt.modules.opkg.remove>`. 719 720 name 721 The name of the package to be deleted. 722 723 724 Multiple Package Options: 725 726 pkgs 727 A list of packages to delete. Must be passed as a python list. The 728 ``name`` parameter will be ignored if this option is passed. 729 730 731 Returns a dict containing the changes. 732 733 CLI Example: 734 735 .. code-block:: bash 736 737 salt '*' pkg.purge <package name> 738 salt '*' pkg.purge <package1>,<package2>,<package3> 739 salt '*' pkg.purge pkgs='["foo", "bar"]' 740 """ 741 return remove(name=name, pkgs=pkgs) 742 743 744def upgrade(refresh=True, **kwargs): # pylint: disable=unused-argument 745 """ 746 Upgrades all packages via ``opkg upgrade`` 747 748 Returns a dictionary containing the changes: 749 750 .. code-block:: python 751 752 {'<package>': {'old': '<old-version>', 753 'new': '<new-version>'}} 754 755 CLI Example: 756 757 .. code-block:: bash 758 759 salt '*' pkg.upgrade 760 """ 761 ret = { 762 "changes": {}, 763 "result": True, 764 "comment": "", 765 } 766 767 errors = [] 768 769 if salt.utils.data.is_true(refresh): 770 refresh_db() 771 772 old = list_pkgs() 773 774 cmd = ["opkg", "upgrade"] 775 result = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False) 776 __context__.pop("pkg.list_pkgs", None) 777 new = list_pkgs() 778 ret = salt.utils.data.compare_dicts(old, new) 779 780 if result["retcode"] != 0: 781 errors.append(result) 782 783 rs_result = _get_restartcheck_result(errors) 784 785 if errors: 786 raise CommandExecutionError( 787 "Problem encountered upgrading packages", 788 info={"errors": errors, "changes": ret}, 789 ) 790 791 _process_restartcheck_result(rs_result) 792 793 return ret 794 795 796def hold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613 797 """ 798 Set package in 'hold' state, meaning it will not be upgraded. 799 800 name 801 The name of the package, e.g., 'tmux' 802 803 CLI Example: 804 805 .. code-block:: bash 806 807 salt '*' pkg.hold <package name> 808 809 pkgs 810 A list of packages to hold. Must be passed as a python list. 811 812 CLI Example: 813 814 .. code-block:: bash 815 816 salt '*' pkg.hold pkgs='["foo", "bar"]' 817 """ 818 if not name and not pkgs and not sources: 819 raise SaltInvocationError("One of name, pkgs, or sources must be specified.") 820 if pkgs and sources: 821 raise SaltInvocationError("Only one of pkgs or sources can be specified.") 822 823 targets = [] 824 if pkgs: 825 targets.extend(pkgs) 826 elif sources: 827 for source in sources: 828 targets.append(next(iter(source))) 829 else: 830 targets.append(name) 831 832 ret = {} 833 for target in targets: 834 if isinstance(target, dict): 835 target = next(iter(target)) 836 837 ret[target] = {"name": target, "changes": {}, "result": False, "comment": ""} 838 839 state = _get_state(target) 840 if not state: 841 ret[target]["comment"] = "Package {} not currently held.".format(target) 842 elif state != "hold": 843 if "test" in __opts__ and __opts__["test"]: 844 ret[target].update(result=None) 845 ret[target]["comment"] = "Package {} is set to be held.".format(target) 846 else: 847 result = _set_state(target, "hold") 848 ret[target].update(changes=result[target], result=True) 849 ret[target]["comment"] = "Package {} is now being held.".format(target) 850 else: 851 ret[target].update(result=True) 852 ret[target]["comment"] = "Package {} is already set to be held.".format( 853 target 854 ) 855 return ret 856 857 858def unhold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613 859 """ 860 Set package current in 'hold' state to install state, 861 meaning it will be upgraded. 862 863 name 864 The name of the package, e.g., 'tmux' 865 866 CLI Example: 867 868 .. code-block:: bash 869 870 salt '*' pkg.unhold <package name> 871 872 pkgs 873 A list of packages to hold. Must be passed as a python list. 874 875 CLI Example: 876 877 .. code-block:: bash 878 879 salt '*' pkg.unhold pkgs='["foo", "bar"]' 880 """ 881 if not name and not pkgs and not sources: 882 raise SaltInvocationError("One of name, pkgs, or sources must be specified.") 883 if pkgs and sources: 884 raise SaltInvocationError("Only one of pkgs or sources can be specified.") 885 886 targets = [] 887 if pkgs: 888 targets.extend(pkgs) 889 elif sources: 890 for source in sources: 891 targets.append(next(iter(source))) 892 else: 893 targets.append(name) 894 895 ret = {} 896 for target in targets: 897 if isinstance(target, dict): 898 target = next(iter(target)) 899 900 ret[target] = {"name": target, "changes": {}, "result": False, "comment": ""} 901 902 state = _get_state(target) 903 if not state: 904 ret[target]["comment"] = "Package {} does not have a state.".format(target) 905 elif state == "hold": 906 if "test" in __opts__ and __opts__["test"]: 907 ret[target].update(result=None) 908 ret["comment"] = "Package {} is set not to be held.".format(target) 909 else: 910 result = _set_state(target, "ok") 911 ret[target].update(changes=result[target], result=True) 912 ret[target]["comment"] = "Package {} is no longer being held.".format( 913 target 914 ) 915 else: 916 ret[target].update(result=True) 917 ret[target]["comment"] = "Package {} is already set not to be held.".format( 918 target 919 ) 920 return ret 921 922 923def _get_state(pkg): 924 """ 925 View package state from the opkg database 926 927 Return the state of pkg 928 """ 929 cmd = ["opkg", "status"] 930 cmd.append(pkg) 931 out = __salt__["cmd.run"](cmd, python_shell=False) 932 state_flag = "" 933 for line in salt.utils.itertools.split(out, "\n"): 934 if line.startswith("Status"): 935 _status, _state_want, state_flag, _state_status = line.split() 936 937 return state_flag 938 939 940def _set_state(pkg, state): 941 """ 942 Change package state on the opkg database 943 944 The state can be any of: 945 946 - hold 947 - noprune 948 - user 949 - ok 950 - installed 951 - unpacked 952 953 This command is commonly used to mark a specific package to be held from 954 being upgraded, that is, to be kept at a certain version. 955 956 Returns a dict containing the package name, and the new and old 957 versions. 958 """ 959 ret = {} 960 valid_states = ("hold", "noprune", "user", "ok", "installed", "unpacked") 961 if state not in valid_states: 962 raise SaltInvocationError("Invalid state: {}".format(state)) 963 oldstate = _get_state(pkg) 964 cmd = ["opkg", "flag"] 965 cmd.append(state) 966 cmd.append(pkg) 967 _out = __salt__["cmd.run"](cmd, python_shell=False) 968 969 # Missing return value check due to opkg issue 160 970 ret[pkg] = {"old": oldstate, "new": state} 971 return ret 972 973 974def _list_pkgs_from_context(versions_as_list): 975 """ 976 Use pkg list from __context__ 977 """ 978 if versions_as_list: 979 return __context__["pkg.list_pkgs"] 980 else: 981 ret = copy.deepcopy(__context__["pkg.list_pkgs"]) 982 __salt__["pkg_resource.stringify"](ret) 983 return ret 984 985 986def list_pkgs(versions_as_list=False, **kwargs): 987 """ 988 List the packages currently installed in a dict:: 989 990 {'<package_name>': '<version>'} 991 992 CLI Example: 993 994 .. code-block:: bash 995 996 salt '*' pkg.list_pkgs 997 salt '*' pkg.list_pkgs versions_as_list=True 998 """ 999 versions_as_list = salt.utils.data.is_true(versions_as_list) 1000 # not yet implemented or not applicable 1001 if any( 1002 [salt.utils.data.is_true(kwargs.get(x)) for x in ("removed", "purge_desired")] 1003 ): 1004 return {} 1005 1006 if "pkg.list_pkgs" in __context__: 1007 return _list_pkgs_from_context(versions_as_list) 1008 1009 cmd = ["opkg", "list-installed"] 1010 ret = {} 1011 out = __salt__["cmd.run"](cmd, output_loglevel="trace", python_shell=False) 1012 for line in salt.utils.itertools.split(out, "\n"): 1013 # This is a continuation of package description 1014 if not line or line[0] == " ": 1015 continue 1016 1017 # This contains package name, version, and description. 1018 # Extract the first two. 1019 pkg_name, pkg_version = line.split(" - ", 2)[:2] 1020 __salt__["pkg_resource.add_pkg"](ret, pkg_name, pkg_version) 1021 1022 __salt__["pkg_resource.sort_pkglist"](ret) 1023 __context__["pkg.list_pkgs"] = copy.deepcopy(ret) 1024 if not versions_as_list: 1025 __salt__["pkg_resource.stringify"](ret) 1026 return ret 1027 1028 1029def list_upgrades(refresh=True, **kwargs): # pylint: disable=unused-argument 1030 """ 1031 List all available package upgrades. 1032 1033 CLI Example: 1034 1035 .. code-block:: bash 1036 1037 salt '*' pkg.list_upgrades 1038 """ 1039 ret = {} 1040 if salt.utils.data.is_true(refresh): 1041 refresh_db() 1042 1043 cmd = ["opkg", "list-upgradable"] 1044 call = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False) 1045 1046 if call["retcode"] != 0: 1047 comment = "" 1048 if "stderr" in call: 1049 comment += call["stderr"] 1050 if "stdout" in call: 1051 comment += call["stdout"] 1052 raise CommandExecutionError(comment) 1053 else: 1054 out = call["stdout"] 1055 1056 for line in out.splitlines(): 1057 name, _oldversion, newversion = line.split(" - ") 1058 ret[name] = newversion 1059 1060 return ret 1061 1062 1063def _convert_to_standard_attr(attr): 1064 """ 1065 Helper function for _process_info_installed_output() 1066 1067 Converts an opkg attribute name to a standard attribute 1068 name which is used across 'pkg' modules. 1069 """ 1070 ret_attr = ATTR_MAP.get(attr, None) 1071 if ret_attr is None: 1072 # All others convert to lowercase 1073 return attr.lower() 1074 return ret_attr 1075 1076 1077def _process_info_installed_output(out, filter_attrs): 1078 """ 1079 Helper function for info_installed() 1080 1081 Processes stdout output from a single invocation of 1082 'opkg status'. 1083 """ 1084 ret = {} 1085 name = None 1086 attrs = {} 1087 attr = None 1088 1089 for line in salt.utils.itertools.split(out, "\n"): 1090 if line and line[0] == " ": 1091 # This is a continuation of the last attr 1092 if filter_attrs is None or attr in filter_attrs: 1093 line = line.strip() 1094 if attrs[attr]: 1095 # If attr is empty, don't add leading newline 1096 attrs[attr] += "\n" 1097 attrs[attr] += line 1098 continue 1099 line = line.strip() 1100 if not line: 1101 # Separator between different packages 1102 if name: 1103 ret[name] = attrs 1104 name = None 1105 attrs = {} 1106 attr = None 1107 continue 1108 key, value = line.split(":", 1) 1109 value = value.lstrip() 1110 attr = _convert_to_standard_attr(key) 1111 if attr == "name": 1112 name = value 1113 elif filter_attrs is None or attr in filter_attrs: 1114 attrs[attr] = value 1115 1116 if name: 1117 ret[name] = attrs 1118 return ret 1119 1120 1121def info_installed(*names, **kwargs): 1122 """ 1123 Return the information of the named package(s), installed on the system. 1124 1125 .. versionadded:: 2017.7.0 1126 1127 :param names: 1128 Names of the packages to get information about. If none are specified, 1129 will return information for all installed packages. 1130 1131 :param attr: 1132 Comma-separated package attributes. If no 'attr' is specified, all available attributes returned. 1133 1134 Valid attributes are: 1135 arch, conffiles, conflicts, depends, description, filename, group, 1136 install_date_time_t, md5sum, packager, provides, recommends, 1137 replaces, size, source, suggests, url, version 1138 1139 CLI Example: 1140 1141 .. code-block:: bash 1142 1143 salt '*' pkg.info_installed 1144 salt '*' pkg.info_installed attr=version,packager 1145 salt '*' pkg.info_installed <package1> 1146 salt '*' pkg.info_installed <package1> <package2> <package3> ... 1147 salt '*' pkg.info_installed <package1> attr=version,packager 1148 salt '*' pkg.info_installed <package1> <package2> <package3> ... attr=version,packager 1149 """ 1150 attr = kwargs.pop("attr", None) 1151 if attr is None: 1152 filter_attrs = None 1153 elif isinstance(attr, str): 1154 filter_attrs = set(attr.split(",")) 1155 else: 1156 filter_attrs = set(attr) 1157 1158 ret = {} 1159 if names: 1160 # Specific list of names of installed packages 1161 for name in names: 1162 cmd = ["opkg", "status", name] 1163 call = __salt__["cmd.run_all"]( 1164 cmd, output_loglevel="trace", python_shell=False 1165 ) 1166 if call["retcode"] != 0: 1167 comment = "" 1168 if call["stderr"]: 1169 comment += call["stderr"] 1170 else: 1171 comment += call["stdout"] 1172 1173 raise CommandExecutionError(comment) 1174 ret.update(_process_info_installed_output(call["stdout"], filter_attrs)) 1175 else: 1176 # All installed packages 1177 cmd = ["opkg", "status"] 1178 call = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False) 1179 if call["retcode"] != 0: 1180 comment = "" 1181 if call["stderr"]: 1182 comment += call["stderr"] 1183 else: 1184 comment += call["stdout"] 1185 1186 raise CommandExecutionError(comment) 1187 ret.update(_process_info_installed_output(call["stdout"], filter_attrs)) 1188 1189 return ret 1190 1191 1192def upgrade_available(name, **kwargs): # pylint: disable=unused-argument 1193 """ 1194 Check whether or not an upgrade is available for a given package 1195 1196 CLI Example: 1197 1198 .. code-block:: bash 1199 1200 salt '*' pkg.upgrade_available <package name> 1201 """ 1202 return latest_version(name) != "" 1203 1204 1205def version_cmp( 1206 pkg1, pkg2, ignore_epoch=False, **kwargs 1207): # pylint: disable=unused-argument 1208 """ 1209 Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if 1210 pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem 1211 making the comparison. 1212 1213 ignore_epoch : False 1214 Set to ``True`` to ignore the epoch when comparing versions 1215 1216 .. versionadded:: 2016.3.4 1217 1218 CLI Example: 1219 1220 .. code-block:: bash 1221 1222 salt '*' pkg.version_cmp '0.2.4-0' '0.2.4.1-0' 1223 """ 1224 normalize = lambda x: str(x).split(":", 1)[-1] if ignore_epoch else str(x) 1225 pkg1 = normalize(pkg1) 1226 pkg2 = normalize(pkg2) 1227 1228 output = __salt__["cmd.run_stdout"]( 1229 ["opkg", "--version"], output_loglevel="trace", python_shell=False 1230 ) 1231 opkg_version = output.split(" ")[2].strip() 1232 if salt.utils.versions.LooseVersion( 1233 opkg_version 1234 ) >= salt.utils.versions.LooseVersion("0.3.4"): 1235 cmd_compare = ["opkg", "compare-versions"] 1236 elif salt.utils.path.which("opkg-compare-versions"): 1237 cmd_compare = ["opkg-compare-versions"] 1238 else: 1239 log.warning( 1240 "Unable to find a compare-versions utility installed. Either upgrade opkg" 1241 " to version > 0.3.4 (preferred) or install the older opkg-compare-versions" 1242 " script." 1243 ) 1244 return None 1245 1246 for oper, ret in (("<<", -1), ("=", 0), (">>", 1)): 1247 cmd = cmd_compare[:] 1248 cmd.append(shlex.quote(pkg1)) 1249 cmd.append(oper) 1250 cmd.append(shlex.quote(pkg2)) 1251 retcode = __salt__["cmd.retcode"]( 1252 cmd, output_loglevel="trace", ignore_retcode=True, python_shell=False 1253 ) 1254 if retcode == 0: 1255 return ret 1256 return None 1257 1258 1259def _set_repo_option(repo, option): 1260 """ 1261 Set the option to repo 1262 """ 1263 if not option: 1264 return 1265 opt = option.split("=") 1266 if len(opt) != 2: 1267 return 1268 if opt[0] == "trusted": 1269 repo["trusted"] = opt[1] == "yes" 1270 else: 1271 repo[opt[0]] = opt[1] 1272 1273 1274def _set_repo_options(repo, options): 1275 """ 1276 Set the options to the repo. 1277 """ 1278 delimiters = "[", "]" 1279 pattern = "|".join(map(re.escape, delimiters)) 1280 for option in options: 1281 splitted = re.split(pattern, option) 1282 for opt in splitted: 1283 _set_repo_option(repo, opt) 1284 1285 1286def _create_repo(line, filename): 1287 """ 1288 Create repo 1289 """ 1290 repo = {} 1291 if line.startswith("#"): 1292 repo["enabled"] = False 1293 line = line[1:] 1294 else: 1295 repo["enabled"] = True 1296 cols = salt.utils.args.shlex_split(line.strip()) 1297 repo["compressed"] = not cols[0] in "src" 1298 repo["name"] = cols[1] 1299 repo["uri"] = cols[2] 1300 repo["file"] = os.path.join(OPKG_CONFDIR, filename) 1301 if len(cols) > 3: 1302 _set_repo_options(repo, cols[3:]) 1303 return repo 1304 1305 1306def _read_repos(conf_file, repos, filename, regex): 1307 """ 1308 Read repos from configuration file 1309 """ 1310 for line in conf_file: 1311 line = salt.utils.stringutils.to_unicode(line) 1312 if not regex.search(line): 1313 continue 1314 repo = _create_repo(line, filename) 1315 1316 # do not store duplicated uri's 1317 if repo["uri"] not in repos: 1318 repos[repo["uri"]] = [repo] 1319 1320 1321def list_repos(**kwargs): # pylint: disable=unused-argument 1322 """ 1323 Lists all repos on ``/etc/opkg/*.conf`` 1324 1325 CLI Example: 1326 1327 .. code-block:: bash 1328 1329 salt '*' pkg.list_repos 1330 """ 1331 repos = {} 1332 regex = re.compile(REPO_REGEXP) 1333 for filename in os.listdir(OPKG_CONFDIR): 1334 if not filename.endswith(".conf"): 1335 continue 1336 with salt.utils.files.fopen(os.path.join(OPKG_CONFDIR, filename)) as conf_file: 1337 _read_repos(conf_file, repos, filename, regex) 1338 return repos 1339 1340 1341def get_repo(repo, **kwargs): # pylint: disable=unused-argument 1342 """ 1343 Display a repo from the ``/etc/opkg/*.conf`` 1344 1345 CLI Examples: 1346 1347 .. code-block:: bash 1348 1349 salt '*' pkg.get_repo repo 1350 """ 1351 repos = list_repos() 1352 1353 if repos: 1354 for source in repos.values(): 1355 for sub in source: 1356 if sub["name"] == repo: 1357 return sub 1358 return {} 1359 1360 1361def _del_repo_from_file(repo, filepath): 1362 """ 1363 Remove a repo from filepath 1364 """ 1365 with salt.utils.files.fopen(filepath) as fhandle: 1366 output = [] 1367 regex = re.compile(REPO_REGEXP) 1368 for line in fhandle: 1369 line = salt.utils.stringutils.to_unicode(line) 1370 if regex.search(line): 1371 if line.startswith("#"): 1372 line = line[1:] 1373 cols = salt.utils.args.shlex_split(line.strip()) 1374 if repo != cols[1]: 1375 output.append(salt.utils.stringutils.to_str(line)) 1376 with salt.utils.files.fopen(filepath, "w") as fhandle: 1377 fhandle.writelines(output) 1378 1379 1380def _set_trusted_option_if_needed(repostr, trusted): 1381 """ 1382 Set trusted option to repo if needed 1383 """ 1384 if trusted is True: 1385 repostr += " [trusted=yes]" 1386 elif trusted is False: 1387 repostr += " [trusted=no]" 1388 return repostr 1389 1390 1391def _add_new_repo(repo, properties): 1392 """ 1393 Add a new repo entry 1394 """ 1395 repostr = "# " if not properties.get("enabled") else "" 1396 repostr += "src/gz " if properties.get("compressed") else "src " 1397 if " " in repo: 1398 repostr += '"' + repo + '" ' 1399 else: 1400 repostr += repo + " " 1401 repostr += properties.get("uri") 1402 repostr = _set_trusted_option_if_needed(repostr, properties.get("trusted")) 1403 repostr += "\n" 1404 conffile = os.path.join(OPKG_CONFDIR, repo + ".conf") 1405 1406 with salt.utils.files.fopen(conffile, "a") as fhandle: 1407 fhandle.write(salt.utils.stringutils.to_str(repostr)) 1408 1409 1410def _mod_repo_in_file(repo, repostr, filepath): 1411 """ 1412 Replace a repo entry in filepath with repostr 1413 """ 1414 with salt.utils.files.fopen(filepath) as fhandle: 1415 output = [] 1416 for line in fhandle: 1417 cols = salt.utils.args.shlex_split( 1418 salt.utils.stringutils.to_unicode(line).strip() 1419 ) 1420 if repo not in cols: 1421 output.append(line) 1422 else: 1423 output.append(salt.utils.stringutils.to_str(repostr + "\n")) 1424 with salt.utils.files.fopen(filepath, "w") as fhandle: 1425 fhandle.writelines(output) 1426 1427 1428def del_repo(repo, **kwargs): # pylint: disable=unused-argument 1429 """ 1430 Delete a repo from ``/etc/opkg/*.conf`` 1431 1432 If the file does not contain any other repo configuration, the file itself 1433 will be deleted. 1434 1435 CLI Examples: 1436 1437 .. code-block:: bash 1438 1439 salt '*' pkg.del_repo repo 1440 """ 1441 refresh = salt.utils.data.is_true(kwargs.get("refresh", True)) 1442 repos = list_repos() 1443 if repos: 1444 deleted_from = dict() 1445 for repository in repos: 1446 source = repos[repository][0] 1447 if source["name"] == repo: 1448 deleted_from[source["file"]] = 0 1449 _del_repo_from_file(repo, source["file"]) 1450 1451 if deleted_from: 1452 ret = "" 1453 for repository in repos: 1454 source = repos[repository][0] 1455 if source["file"] in deleted_from: 1456 deleted_from[source["file"]] += 1 1457 for repo_file, count in deleted_from.items(): 1458 msg = "Repo '{}' has been removed from {}.\n" 1459 if count == 1 and os.path.isfile(repo_file): 1460 msg = "File {1} containing repo '{0}' has been removed.\n" 1461 try: 1462 os.remove(repo_file) 1463 except OSError: 1464 pass 1465 ret += msg.format(repo, repo_file) 1466 if refresh: 1467 refresh_db() 1468 return ret 1469 1470 return "Repo {} doesn't exist in the opkg repo lists".format(repo) 1471 1472 1473def mod_repo(repo, **kwargs): 1474 """ 1475 Modify one or more values for a repo. If the repo does not exist, it will 1476 be created, so long as uri is defined. 1477 1478 The following options are available to modify a repo definition: 1479 1480 repo 1481 alias by which opkg refers to the repo. 1482 uri 1483 the URI to the repo. 1484 compressed 1485 defines (True or False) if the index file is compressed 1486 enabled 1487 enable or disable (True or False) repository 1488 but do not remove if disabled. 1489 refresh 1490 enable or disable (True or False) auto-refresh of the repositories 1491 1492 CLI Examples: 1493 1494 .. code-block:: bash 1495 1496 salt '*' pkg.mod_repo repo uri=http://new/uri 1497 salt '*' pkg.mod_repo repo enabled=False 1498 """ 1499 repos = list_repos() 1500 found = False 1501 uri = "" 1502 if "uri" in kwargs: 1503 uri = kwargs["uri"] 1504 1505 for repository in repos: 1506 source = repos[repository][0] 1507 if source["name"] == repo: 1508 found = True 1509 repostr = "" 1510 if "enabled" in kwargs and not kwargs["enabled"]: 1511 repostr += "# " 1512 if "compressed" in kwargs: 1513 repostr += "src/gz " if kwargs["compressed"] else "src" 1514 else: 1515 repostr += "src/gz" if source["compressed"] else "src" 1516 repo_alias = kwargs["alias"] if "alias" in kwargs else repo 1517 if " " in repo_alias: 1518 repostr += ' "{}"'.format(repo_alias) 1519 else: 1520 repostr += " {}".format(repo_alias) 1521 repostr += " {}".format(kwargs["uri"] if "uri" in kwargs else source["uri"]) 1522 trusted = kwargs.get("trusted") 1523 repostr = ( 1524 _set_trusted_option_if_needed(repostr, trusted) 1525 if trusted is not None 1526 else _set_trusted_option_if_needed(repostr, source.get("trusted")) 1527 ) 1528 _mod_repo_in_file(repo, repostr, source["file"]) 1529 elif uri and source["uri"] == uri: 1530 raise CommandExecutionError( 1531 "Repository '{}' already exists as '{}'.".format(uri, source["name"]) 1532 ) 1533 1534 if not found: 1535 # Need to add a new repo 1536 if "uri" not in kwargs: 1537 raise CommandExecutionError( 1538 "Repository '{}' not found and no URI passed to create one.".format( 1539 repo 1540 ) 1541 ) 1542 properties = {"uri": kwargs["uri"]} 1543 # If compressed is not defined, assume True 1544 properties["compressed"] = ( 1545 kwargs["compressed"] if "compressed" in kwargs else True 1546 ) 1547 # If enabled is not defined, assume True 1548 properties["enabled"] = kwargs["enabled"] if "enabled" in kwargs else True 1549 properties["trusted"] = kwargs.get("trusted") 1550 _add_new_repo(repo, properties) 1551 1552 if "refresh" in kwargs: 1553 refresh_db() 1554 1555 1556def file_list(*packages, **kwargs): # pylint: disable=unused-argument 1557 """ 1558 List the files that belong to a package. Not specifying any packages will 1559 return a list of _every_ file on the system's package database (not 1560 generally recommended). 1561 1562 CLI Examples: 1563 1564 .. code-block:: bash 1565 1566 salt '*' pkg.file_list httpd 1567 salt '*' pkg.file_list httpd postfix 1568 salt '*' pkg.file_list 1569 """ 1570 output = file_dict(*packages) 1571 files = [] 1572 for package in list(output["packages"].values()): 1573 files.extend(package) 1574 return {"errors": output["errors"], "files": files} 1575 1576 1577def file_dict(*packages, **kwargs): # pylint: disable=unused-argument 1578 """ 1579 List the files that belong to a package, grouped by package. Not 1580 specifying any packages will return a list of _every_ file on the system's 1581 package database (not generally recommended). 1582 1583 CLI Examples: 1584 1585 .. code-block:: bash 1586 1587 salt '*' pkg.file_list httpd 1588 salt '*' pkg.file_list httpd postfix 1589 salt '*' pkg.file_list 1590 """ 1591 errors = [] 1592 ret = {} 1593 cmd_files = ["opkg", "files"] 1594 1595 if not packages: 1596 packages = list(list_pkgs().keys()) 1597 1598 for package in packages: 1599 files = [] 1600 cmd = cmd_files[:] 1601 cmd.append(package) 1602 out = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False) 1603 for line in out["stdout"].splitlines(): 1604 if line.startswith("/"): 1605 files.append(line) 1606 elif line.startswith(" * "): 1607 errors.append(line[3:]) 1608 break 1609 else: 1610 continue 1611 if files: 1612 ret[package] = files 1613 1614 return {"errors": errors, "packages": ret} 1615 1616 1617def owner(*paths, **kwargs): # pylint: disable=unused-argument 1618 """ 1619 Return the name of the package that owns the file. Multiple file paths can 1620 be passed. Like :mod:`pkg.version <salt.modules.opkg.version`, if a single 1621 path is passed, a string will be returned, and if multiple paths are passed, 1622 a dictionary of file/package name pairs will be returned. 1623 1624 If the file is not owned by a package, or is not present on the minion, 1625 then an empty string will be returned for that path. 1626 1627 CLI Example: 1628 1629 .. code-block:: bash 1630 1631 salt '*' pkg.owner /usr/bin/apachectl 1632 salt '*' pkg.owner /usr/bin/apachectl /usr/bin/basename 1633 """ 1634 if not paths: 1635 return "" 1636 ret = {} 1637 cmd_search = ["opkg", "search"] 1638 for path in paths: 1639 cmd = cmd_search[:] 1640 cmd.append(path) 1641 output = __salt__["cmd.run_stdout"]( 1642 cmd, output_loglevel="trace", python_shell=False 1643 ) 1644 if output: 1645 ret[path] = output.split(" - ")[0].strip() 1646 else: 1647 ret[path] = "" 1648 if len(ret) == 1: 1649 return next(iter(ret.values())) 1650 return ret 1651 1652 1653def version_clean(version): 1654 """ 1655 Clean the version string removing extra data. 1656 There's nothing do to here for nipkg.py, therefore it will always 1657 return the given version. 1658 """ 1659 return version 1660 1661 1662def check_extra_requirements(pkgname, pkgver): 1663 """ 1664 Check if the installed package already has the given requirements. 1665 There's nothing do to here for nipkg.py, therefore it will always 1666 return True. 1667 """ 1668 return True 1669