Blame SOURCES/BZ-1497351-versionlock-add-hint-and-status-cmd.patch

b7ec20
commit 691faec2bed8457edd19b8ad5587768bfbe4c653
b7ec20
Author: Michal Domonkos <mdomonko@redhat.com>
b7ec20
Date:   Mon May 28 16:49:10 2018 +0200
b7ec20
b7ec20
    docs: fix versionlock
b7ec20
    
b7ec20
    Fixes the formatting and clarifies the "exclude" subcommand (inspired
b7ec20
    by: https://illiterat.livejournal.com/8221.html).
b7ec20
b7ec20
diff --git a/docs/yum-versionlock.1 b/docs/yum-versionlock.1
b7ec20
index f7cd467..e14bbd5 100644
b7ec20
--- a/docs/yum-versionlock.1
b7ec20
+++ b/docs/yum-versionlock.1
b7ec20
@@ -16,22 +16,17 @@ This allows you to protect packages from being updated by newer versions.
b7ec20
 The plugin provides a command "versionlock" which allows you to view and edit
b7ec20
 the list of locked packages easily.
b7ec20
 .br
b7ec20
-.I  \fR yum versionlock add <package-wildcard>...
b7ec20
-.PP
b7ec20
+.IP "\fByum versionlock add <package-wildcard>...\fR"
b7ec20
 Add a versionlock for all of the packages in the rpmdb matching the given
b7ec20
 wildcards.
b7ec20
-.I  \fR yum versionlock exclude <package-wildcard>...
b7ec20
-.PP
b7ec20
-Add a exclude (within versionlock) for the latest versions of the
b7ec20
-packages in the available repos. matching the given wildcards.
b7ec20
-.I  \fR yum versionlock list
b7ec20
-.PP
b7ec20
+.IP "\fByum versionlock exclude <package-wildcard>...\fR"
b7ec20
+Opposite; disallow currently available versions of the packages matching the
b7ec20
+given wildcards.
b7ec20
+.IP "\fByum versionlock list\fR"
b7ec20
 List the current versionlock entries.
b7ec20
-.I  \fR yum versionlock delete <entry-wildcard>...
b7ec20
-.PP
b7ec20
+.IP "\fByum versionlock delete <entry-wildcard>...\fR"
b7ec20
 Remove any matching versionlock entries.
b7ec20
-.I  \fR yum versionlock clear
b7ec20
-.PP
b7ec20
+.IP "\fByum versionlock clear\fR"
b7ec20
 Remove all versionlock entries.
b7ec20
 
b7ec20
 .SH FILES
b7ec20
commit f761392dc6209ec17e718291c2319ea011923880
b7ec20
Author: Michal Domonkos <mdomonko@redhat.com>
b7ec20
Date:   Mon May 28 16:55:50 2018 +0200
b7ec20
b7ec20
    versionlock: add hint and "status" subcommand. BZ 1497351
b7ec20
    
b7ec20
    It is nice to be reminded of any versionlocks in effect when running yum
b7ec20
    transactions (esp. when doing a "yum update" and wondering why no
b7ec20
    updates were found).  This commit adds the following message that is
b7ec20
    printed in the excludes hook:
b7ec20
    
b7ec20
      Excluding X updates due to versionlock (use "yum versionlock status"
b7ec20
      to show them)
b7ec20
    
b7ec20
    Since it would be too annoying to have all the excludes printed every
b7ec20
    time, we only show their count (X) and provide the new "status"
b7ec20
    subcommand that allows the user to list them if they are interested.
b7ec20
    
b7ec20
    Note that even this short message might be annoying as it appears on
b7ec20
    every yum run, so we're also adding an option to disable it.
b7ec20
b7ec20
diff --git a/docs/yum-versionlock.1 b/docs/yum-versionlock.1
b7ec20
index e14bbd5..6190fea 100644
b7ec20
--- a/docs/yum-versionlock.1
b7ec20
+++ b/docs/yum-versionlock.1
b7ec20
@@ -24,6 +24,10 @@ Opposite; disallow currently available versions of the packages matching the
b7ec20
 given wildcards.
b7ec20
 .IP "\fByum versionlock list\fR"
b7ec20
 List the current versionlock entries.
b7ec20
+.IP "\fByum versionlock status\fR"
b7ec20
+List any available updates that are currently blocked by versionlock.
b7ec20
+That is, for each entry in the lock list, print the newest package available
b7ec20
+in the repos unless it is the particular locked/excluded version.
b7ec20
 .IP "\fByum versionlock delete <entry-wildcard>...\fR"
b7ec20
 Remove any matching versionlock entries.
b7ec20
 .IP "\fByum versionlock clear\fR"
b7ec20
diff --git a/docs/yum-versionlock.conf.5 b/docs/yum-versionlock.conf.5
b7ec20
index 78a6e49..b5be24e 100644
b7ec20
--- a/docs/yum-versionlock.conf.5
b7ec20
+++ b/docs/yum-versionlock.conf.5
b7ec20
@@ -26,7 +26,7 @@ character to the version.
b7ec20
 .B yum-versionlock.conf(5)
b7ec20
 utilizes configuration options in the form of
b7ec20
 .IP OPTION=VALUE
b7ec20
-.SH OPTION
b7ec20
+.SH OPTIONS
b7ec20
 .IP follow_obsoletes
b7ec20
 This option is a boolean flag which specifies if the versionlock plugin should
b7ec20
 look at all the obsoletes, and see if any of the packages specified have an
b7ec20
@@ -36,13 +36,20 @@ excluded. This option is off by default, as
b7ec20
 will take some time to do the obsoletes processing, and for non-rename
b7ec20
 obsoletes any issues you had which kept you at a specific version of a package
b7ec20
 should be different with another package.
b7ec20
-.SH OPTION
b7ec20
 .IP locklist
b7ec20
 This option is a string with points to the file which will have the versionlock
b7ec20
 information in it. Note that the file
b7ec20
 .B has
b7ec20
 to exist (or the versionlock plugin will make yum exit). However it can be
b7ec20
 empty.
b7ec20
+.IP show_hint
b7ec20
+This option is a boolean flag which specifies if the versionlock plugin should
b7ec20
+print a hint message whenever yum runs saying how many package updates
b7ec20
+available from the repos are being blocked due to versionlocks.
b7ec20
+This hint serves as a reminder that there are locks in effect and that you may
b7ec20
+want to reconsider them since newer versions of those packages have been
b7ec20
+released.
b7ec20
+Default is 1 (show the hint).
b7ec20
 .SH AUTHOR
b7ec20
 .RS
b7ec20
 Chitlesh Goorah <chitlesh@fedoraproject.org>
b7ec20
diff --git a/plugins/versionlock/versionlock.conf b/plugins/versionlock/versionlock.conf
b7ec20
index 4e997da..42e05c9 100644
b7ec20
--- a/plugins/versionlock/versionlock.conf
b7ec20
+++ b/plugins/versionlock/versionlock.conf
b7ec20
@@ -1,5 +1,7 @@
b7ec20
 [main]
b7ec20
 enabled = 1
b7ec20
 locklist = /etc/yum/pluginconf.d/versionlock.list
b7ec20
+#  Show a hint when any locked packages have updates available
b7ec20
+show_hint = 1
b7ec20
 #  Uncomment this to lock out "upgrade via. obsoletes" etc. (slower)
b7ec20
 # follow_obsoletes = 1
b7ec20
diff --git a/plugins/versionlock/versionlock.py b/plugins/versionlock/versionlock.py
b7ec20
index dfe4dd3..ad66855 100644
b7ec20
--- a/plugins/versionlock/versionlock.py
b7ec20
+++ b/plugins/versionlock/versionlock.py
b7ec20
@@ -42,6 +42,9 @@ _version_lock_excluder_B_nevr = set()
b7ec20
 # _version_lock_excluder_pkgtup = set()
b7ec20
 
b7ec20
 fileurl = None
b7ec20
+show_hint = True
b7ec20
+follow_obsoletes = False
b7ec20
+no_exclude = False
b7ec20
 
b7ec20
 def _read_locklist():
b7ec20
     locklist = []
b7ec20
@@ -73,6 +76,68 @@ def _match(ent, patterns):
b7ec20
                 return True
b7ec20
     return False
b7ec20
 
b7ec20
+def _get_updates(base):
b7ec20
+    """Return packages that update or obsolete anything in our locklist.
b7ec20
+
b7ec20
+    Returns a dict of locked_name->X, where X is either a package object or a
b7ec20
+    list of them.  If it's the former, it's the updating package.  If it's the
b7ec20
+    latter, it's the obsoleting packages (since multiple packages may obsolete
b7ec20
+    the same name).
b7ec20
+    """
b7ec20
+
b7ec20
+    updates = {}
b7ec20
+
b7ec20
+    # Read in the locked versions
b7ec20
+    locks = {}
b7ec20
+    for ent in _read_locklist():
b7ec20
+        (n, v, r, e, a) = splitFilename(ent)
b7ec20
+        if e and e[0] == '!':
b7ec20
+            e = e[1:]
b7ec20
+        elif e == '':
b7ec20
+            e = '0'
b7ec20
+        locks.setdefault(n, []).append((e, v, r))
b7ec20
+
b7ec20
+    # Process regular updates
b7ec20
+    #
b7ec20
+    # We are using searchNames() + packagesNewestByName() here instead of just
b7ec20
+    # returnNewestByName() because the former way is much, much faster for big
b7ec20
+    # name lists.
b7ec20
+    #
b7ec20
+    # The problem with returnNewestByName() is that it may easily end up
b7ec20
+    # querying all the packages in pkgSack which is terribly slow (takes
b7ec20
+    # seconds); all it takes is a "-" in a package name and more than
b7ec20
+    # PATTERNS_MAX (8 by default) package names to trigger that.
b7ec20
+    #
b7ec20
+    # Since we know that we only ever deal with names, we can just go straight
b7ec20
+    # to searchNames() to avoid the full query.
b7ec20
+    pkgs = base.pkgSack.searchNames(locks.keys())
b7ec20
+    for p in packagesNewestByName(pkgs):
b7ec20
+        name = p.name
b7ec20
+        evr = p.returnEVR()
b7ec20
+        if (evr.epoch, evr.version, evr.release) in locks[name]:
b7ec20
+            # This one is either the locked or excluded version, skip
b7ec20
+            continue
b7ec20
+        updates[name] = p
b7ec20
+
b7ec20
+    # Process obsoletes
b7ec20
+    tups = base.up.getObsoletesTuples() if follow_obsoletes else []
b7ec20
+    for new, old in tups:
b7ec20
+        nname = new[0]
b7ec20
+        oname = old[0]
b7ec20
+        if oname not in locks:
b7ec20
+            # Not our package, skip
b7ec20
+            continue
b7ec20
+        if nname in locks and new[2:] in locks[nname]:
b7ec20
+            # This one is either the locked or excluded version, skip
b7ec20
+            continue
b7ec20
+        # Only record obsoletes for any given package name
b7ec20
+        if oname not in updates or not isinstance(updates[oname], list):
b7ec20
+            updates[oname] = []
b7ec20
+        p = base.getPackageObject(new)
b7ec20
+        updates[oname].append(p)
b7ec20
+
b7ec20
+    return updates
b7ec20
+
b7ec20
 class VersionLockCommand:
b7ec20
     created = 1247693044
b7ec20
 
b7ec20
@@ -80,7 +145,7 @@ class VersionLockCommand:
b7ec20
         return ["versionlock"]
b7ec20
 
b7ec20
     def getUsage(self):
b7ec20
-        return '[add|exclude|list|delete|clear] [PACKAGE-wildcard]'
b7ec20
+        return '[add|exclude|list|status|delete|clear] [PACKAGE-wildcard]'
b7ec20
 
b7ec20
     def getSummary(self):
b7ec20
         return 'Control package version locks.'
b7ec20
@@ -93,7 +158,7 @@ class VersionLockCommand:
b7ec20
         if extcmds:
b7ec20
             if extcmds[0] not in ('add',
b7ec20
                                   'exclude', 'add-!', 'add!', 'blacklist',
b7ec20
-                                  'list', 'del', 'delete', 'clear'):
b7ec20
+                                  'list', 'status', 'del', 'delete', 'clear'):
b7ec20
                 cmd = 'add'
