File add-hold-unhold-functions.patch of Package salt

From 6176ef8aa39626dcb450a1665231a796e9544342 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
Date: Thu, 6 Dec 2018 16:26:23 +0100
Subject: [PATCH] Add hold/unhold functions

Add unhold function

Add warnings
---
 salt/modules/zypperpkg.py | 186 +++++++++++++++++++++++++++-----------
 1 file changed, 131 insertions(+), 55 deletions(-)

diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py
index 44bcbbf2f2..6fa6e3e0a1 100644
--- a/salt/modules/zypperpkg.py
+++ b/salt/modules/zypperpkg.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 """
 Package support for openSUSE via the zypper package manager
 
@@ -12,8 +11,6 @@ Package support for openSUSE via the zypper package manager
 
 """
 
-# Import python libs
-from __future__ import absolute_import, print_function, unicode_literals
 
 import datetime
 import fnmatch
@@ -24,7 +21,6 @@ import time
 from xml.dom import minidom as dom
 from xml.parsers.expat import ExpatError
 
-# Import salt libs
 import salt.utils.data
 import salt.utils.environment
 import salt.utils.event
@@ -35,9 +31,9 @@ import salt.utils.pkg
 import salt.utils.pkg.rpm
 import salt.utils.stringutils
 import salt.utils.systemd
+import salt.utils.versions
 from salt.exceptions import CommandExecutionError, MinionError, SaltInvocationError
 
-# Import 3rd-party libs
 # pylint: disable=import-error,redefined-builtin,no-name-in-module
 from salt.ext import six
 from salt.ext.six.moves import configparser
@@ -51,8 +47,8 @@ log = logging.getLogger(__name__)
 
 HAS_ZYPP = False
 ZYPP_HOME = "/etc/zypp"
-LOCKS = "{0}/locks".format(ZYPP_HOME)
-REPOS = "{0}/repos.d".format(ZYPP_HOME)
+LOCKS = "{}/locks".format(ZYPP_HOME)
+REPOS = "{}/repos.d".format(ZYPP_HOME)
 DEFAULT_PRIORITY = 99
 PKG_ARCH_SEPARATOR = "."
 
@@ -75,7 +71,7 @@ def __virtual__():
     return __virtualname__
 
 
-class _Zypper(object):
+class _Zypper:
     """
     Zypper parallel caller.
     Validates the result and either raises an exception or reports an error.
@@ -339,7 +335,7 @@ class _Zypper(object):
                             attrs=["pid", "name", "cmdline", "create_time"],
                         )
                         data["cmdline"] = " ".join(data["cmdline"])
-                        data["info"] = "Blocking process created at {0}.".format(
+                        data["info"] = "Blocking process created at {}.".format(
                             datetime.datetime.utcfromtimestamp(
                                 data["create_time"]
                             ).isoformat()
@@ -347,7 +343,7 @@ class _Zypper(object):
                         data["success"] = True
                 except Exception as err:  # pylint: disable=broad-except
                     data = {
-                        "info": "Unable to retrieve information about blocking process: {0}".format(
+                        "info": "Unable to retrieve information about blocking process: {}".format(
                             err.message
                         ),
                         "success": False,
@@ -382,7 +378,7 @@ class _Zypper(object):
             )
         if self.error_msg and not self.__no_raise and not self.__ignore_repo_failure:
             raise CommandExecutionError(
-                "Zypper command failure: {0}".format(self.error_msg)
+                "Zypper command failure: {}".format(self.error_msg)
             )
 
         return (
@@ -397,7 +393,7 @@ class _Zypper(object):
 __zypper__ = _Zypper()
 
 
-class Wildcard(object):
+class Wildcard:
     """
     .. versionadded:: 2017.7.0
 
@@ -439,7 +435,7 @@ class Wildcard(object):
                     for vrs in self._get_scope_versions(self._get_available_versions())
                 ]
             )
-            return versions and "{0}{1}".format(self._op or "", versions[-1]) or None
+            return versions and "{}{}".format(self._op or "", versions[-1]) or None
 
     def _get_available_versions(self):
         """
@@ -451,17 +447,15 @@ class Wildcard(object):
         ).getElementsByTagName("solvable")
         if not solvables:
             raise CommandExecutionError(
-                "No packages found matching '{0}'".format(self.name)
+                "No packages found matching '{}'".format(self.name)
             )
 
         return sorted(
-            set(
-                [
-                    slv.getAttribute(self._attr_solvable_version)
-                    for slv in solvables
-                    if slv.getAttribute(self._attr_solvable_version)
-                ]
-            )
+            {
+                slv.getAttribute(self._attr_solvable_version)
+                for slv in solvables
+                if slv.getAttribute(self._attr_solvable_version)
+            }
         )
 
     def _get_scope_versions(self, pkg_versions):
@@ -489,7 +483,7 @@ class Wildcard(object):
         self._op = version.replace(exact_version, "") or None
         if self._op and self._op not in self.Z_OP:
             raise CommandExecutionError(
-                'Zypper do not supports operator "{0}".'.format(self._op)
+                'Zypper do not supports operator "{}".'.format(self._op)
             )
         self.version = exact_version
 
@@ -539,14 +533,11 @@ def list_upgrades(refresh=True, root=None, **kwargs):
     cmd = ["list-updates"]
     if "fromrepo" in kwargs:
         repos = kwargs["fromrepo"]
-        if isinstance(repos, six.string_types):
+        if isinstance(repos, str):
             repos = [repos]
         for repo in repos:
             cmd.extend(
-                [
-                    "--repo",
-                    repo if isinstance(repo, six.string_types) else six.text_type(repo),
-                ]
+                ["--repo", repo if isinstance(repo, str) else str(repo),]
             )
         log.debug("Targeting repos: %s", repos)
     for update_node in (
@@ -610,7 +601,7 @@ def info_installed(*names, **kwargs):
         for _nfo in pkg_nfo:
             t_nfo = dict()
             # Translate dpkg-specific keys to a common structure
-            for key, value in six.iteritems(_nfo):
+            for key, value in _nfo.items():
                 if key == "source_rpm":
                     t_nfo["source"] = value
                 else:
@@ -1033,9 +1024,7 @@ def list_repo_pkgs(*args, **kwargs):
     fromrepo = kwargs.pop("fromrepo", "") or ""
     ret = {}
 
-    targets = [
-        arg if isinstance(arg, six.string_types) else six.text_type(arg) for arg in args
-    ]
+    targets = [arg if isinstance(arg, str) else str(arg) for arg in args]
 
     def _is_match(pkgname):
         """
@@ -1124,7 +1113,7 @@ def _get_repo_info(alias, repos_cfg=None, root=None):
     try:
         meta = dict((repos_cfg or _get_configured_repos(root=root)).items(alias))
         meta["alias"] = alias
-        for key, val in six.iteritems(meta):
+        for key, val in meta.items():
             if val in ["0", "1"]:
                 meta[key] = int(meta[key]) == 1
             elif val == "NONE":
@@ -1197,7 +1186,7 @@ def del_repo(repo, root=None):
                     "message": msg[0].childNodes[0].nodeValue,
                 }
 
-    raise CommandExecutionError("Repository '{0}' not found.".format(repo))
+    raise CommandExecutionError("Repository '{}' not found.".format(repo))
 
 
 def mod_repo(repo, **kwargs):
@@ -1252,13 +1241,13 @@ def mod_repo(repo, **kwargs):
         url = kwargs.get("url", kwargs.get("mirrorlist", kwargs.get("baseurl")))
         if not url:
             raise CommandExecutionError(
-                "Repository '{0}' not found, and neither 'baseurl' nor "
+                "Repository '{}' not found, and neither 'baseurl' nor "
                 "'mirrorlist' was specified".format(repo)
             )
 
         if not _urlparse(url).scheme:
             raise CommandExecutionError(
-                "Repository '{0}' not found and URL for baseurl/mirrorlist "
+                "Repository '{}' not found and URL for baseurl/mirrorlist "
                 "is malformed".format(repo)
             )
 
@@ -1281,7 +1270,7 @@ def mod_repo(repo, **kwargs):
 
             if new_url == base_url:
                 raise CommandExecutionError(
-                    "Repository '{0}' already exists as '{1}'.".format(repo, alias)
+                    "Repository '{}' already exists as '{}'.".format(repo, alias)
                 )
 
         # Add new repo
@@ -1291,7 +1280,7 @@ def mod_repo(repo, **kwargs):
         repos_cfg = _get_configured_repos(root=root)
         if repo not in repos_cfg.sections():
             raise CommandExecutionError(
-                "Failed add new repository '{0}' for unspecified reason. "
+                "Failed add new repository '{}' for unspecified reason. "
                 "Please check zypper logs.".format(repo)
             )
         added = True
