"Fossies" - the Fresh Open Source Software Archive

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


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

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