b7ec20
             else:
b7ec20
                 cmd = {'del'       : 'delete',
b7ec20
@@ -190,6 +255,19 @@ class VersionLockCommand:
b7ec20
             os.rename(tmpfilename, filename)
b7ec20
             return 0, ['versionlock deleted: ' + str(count)]
b7ec20
 
b7ec20
+        if cmd == 'status':
b7ec20
+            global no_exclude
b7ec20
+            no_exclude = True
b7ec20
+            updates = _get_updates(base)
b7ec20
+            for name, value in updates.iteritems():
b7ec20
+                if isinstance(value, list):
b7ec20
+                    value = set(p.envr + '.*' for p in value)
b7ec20
+                    for v in value:
b7ec20
+                        print '%s (replacing %s)' % (v, name)
b7ec20
+                    continue
b7ec20
+                print value.envr + '.*'
b7ec20
+            return 0, ['versionlock status done']
b7ec20
+
b7ec20
         assert cmd == 'list'
b7ec20
         for ent in _read_locklist():
b7ec20
             print ent
b7ec20
@@ -201,8 +279,12 @@ class VersionLockCommand:
b7ec20
 
b7ec20
 def config_hook(conduit):
b7ec20
     global fileurl
b7ec20
+    global follow_obsoletes
b7ec20
+    global show_hint
b7ec20
 
b7ec20
     fileurl = conduit.confString('main', 'locklist')
b7ec20
+    follow_obsoletes = conduit.confBool('main', 'follow_obsoletes', default=False)
b7ec20
+    show_hint = conduit.confBool('main', 'show_hint', default=True)
b7ec20
 
b7ec20
     if hasattr(conduit._base, 'registerCommand'):
b7ec20
         conduit.registerCommand(VersionLockCommand())
b7ec20
@@ -227,6 +309,9 @@ def _add_versionlock_blacklist(conduit):
b7ec20
     ape(None, exid + str(3), 'exclude.marked')
b7ec20
 
b7ec20
 def exclude_hook(conduit):
b7ec20
+    if no_exclude:
b7ec20
+        return
b7ec20
+
b7ec20
     conduit.info(3, 'Reading version lock configuration')
b7ec20
 
b7ec20
     if not fileurl:
b7ec20
@@ -250,8 +335,7 @@ def exclude_hook(conduit):
b7ec20
         _version_lock_excluder_n.add(n)
b7ec20
         _version_lock_excluder_nevr.add("%s-%s:%s-%s" % (n, e, v, r))
b7ec20
 
b7ec20
-    if (_version_lock_excluder_n and
b7ec20
-        conduit.confBool('main', 'follow_obsoletes', default=False)):
b7ec20
+    if (_version_lock_excluder_n and follow_obsoletes):
b7ec20
         #  If anything obsoletes something that we have versionlocked ... then
b7ec20
         # remove all traces of that too.
b7ec20
         for (pkgtup, instTup) in conduit._base.up.getObsoletesTuples():
b7ec20
@@ -259,6 +343,18 @@ def exclude_hook(conduit):
b7ec20
                 continue
b7ec20
             _version_lock_excluder_n.add(pkgtup[0].lower())
b7ec20
 
b7ec20
+    total = len(_get_updates(conduit._base)) if show_hint else 0
b7ec20
+    if total:
b7ec20
+        if total > 1:
b7ec20
+            suffix = 's'
b7ec20
+            what = 'them'
b7ec20
+        else:
b7ec20
+            suffix = ''
b7ec20
+            what = 'it'
b7ec20
+        conduit.info(2, 'Excluding %d update%s due to versionlock '
b7ec20
+                        '(use "yum versionlock status" to show %s)'
b7ec20
+                        % (total, suffix, what))
b7ec20
+
b7ec20
     if _version_lock_excluder_n:
b7ec20
         _add_versionlock_whitelist(conduit)
b7ec20
     if _version_lock_excluder_B_nevr: