$OpenBSD: patch-salt_modules_openbsdpkg_py,v 1.2 2018/04/25 10:02:48 jasper Exp $

- fix `latest_version` for installed packages
- have purge pass '-cqq' to pkg_delete
- implement upgrading packages

Index: salt/modules/openbsdpkg.py
--- salt/modules/openbsdpkg.py.orig
+++ salt/modules/openbsdpkg.py
@@ -100,8 +100,13 @@ def list_pkgs(versions_as_list=False, **kwargs):
 
 def latest_version(*names, **kwargs):
     '''
-    The available version of the package in the repository
+    Return the latest version of the named package available for upgrade or
+    installation. If more than one package name is specified, a dict of
+    name/version pairs is returned.
 
+    If the latest version of a given package is already installed, an empty
+    string will be returned for that package.
+
     CLI Example:
 
     .. code-block:: bash
@@ -116,20 +121,43 @@ def latest_version(*names, **kwargs):
     for name in names:
         ret[name] = ''
 
-    cmd = 'pkg_info -q -I {0}'.format(' '.join(names))
-    out = __salt__['cmd.run_stdout'](cmd, python_shell=False, output_loglevel='trace')
-    for line in out.splitlines():
-        try:
-            pkgname, pkgver, flavor = __PKG_RE.match(line).groups()
-        except AttributeError:
-            continue
-        pkgname += '--{0}'.format(flavor) if flavor else ''
-        cur = pkgs.get(pkgname, '')
-        if not cur or salt.utils.versions.compare(ver1=cur,
-                                                  oper='<',
-                                                  ver2=pkgver):
-            ret[pkgname] = pkgver
+        # Query the repository for the package name
+        cmd = 'pkg_info -Q {0}'.format(name)
+        out = __salt__['cmd.run_stdout'](cmd, python_shell=False, output_loglevel='trace')
+ 
+        # Since we can only query instead of request the specific package
+        # we'll have to go through the returned list and find what we
+        # were looking for.
+        # Keep in mind the match may be flavored.
+        for line in out.splitlines():
+            try:
+                pkgname, pkgver, flavor = __PKG_RE.match(line).groups()
+            except AttributeError:
+                continue
 
+            match = re.match(r'.*\(installed\)$', pkgver)
+            if match:
+                # Package is explicitly marked as installed already,
+                # so skip any further comparison and move on to the
+                # next package to compare (if provided).
+                break
+
+            # First check if we need to look for flavors before
+            # looking at unflavored packages.
+            if "{0}--{1}".format(pkgname, flavor) == name:
+                pkgname += '--{0}'.format(flavor)
+            elif pkgname == name:
+                pass
+            else:
+                # No match just move on.
+                continue
+
+            cur = pkgs.get(pkgname, '')
+            if not cur or salt.utils.versions.compare_versions(ver1=cur,
+                                                      oper='<',
+                                                      ver2=pkgver):
+                ret[pkgname] = pkgver
+
     # Return a string if only one package name passed
     if len(names) == 1:
         return ret[names[0]]
@@ -221,7 +249,7 @@ def install(name=None, pkgs=None, sources=None, **kwar
     return ret
 
 
-def remove(name=None, pkgs=None, **kwargs):
+def remove(name=None, pkgs=None, purge=False, **kwargs):
     '''
     Remove a single package with pkg_delete
 
@@ -255,8 +283,13 @@ def remove(name=None, pkgs=None, **kwargs):
     if not targets:
         return {}
 
-    cmd = 'pkg_delete -xD dependencies {0}'.format(' '.join(targets))
+    cmd = ['pkg_delete', '-Ix', '-Ddependencies']
 
+    if purge:
+        cmd.append('-cqq')
+
+    cmd.extend(targets)
+
     out = __salt__['cmd.run_all'](
         cmd,
         python_shell=False,
@@ -282,8 +315,7 @@ def remove(name=None, pkgs=None, **kwargs):
 
 def purge(name=None, pkgs=None, **kwargs):
     '''
-    Package purges are not supported, this function is identical to
-    ``remove()``.
+    Remove a package and extra configuration files.
 
     name
         The name of the package to be deleted.
@@ -308,4 +340,70 @@ def purge(name=None, pkgs=None, **kwargs):
         salt '*' pkg.purge <package1>,<package2>,<package3>
         salt '*' pkg.purge pkgs='["foo", "bar"]'
     '''
-    return remove(name=name, pkgs=pkgs)
+    return remove(name=name, pkgs=pkgs, purge=True)
+
+
+def upgrade_available(name):
+    '''
+    Check whether or not an upgrade is available for a given package
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' pkg.upgrade_available <package name>
+    '''
+    return latest_version(name) != ''
+
+
+def upgrade(name=None,
+            pkgs=None,
+            **kwargs):
+    '''
+    Run a full package upgrade (``pkg_add -u``), or upgrade a specific package
+    if ``name`` or ``pkgs`` is provided.
+    ``name`` is ignored when ``pkgs`` is specified.
+
+    Returns a dictionary containing the changes:
+
+    .. code-block:: python
+
+        {'<package>': {'old': '<old-version>',
+                       'new': '<new-version>'}}
+
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' pkg.upgrade
+        salt '*' pkg.upgrade python%2.7
+    '''
+    old = list_pkgs()
+
+    cmd = ['pkg_add',  '-Ix', '-u']
+
+    if kwargs.get('noop', False):
+        cmd.append('-n')
+
+    if pkgs:
+        cmd.extend(pkgs)
+    elif name:
+        cmd.append(name)
+
+    # Now run the upgrade, compare the list of installed packages before and
+    # after and we have all the info we need.
+    result = __salt__['cmd.run_all'](cmd, output_loglevel='trace',
+                                     python_shell=False)
+
+    __context__.pop('pkg.list_pkgs', None)
+    new = list_pkgs()
+    ret = salt.utils.compare_dicts(old, new)
+
+    if result['retcode'] != 0:
+        raise CommandExecutionError(
+                'Problem encountered upgrading packages',
+                info={'changes': ret, 'result': result}
+        )
+
+    return ret