@@ -1327,12 +1316,10 @@ def mod_repo(repo, **kwargs):
         cmd_opt.append(kwargs["gpgcheck"] and "--gpgcheck" or "--no-gpgcheck")
 
     if "priority" in kwargs:
-        cmd_opt.append(
-            "--priority={0}".format(kwargs.get("priority", DEFAULT_PRIORITY))
-        )
+        cmd_opt.append("--priority={}".format(kwargs.get("priority", DEFAULT_PRIORITY)))
 
     if "humanname" in kwargs:
-        cmd_opt.append("--name='{0}'".format(kwargs.get("humanname")))
+        cmd_opt.append("--name='{}'".format(kwargs.get("humanname")))
 
     if kwargs.get("gpgautoimport") is True:
         global_cmd_opt.append("--gpg-auto-import-keys")
@@ -1589,7 +1576,7 @@ def install(
 
     if pkg_type == "repository":
         targets = []
-        for param, version_num in six.iteritems(pkg_params):
+        for param, version_num in pkg_params.items():
             if version_num is None:
                 log.debug("targeting package: %s", param)
                 targets.append(param)
@@ -1597,7 +1584,7 @@ def install(
                 prefix, verstr = salt.utils.pkg.split_comparison(version_num)
                 if not prefix:
                     prefix = "="
-                target = "{0}{1}{2}".format(param, prefix, verstr)
+                target = "{}{}{}".format(param, prefix, verstr)
                 log.debug("targeting package: %s", target)
                 targets.append(target)
     elif pkg_type == "advisory":
@@ -1606,7 +1593,7 @@ def install(
         for advisory_id in pkg_params:
             if advisory_id not in cur_patches:
                 raise CommandExecutionError(
-                    'Advisory id "{0}" not found'.format(advisory_id)
+                    'Advisory id "{}" not found'.format(advisory_id)
                 )
             else:
                 # If we add here the `patch:` prefix, the
@@ -1703,7 +1690,7 @@ def install(
 
     if errors:
         raise CommandExecutionError(
-            "Problem encountered {0} package(s)".format(
+            "Problem encountered {} package(s)".format(
                 "downloading" if downloadonly else "installing"
             ),
             info={"errors": errors, "changes": ret},
@@ -1797,7 +1784,7 @@ def upgrade(
         cmd_update.append("--dry-run")
 
     if fromrepo:
-        if isinstance(fromrepo, six.string_types):
+        if isinstance(fromrepo, str):
             fromrepo = [fromrepo]
         for repo in fromrepo:
             cmd_update.extend(["--from" if dist_upgrade else "--repo", repo])
@@ -2052,7 +2039,7 @@ def list_locks(root=None):
                         )
                 if lock.get("solvable_name"):
                     locks[lock.pop("solvable_name")] = lock
-    except IOError:
+    except OSError:
         pass
     except Exception:  # pylint: disable=broad-except
         log.warning("Detected a problem when accessing {}".format(_locks))
@@ -2089,7 +2076,7 @@ def clean_locks(root=None):
     return out
 
 
-def remove_lock(packages, root=None, **kwargs):  # pylint: disable=unused-argument
+def unhold(name=None, pkgs=None, **kwargs):
     """
     Remove specified package lock.
 
@@ -2104,8 +2091,50 @@ def remove_lock(packages, root=None, **kwargs):  # pylint: disable=unused-argume
         salt '*' pkg.remove_lock <package1>,<package2>,<package3>
         salt '*' pkg.remove_lock pkgs='["foo", "bar"]'
     """
+    ret = {}
+    if (not name and not pkgs) or (name and pkgs):
+        raise CommandExecutionError("Name or packages must be specified.")
+    elif name:
+        pkgs = [name]
+
+    locks = list_locks()
+    try:
+        pkgs = list(__salt__["pkg_resource.parse_targets"](pkgs)[0].keys())
+    except MinionError as exc:
+        raise CommandExecutionError(exc)
+
+    removed = []
+    missing = []
+    for pkg in pkgs:
+        if locks.get(pkg):
+            removed.append(pkg)
+            ret[pkg]["comment"] = "Package {} is no longer held.".format(pkg)
+        else:
+            missing.append(pkg)
+            ret[pkg]["comment"] = "Package {} unable to be unheld.".format(pkg)
+
+    if removed:
+        __zypper__.call("rl", *removed)
+
+    return ret
+
+
+def remove_lock(packages, **kwargs):  # pylint: disable=unused-argument
+    """
+    Remove specified package lock.
+
+    CLI Example:
+
+    .. code-block:: bash
 
-    locks = list_locks(root)
+        salt '*' pkg.remove_lock <package name>
+        salt '*' pkg.remove_lock <package1>,<package2>,<package3>
+        salt '*' pkg.remove_lock pkgs='["foo", "bar"]'
+    """
+    salt.utils.versions.warn_until(
+        "Sodium", "This function is deprecated. Please use unhold() instead."
+    )
+    locks = list_locks()
     try:
         packages = list(__salt__["pkg_resource.parse_targets"](packages)[0].keys())
     except MinionError as exc:
@@ -2125,7 +2154,51 @@ def remove_lock(packages, root=None, **kwargs):  # pylint: disable=unused-argume
     return {"removed": len(removed), "not_found": missing}
 
 
-def add_lock(packages, root=None, **kwargs):  # pylint: disable=unused-argument
+def hold(name=None, pkgs=None, **kwargs):
+    """
+    Add a package lock. Specify packages to lock by exact name.
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' pkg.add_lock <package name>
+        salt '*' pkg.add_lock <package1>,<package2>,<package3>
+        salt '*' pkg.add_lock pkgs='["foo", "bar"]'
+
+    :param name:
+    :param pkgs:
+    :param kwargs:
+    :return:
+    """
+    ret = {}
+    if (not name and not pkgs) or (name and pkgs):
+        raise CommandExecutionError("Name or packages must be specified.")
+    elif name:
+        pkgs = [name]
+
+    locks = list_locks()
+    added = []
+    try:
+        pkgs = list(__salt__["pkg_resource.parse_targets"](pkgs)[0].keys())
+    except MinionError as exc:
+        raise CommandExecutionError(exc)
+
+    for pkg in pkgs:
+        ret[pkg] = {"name": pkg, "changes": {}, "result": False, "comment": ""}
+        if not locks.get(pkg):
+            added.append(pkg)
+            ret[pkg]["comment"] = "Package {} is now being held.".format(pkg)
+        else:
+            ret[pkg]["comment"] = "Package {} is already set to be held.".format(pkg)
+
+    if added:
+        __zypper__.call("al", *added)
+
+    return ret
+
+
+def add_lock(packages, **kwargs):  # pylint: disable=unused-argument
     """
     Add a package lock. Specify packages to lock by exact name.
 
@@ -2140,7 +2213,10 @@ def add_lock(packages, root=None, **kwargs):  # pylint: disable=unused-argument
         salt '*' pkg.add_lock <package1>,<package2>,<package3>
         salt '*' pkg.add_lock pkgs='["foo", "bar"]'
     """
-    locks = list_locks(root)
+    salt.utils.versions.warn_until(
+        "Sodium", "This function is deprecated. Please use hold() instead."
+    )
+    locks = list_locks()
     added = []
     try:
         packages = list(__salt__["pkg_resource.parse_targets"](packages)[0].keys())
@@ -2495,7 +2571,7 @@ def search(criteria, refresh=False, **kwargs):
         .getElementsByTagName("solvable")
     )
     if not solvables:
-        raise CommandExecutionError("No packages found matching '{0}'".format(criteria))
+        raise CommandExecutionError("No packages found matching '{}'".format(criteria))
 
     out = {}
     for solvable in solvables:
@@ -2649,13 +2725,13 @@ def download(*packages, **kwargs):
         if failed:
             pkg_ret[
                 "_error"
-            ] = "The following package(s) failed to download: {0}".format(
+            ] = "The following package(s) failed to download: {}".format(
                 ", ".join(failed)
             )
         return pkg_ret
 
     raise CommandExecutionError(
-        "Unable to download packages: {0}".format(", ".join(packages))
+        "Unable to download packages: {}".format(", ".join(packages))
     )
 
 
@@ -2726,7 +2802,7 @@ def diff(*paths, **kwargs):
 
     if pkg_to_paths:
         local_pkgs = __salt__["pkg.download"](*pkg_to_paths.keys(), **kwargs)
-        for pkg, files in six.iteritems(pkg_to_paths):
+        for pkg, files in pkg_to_paths.items():
             for path in files:
                 ret[path] = (
                     __salt__["lowpkg.diff"](local_pkgs[pkg]["path"], path)
-- 
2.29.2
openSUSE Build Service is sponsored by