[yum-commits] 6 commits - docs/Makefile docs/yum-cron.8 etc/Makefile etc/yum-cron.conf yum-cron/cleanup.yum yum-cron/Makefile yum-cron/update.yum yum-cron/yum-cleanup.cron.sh yum-cron/yum-cron.py yum-cron/yum-cron.sh yum-cron/yum-cron.sysconfig yum-cron/yum-cron.sysvinit yum-cron/yum-update.cron.sh yum.spec

zpavlas at osuosl.org zpavlas at osuosl.org
Thu Oct 25 15:24:03 UTC 2012


 docs/Makefile                |    1 
 docs/yum-cron.8              |   49 +
 etc/Makefile                 |    5 
 etc/yum-cron.conf            |   55 ++
 yum-cron/Makefile            |    8 
 yum-cron/cleanup.yum         |    4 
 yum-cron/update.yum          |    3 
 yum-cron/yum-cleanup.cron.sh |   30 -
 yum-cron/yum-cron.py         | 1113 +++++++++++++++++++++++++++++++++++++++++++
 yum-cron/yum-cron.sh         |  170 ------
 yum-cron/yum-cron.sysconfig  |   92 ---
 yum-cron/yum-cron.sysvinit   |    7 
 yum-cron/yum-update.cron.sh  |   26 -
 yum.spec                     |    9 
 14 files changed, 1227 insertions(+), 345 deletions(-)

New commits:
commit 64f26faced403af46a8df135af3623bcea205600
Author: Zdeněk Pavlas <zpavlas at redhat.com>
Date:   Thu Oct 25 16:21:58 2012 +0200

    Enable background downloading
    
    When not applying the updates, turn on background downloading.
    Send messages that download was succesfull in sys.exit(0) path.

diff --git a/yum-cron/yum-cron.py b/yum-cron/yum-cron.py
index 05c87fb..44fc785 100755
--- a/yum-cron/yum-cron.py
+++ b/yum-cron/yum-cron.py
@@ -932,16 +932,17 @@ class YumCronBase(yum.YumBase):
                                             self.tsInfo.getMembers()))
         try:
             # Download the updates
+            self.conf.download_only = not self.opts.apply_updates
             self.downloadPkgs(dlpkgs)
         except Exception, e:
             self.emitDownloadFailed("%s" % e)
             sys.exit(1)
-        else :
-            # Emit a message that the packages have been downloaded
-            # successfully
-            if emit :
+        except SystemExit, e:
+            if e.code == 0:
+                # Emit a message that the packages have been downloaded
                 self.emitDownloaded()
                 self.emitMessages()
+            raise
 
     def installUpdates(self, emit):
         """Apply the available updates.
commit f67f8bae744a799be52ac934e7123715017dd266
Author: Zdeněk Pavlas <zpavlas at redhat.com>
Date:   Thu Oct 25 15:06:34 2012 +0200

    minor fixes, some cleanup
    
    - fix locking issue when running yum-cron as non-root user
    - drop nonroot_workdir option, use default userdir logic
    - update default paths, fix some typos

diff --git a/yum-cron/yum-cron.py b/yum-cron/yum-cron.py
index d89786d..05c87fb 100755
--- a/yum-cron/yum-cron.py
+++ b/yum-cron/yum-cron.py
@@ -1,7 +1,6 @@
 #!/usr/bin/python -tt
 import os
 import sys
-import time
 import gzip
 from socket import gethostname
 
@@ -23,7 +22,7 @@ from time import sleep
 sys.path.append('/usr/share/yum-cli')
 import callback
 
-default_config_file = '/home/nick/yum/new-yum-cron/yum-cron.conf'
+default_config_file = '/etc/yum/yum-cron.conf'
 
 class UpdateEmitter(object):
     """Abstract class for implementing different types of emitters.
@@ -648,7 +647,7 @@ class EmailEmitter(UpdateEmitter):
         :param errmsgs: a string that contains the error message
         """
         self.subject = "Yum: Failed to download updates on %s" % self.opts.system_name
-        super(EmailEmitter, self).lockFailed(errmsg)
+        super(EmailEmitter, self).downloadFailed(errmsg)
 
     def updatesFailed(self, errmsg):
         """Append a message to the output list stating that installing
@@ -694,7 +693,6 @@ class YumCronConfig(BaseConfig):
     """Class to parse configuration information from the config file, and
     to store this information.
     """
-    nonroot_workdir = Option("/var/tmp/yum-cron")
     system_name = Option(gethostname())
     output_width = IntOption(80)
     random_sleep = IntOption(0)
@@ -795,9 +793,7 @@ class YumCronBase(yum.YumBase):
 
             # if we are not root do the special subdir thing
             if os.geteuid() != 0:
-                if not os.path.exists(self.opts.nonroot_workdir):
-                    os.makedirs(self.opts.nonroot_workdir)
-                self.repos.setCacheDir(self.opts.nonroot_workdir)
+                self.setCacheDir()
 
             # Create the configuration
             self.conf
commit c857fe178b431ed378ff7e48d031374f476843c2
Author: Zdeněk Pavlas <zpavlas at redhat.com>
Date:   Thu Oct 25 15:01:06 2012 +0200

    0yum-update.cron is not a config file

diff --git a/yum.spec b/yum.spec
index 728071f..a309594 100644
--- a/yum.spec
+++ b/yum.spec
@@ -281,7 +281,7 @@ exit 0
 %files cron
 %defattr(-,root,root)
 %doc COPYING
-%config(noreplace) %{_sysconfdir}/cron.daily/0yum-update.cron
+%{_sysconfdir}/cron.daily/0yum-update.cron
 %config(noreplace) %{_sysconfdir}/yum/yum-cron.conf
 %{_sysconfdir}/rc.d/init.d/yum-cron
 %{_sbindir}/yum-cron
commit 52eaca4291678aad3c7499eddea60c1b89134359
Author: Zdeněk Pavlas <zpavlas at redhat.com>
Date:   Thu Oct 25 14:56:42 2012 +0200

    yum-updatesd.conf is not executable

diff --git a/etc/Makefile b/etc/Makefile
index 0c7b9f0..ec5af83 100644
--- a/etc/Makefile
+++ b/etc/Makefile
@@ -24,9 +24,7 @@ install:
 
 	mkdir -p $(DESTDIR)/etc/dbus-1/system.d/
 	install -m 755 yum-updatesd-dbus.conf $(DESTDIR)/etc/dbus-1/system.d/yum-updatesd.conf
-
-	install -m 755 yum-updatesd.conf $(DESTDIR)/etc/yum/yum-updatesd.conf
-
+	install -m 644 yum-updatesd.conf $(YUMETC)
 	mkdir -p $(DESTDIR)/etc/bash_completion.d
 	install -m 644 yum.bash $(DESTDIR)/etc/bash_completion.d
 	install -m 644 yum-cron.conf $(YUMETC)
commit 82f2f1b6c6315f1636d2453bf4ac1d77d0c5e53f
Author: Zdeněk Pavlas <zpavlas at redhat.com>
Date:   Thu Oct 25 14:45:36 2012 +0200

    yum-cron: add new yum-cron.py
    
    - add yum-cron.8, yum-cron.conf
    - yum-cron.py replaces yum-cron.sh

diff --git a/docs/Makefile b/docs/Makefile
index dc31d0e..8cc7f9f 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -13,3 +13,4 @@ install:
 	install -m 644 yum.conf.5 $(DESTDIR)/usr/share/man/man5/yum.conf.5
 	install -m 644 yum-updatesd.8 $(DESTDIR)/usr/share/man/man8/yum-updatesd.8
 	install -m 644 yum-updatesd.conf.5 $(DESTDIR)/usr/share/man/man5/yum-updatesd.conf.5
+	install -m 644 yum-cron.8 $(DESTDIR)/usr/share/man/man8
diff --git a/docs/yum-cron.8 b/docs/yum-cron.8
new file mode 100644
index 0000000..4d01a5a
--- /dev/null
+++ b/docs/yum-cron.8
@@ -0,0 +1,49 @@
+.\" yum-cron - cron interface for yum
+.TH "yum-cron" "8" ""  "Nick Jacek" ""
+.SH "NAME"
+yum-cron \- an interface to convieniently call yum from cron
+
+.SH "SYNOPSIS"
+\fByum-cron\fP [config-file]
+
+.SH "DESCRIPTION"
+.PP 
+\fByum-cron\fP is an alternate interface to yum that is optimised to
+be convenient to call from cron.  It provides methods to keep
+repository metadata up to date, and to check for, download, and apply
+updates.  Rather than accepting many different command line arguments,
+the different functions of yum-cron can be accessed through config
+files.
+.PP 
+\fIconfig-file\fP is used to optionally specify the path to the
+configuration file to use.  If it is not given, the default
+configuration file will be used.  It is useful to be able to specify
+different configuration files for different use cases.  For example,
+one configuration file might be set to update the repository metadata,
+and a line could be added to the crontab to run yum-cron frequently
+using this file.  Then, another configuration file might be set to
+install updates, and yum-cron could be run from cron using this file
+just once each day.
+
+.SH "FILES"
+.nf
+/etc/yum-cron.conf
+.fi 
+
+.PP
+.SH "SEE ALSO"
+.nf
+.I yum (8)
+.fi
+
+.PP
+.SH "AUTHORS"
+.nf
+See the Authors file included with this program.
+.fi
+
+.PP
+.SH "BUGS"
+There of course aren't any bugs, but if you find any, you should email
+ the mailing list, yum at lists.baseurl.org, or consult bugzilla.
+.fi
diff --git a/etc/Makefile b/etc/Makefile
index a512cdf..0c7b9f0 100644
--- a/etc/Makefile
+++ b/etc/Makefile
@@ -29,3 +29,4 @@ install:
 
 	mkdir -p $(DESTDIR)/etc/bash_completion.d
 	install -m 644 yum.bash $(DESTDIR)/etc/bash_completion.d
+	install -m 644 yum-cron.conf $(YUMETC)
diff --git a/etc/yum-cron.conf b/etc/yum-cron.conf
new file mode 100644
index 0000000..269c6b7
--- /dev/null
+++ b/etc/yum-cron.conf
@@ -0,0 +1,55 @@
+[commands]
+# Whether a message should emitted when updates are available.
+update_messages = yes
+
+# Whether updates should be downloaded when they are available. Note
+# that updates_messages must also be yes for updates to be downloaded.
+download_updates = no
+
+# Whether updates should be applied when they are available.  Note
+# that both update_messages and download_updates must also be yes for
+# the update to be applied
+apply_updates = no
+
+# Maximum amout of time to randomly sleep, in minutes.  The program
+# will sleep for a random amount of time between 0 and random_sleep
+# minutes before running.  This is useful for e.g. staggering the
+# times that multiple systems will access update servers.  If
+# random_sleep is 0 or negative, the program will run immediately.
+random_sleep = 0
+
+
+[emitters]
+# Name to use for this system in messages that are emitted.  If
+# system_name is None, the hostname will be used.
+system_name = None
+
+# How to send messages.  Valid options are stdio and email.  If
+# emit_via includes stdio, messages will be sent to stdout; this is useful
+# to have cron send the messages.  If emit_via includes email, this
+# program will send email itself according to the configured options.
+# If emit_via is None or left blank, no messages will be sent.
+emit_via = stdio
+
+# The width, in characters, that messages that are emitted should be
+# formatted to.
+ouput_width = 80
+
+
+[email]
+# The address to send email messages from.
+email_from = root
+
+# List of addresses to send messages to.
+email_to = root
+
+# Name of the host to connect to to send email messages.
+email_host = localhost
+
+
+[groups]
+# List of groups to update
+group_list = None
+
+# The types of group packages to install
+group_package_types = mandatory, default
diff --git a/yum-cron/Makefile b/yum-cron/Makefile
index 4c26969..1a68a3c 100644
--- a/yum-cron/Makefile
+++ b/yum-cron/Makefile
@@ -12,4 +12,4 @@ install:
 # manpage update, mlocate, and prelink
 	install -D -m 755 yum-update.cron.sh $(DESTDIR)/etc/cron.daily/0yum-update.cron
 	install -D -m 755 yum-cron.sysvinit $(DESTDIR)/etc/rc.d/init.d/yum-cron
-	install -D -m 755 yum-cron.sh $(DESTDIR)/usr/sbin/yum-cron
+	install -D -m 755 yum-cron.py $(DESTDIR)/usr/sbin/yum-cron
diff --git a/yum-cron/yum-cron.py b/yum-cron/yum-cron.py
new file mode 100755
index 0000000..d89786d
--- /dev/null
+++ b/yum-cron/yum-cron.py
@@ -0,0 +1,1116 @@
+#!/usr/bin/python -tt
+import os
+import sys
+import time
+import gzip
+from socket import gethostname
+
+import yum
+import yum.Errors
+from yum.config import BaseConfig, Option, IntOption, ListOption, BoolOption
+from yum.parser import ConfigPreProcessor
+from ConfigParser import ConfigParser, ParsingError
+from yum.constants import *
+from yum.update_md import UpdateMetadata
+from email.mime.text import MIMEText
+from yum.i18n import to_str, to_utf8, to_unicode, utf8_width, utf8_width_fill, utf8_text_fill
+from yum import  _, P_
+import smtplib
+from random import random
+from time import sleep
+
+# FIXME: is it really sane to use this from here?
+sys.path.append('/usr/share/yum-cli')
+import callback
+
+default_config_file = '/home/nick/yum/new-yum-cron/yum-cron.conf'
+
+class UpdateEmitter(object):
+    """Abstract class for implementing different types of emitters.
+       Most methods will add certain messages the output list.  Then,
+       the sendMessage method can be overridden in a subclass to
+       combine these messages and transmit them as required.
+    """
+
+    def __init__(self, opts):
+        self.opts  = opts
+        self.output = []
+
+    def updatesAvailable(self, tsInfo):
+        """Appends a message to the output list stating that there are
+        updates available.
+
+        :param tsInfo: A :class:`yum.transactioninfo.TransactionData`
+           instance that contains information about the transaction.
+        """
+        self.output.append('The following updates are available on %s:' % self.opts.system_name)
+        self.output.append(self._formatTransaction(tsInfo))
+
+    def updatesDownloading(self, tsInfo):
+        """Append a message to the output list stating that
+        downloading updates has started.
+
+        :param tsInfo: A :class:`yum.transactioninfo.TransactionData`
+           instance that contains information about the transaction.
+        """
+        self.output.append('The following updates will be downloaded on %s:' % self.opts.system_name)
+        self.output.append(self._formatTransaction(tsInfo))
+
+    def updatesDownloaded(self):
+        """Append a message to the output list stating that updates
+        have been downloaded successfully.
+        """
+        self.output.append("Updates downloaded successfully.")
+
+    def updatesInstalling(self, tsInfo):
+        """Append a message to the output list stating that
+        installing updates has started.
+
+        :param tsInfo: A :class:`yum.transactioninfo.TransactionData`
+           instance that contains information about the transaction.
+        """
+        self.output.append('The following updates will be applied on %s:' % self.opts.system_name)
+        self.output.append(self._formatTransaction(tsInfo))
+
+    def updatesInstalled(self):
+        """Append a message to the output list stating that updates
+        have been installed successfully.
+        """
+        self.output.append('The updates were sucessfully applied')
+
+    def setupFailed(self, errmsg):
+        """Append a message to the output list stating that setup
+        failed, and then call sendMessages to emit the output.
+
+        :param errmsgs: a string that contains the error message
+        """
+        self.output.append("Plugins failed to initialize with the following error message: \n%s" 
+                           % errmsg)
+        self.sendMessages()
+
+    def lockFailed(self, errmsg):
+        """Append a message to the output list stating that the
+        program failed to acquire the yum lock, then call sendMessages
+        to emit the output.
+
+        :param errmsg: a string that contains the error message
+        """
+        self.output.append("Failed to acquire the yum lock with the following error message: \n%s"
+                           % errmsg)
+        self.sendMessages()
+
+    def checkFailed(self, errmsg):
+        """Append a message to the output stating that checking for
+        updates failed, then call sendMessages to emit the output.
+
+        :param errmsgs: a string that contains the error message
+        """
+        self.output.append("Failed to check for updates with the following error message: \n%s" 
+                           % errmsg)
+        self.sendMessages()
+
+    def groupError(self, errmsg):
+        """Append a message to the output list stating that an error
+        was encountered while checking for group updates.
+
+        :param errmsgs: a string that contains the error message
+        """
+        self.output.append("Error checking for group updates: \n%s" 
+                           % errmsg)
+
+    def groupFailed(self, errmsg):
+        """Append a message to the output list stating that checking
+        for group updates failed, then call sendMessages to emit the output.
+
+        :param errmsgs: a string that contains the error message
+        """
+        self.output.append("Failed to check for updates with the following error message: \n%s" 
+                           % errmsg)
+        self.sendMessages()
+
+    def downloadFailed(self, errmsg):
+        """Append a message to the output list stating that
+        downloading updates failed, then call sendMessages to emit the output.
+
+        :param errmsgs: a string that contains the error message
+        """
+        self.output.append("Updates failed to download with the following error message: \n%s"
+                      % errmsg)
+        self.sendMessages()
+        
+    def updatesFailed(self, errmsg):
+        """Append a message to the output list stating that installing
+        updates failed, then call sendMessages to emit the output.
+
+        :param errmsgs: a string that contains the error message
+        """
+        self.output.append("Updates failed to install with the following error message: \n%s"
+                      % errmsg)
+        self.sendMessages()
+
+    def sendMessages(self):
+        """Send the messages that have been stored.  This should be
+        overridden by inheriting classes to emit the messages
+        according to their individual methods.
+        """
+        pass
+
+    def _format_number(self, number, SI=0, space=' '):
+        """Return a human-readable metric-like string representation
+        of a number.
+
+        :param number: the number to be converted to a human-readable form
+        :param SI: If is 0, this function will use the convention
+           that 1 kilobyte = 1024 bytes, otherwise, the convention
+           that 1 kilobyte = 1000 bytes will be used
+        :param space: string that will be placed between the number
+           and the SI prefix
+        :return: a human-readable metric-like string representation of
+           *number*
+        """
+        symbols = [ ' ', # (none)
+                    'k', # kilo
+                    'M', # mega
+                    'G', # giga
+                    'T', # tera
+                    'P', # peta
+                    'E', # exa
+                    'Z', # zetta
+                    'Y'] # yotta
+    
+        if SI: step = 1000.0
+        else: step = 1024.0
+    
+        thresh = 999
+        depth = 0
+        max_depth = len(symbols) - 1
+    
+        # we want numbers between 0 and thresh, but don't exceed the length
+        # of our list.  In that event, the formatting will be screwed up,
+        # but it'll still show the right number.
+        while number > thresh and depth < max_depth:
+            depth  = depth + 1
+            number = number / step
+    
+        if type(number) == type(1) or type(number) == type(1L):
+            format = '%i%s%s'
+        elif number < 9.95:
+            # must use 9.95 for proper sizing.  For example, 9.99 will be
+            # rounded to 10.0 with the .1f format string (which is too long)
+            format = '%.1f%s%s'
+        else:
+            format = '%.0f%s%s'
+    
+        return(format % (float(number or 0), space, symbols[depth]))
+
+    def _fmtColumns(self, columns, msg=u'', end=u'', text_width=utf8_width):
+        """Return a row of data formatted into a string for output.
+        Items can overflow their columns. 
+
+        :param columns: a list of tuples containing the data to
+           output.  Each tuple contains first the item to be output,
+           then the amount of space allocated for the column, and then
+           optionally a type of highlighting for the item
+        :param msg: a string to begin the line of output with
+        :param end: a string to end the line of output with
+        :param text_width: a function to find the width of the items
+           in the columns.  This defaults to utf8 but can be changed
+           to len() if you know it'll be fine
+        :return: a row of data formatted into a string for output
+        """
+        total_width = len(msg)
+        data = []
+        for col_data in columns[:-1]:
+            (val, width) = col_data
+
+            if not width: # Don't count this column, invisible text
+                msg += u"%s"
+                data.append(val)
+                continue
+
+            (align, width) = self._fmt_column_align_width(width)
+            val_width = text_width(val)
+            if val_width <= width:
+                #  Don't use utf8_width_fill() because it sucks performance
+                # wise for 1,000s of rows. Also allows us to use len(), when
+                # we can.
+                msg += u"%s%s "
+                if (align == u'-'):
+                    data.extend([val, " " * (width - val_width)])
+                else:
+                    data.extend([" " * (width - val_width), val])
+            else:
+                msg += u"%s\n" + " " * (total_width + width + 1)
+                data.append(val)
+            total_width += width
+            total_width += 1
+        (val, width) = columns[-1]
+        (align, width) = self._fmt_column_align_width(width)
+        val = utf8_width_fill(val, width, left=(align == u'-'))
+        msg += u"%%s%s" % end
+        data.append(val)
+        return msg % tuple(data)
+
+    def _calcColumns(self, data, total_width, columns=None, remainder_column=0, indent=''):
+        """Dynamically calculate the widths of the columns that the
+        fields in data should be placed into for output.
+        
+        :param data: a list of dictionaries that represent the data to
+           be output.  Each dictionary in the list corresponds to annn
+           column of output. The keys of the dictionary are the
+           lengths of the items to be output, and the value associated
+           with a key is the number of items of that length.
+        :param total_width: the total width of the output.
+        :param columns: a list containing the minimum amount of space
+           that must be allocated for each row. This can be used to
+           ensure that there is space available in a column if, for
+           example, the actual lengths of the items being output
+           cannot be given in *data*
+        :param remainder_column: number of the column to receive a few
+           extra spaces that may remain after other allocation has
+           taken place
+        :param indent: string that will be prefixed to a line of
+           output to create e.g. an indent
+        :return: a list of the widths of the columns that the fields
+           in data should be placed into for output
+        """
+        if total_width is None:
+            total_width = self.term.columns
+
+        cols = len(data)
+        # Convert the data to ascending list of tuples, (field_length, pkgs)
+        pdata = data
+        data  = [None] * cols # Don't modify the passed in data
+        for d in range(0, cols):
+            data[d] = sorted(pdata[d].items())
+
+        #  We start allocating 1 char to everything but the last column, and a
+        # space between each (again, except for the last column). Because
+        # at worst we are better with:
+        # |one two three|
+        # | four        |
+        # ...than:
+        # |one two three|
+        # |            f|
+        # |our          |
+        # ...the later being what we get if we pre-allocate the last column, and
+        # thus. the space, due to "three" overflowing it's column by 2 chars.
+        if columns is None:
+            columns = [1] * (cols - 1)
+            columns.append(0)
+
+        total_width -= (sum(columns) + (cols - 1) +
+                        utf8_width(indent))
+        if not columns[-1]:
+            total_width += 1
+        while total_width > 0:
+            # Find which field all the spaces left will help best
+            helps = 0
+            val   = 0
+            for d in xrange(0, cols):
+                thelps = self._calc_columns_spaces_helps(columns[d], data[d],
+                                                         total_width)
+                if not thelps:
+                    continue
+                #  We prefer to overflow: the last column, and then earlier
+                # columns. This is so that in the best case (just overflow the
+                # last) ... grep still "works", and then we make it prettier.
+                if helps and (d == (cols - 1)) and (thelps / 2) < helps:
+                    continue
+                if thelps < helps:
+                    continue
+                helps = thelps
+                val   = d
+
+            #  If we found a column to expand, move up to the next level with
+            # that column and start again with any remaining space.
+            if helps:
+                diff = data[val].pop(0)[0] - columns[val]
+                if not columns[val] and (val == (cols - 1)):
+                    #  If we are going from 0 => N on the last column, take 1
+                    # for the space before the column.
+                    total_width  -= 1
+                columns[val] += diff
+                total_width  -= diff
+                continue
+
+            overflowed_columns = 0
+            for d in xrange(0, cols):
+                if not data[d]:
+                    continue
+                overflowed_columns += 1
+            if overflowed_columns:
+                #  Split the remaining spaces among each overflowed column
+                # equally
+                norm = total_width / overflowed_columns
+                for d in xrange(0, cols):
+                    if not data[d]:
+                        continue
+                    columns[d] += norm
+                    total_width -= norm
+
+            #  Split the remaining spaces among each column equally, except the
+            # last one. And put the rest into the remainder column
+            cols -= 1
+            norm = total_width / cols
+            for d in xrange(0, cols):
+                columns[d] += norm
+            columns[remainder_column] += total_width - (cols * norm)
+            total_width = 0
+
+        return columns
+
+    @staticmethod
+    def _fmt_column_align_width(width):
+        if width < 0:
+            return (u"-", -width)
+        return (u"", width)
+
+    @staticmethod
+    def _calc_columns_spaces_helps(current, data_tups, left):
+        """ Spaces left on the current field will help how many pkgs? """
+        ret = 0
+        for tup in data_tups:
+            if left < (tup[0] - current):
+                break
+            ret += tup[1]
+        return ret
+
+    def _formatTransaction(self, tsInfo):
+        """Return a string containing a human-readable formatted
+        summary of the transaction.
+        
+        :param tsInfo: :class:`yum.transactioninfo.TransactionData`
+           instance that contains information about the transaction
+        :return: a string that contains a formatted summary of the
+           transaction
+           """
+        # Sort the packages in the transaction into different lists,
+        # e.g. installed, updated etc
+        tsInfo.makelists(True, True)
+
+        # For each package list, pkglist_lines will contain a tuple
+        # that contains the name of the list, and a list of tuples
+        # with information about each package in the list
+        pkglist_lines = []
+        data  = {'n' : {}, 'v' : {}, 'r' : {}}
+        a_wid = 0 # Arch can't get "that big" ... so always use the max.
+
+
+        def _add_line(lines, data, a_wid, po, obsoletes=[]):
+            # Create a tuple of strings that contain the name, arch,
+            # version, repository, size, and obsoletes of the package
+            # given in po.  Then, append this tuple to lines.  The
+            # strings are formatted so that the tuple can be easily
+            # joined together for output.
+
+            
+            (n,a,e,v,r) = po.pkgtup
+            
+            # Retrieve the version, repo id, and size of the package
+            # in human-readable form
+            evr = po.printVer()
+            repoid = po.ui_from_repo
+            size = self._format_number(float(po.size))
+
+            if a is None: # gpgkeys are weird
+                a = 'noarch'
+
+            lines.append((n, a, evr, repoid, size, obsoletes))
+            #  Create a dict of field_length => number of packages, for
+            # each field.
+            for (d, v) in (("n",len(n)), ("v",len(evr)), ("r",len(repoid))):
+                data[d].setdefault(v, 0)
+                data[d][v] += 1
+            a_wid = max(a_wid, len(a))
+
+            return a_wid
+
+        
+
+        # Iterate through the different groups of packages
+        for (action, pkglist) in [(_('Installing'), tsInfo.installed),
+                            (_('Updating'), tsInfo.updated),
+                            (_('Removing'), tsInfo.removed),
+                            (_('Reinstalling'), tsInfo.reinstalled),
+                            (_('Downgrading'), tsInfo.downgraded),
+                            (_('Installing for dependencies'), tsInfo.depinstalled),
+                            (_('Updating for dependencies'), tsInfo.depupdated),
+                            (_('Removing for dependencies'), tsInfo.depremoved)]:
+            # Create a list to hold the tuples of strings for each package
+            lines = []
+
+            # Append the tuple for each package to lines, and update a_wid
+            for txmbr in pkglist:
+                a_wid = _add_line(lines, data, a_wid, txmbr.po, txmbr.obsoletes)
+
+            # Append the lines instance for this package list to pkglist_lines
+            pkglist_lines.append((action, lines))
+
+        # # Iterate through other package lists
+        # for (action, pkglist) in [(_('Skipped (dependency problems)'),
+        #                            self.skipped_packages),
+        #                           (_('Not installed'), self._not_found_i.values()),
+        #                           (_('Not available'), self._not_found_a.values())]:
+        #     lines = []
+        #     for po in pkglist:
+        #         a_wid = _add_line(lines, data, a_wid, po)
+
+        #     pkglist_lines.append((action, lines))
+
+        if not data['n']:
+            return u''
+        else:
+            # Change data to a list with the correct number of
+            # columns, in the correct order
+            data    = [data['n'],    {}, data['v'], data['r'], {}]
+
+            
+             
+            # Calculate the space needed for each column
+            columns = [1,         a_wid,         1,         1,  5]
+
+            columns = self._calcColumns(data, self.opts.output_width,
+                                        columns, remainder_column = 2, indent="  ")
+
+            (n_wid, a_wid, v_wid, r_wid, s_wid) = columns
+            assert s_wid == 5
+
+            # out will contain the output as a list of strings, that
+            # can be later joined together
+            out = [u"""
+%s
+%s
+%s
+""" % ('=' * self.opts.output_width,
+       self._fmtColumns(((_('Package'), -n_wid), (_('Arch'), -a_wid),
+                        (_('Version'), -v_wid), (_('Repository'), -r_wid),
+                        (_('Size'), s_wid)), u" "),
+       '=' * self.opts.output_width)]
+
+        # Add output for each package list in pkglist_lines
+        for (action, lines) in pkglist_lines:
+            #If the package list is empty, skip it
+            if not lines:
+                continue
+
+            # Add the name of the package list
+            totalmsg = u"%s:\n" % action
+            # Add a line of output about an individual package
+            for (n, a, evr, repoid, size, obsoletes) in lines:
+                columns = ((n,   -n_wid), (a,      -a_wid),
+                           (evr, -v_wid), (repoid, -r_wid), (size, s_wid))
+                msg = self._fmtColumns(columns, u" ", u"\n")
+                for obspo in sorted(obsoletes):
+                    appended = _('     replacing  %s.%s %s\n')
+                    appended %= (obspo.name,
+                                 obspo.arch, obspo.printVer())
+                    msg = msg+appended
+                totalmsg = totalmsg + msg
+
+            # Append the line about the individual package to out
+            out.append(totalmsg)
+
+        # Add a summary of the transaction
+        out.append(_("""
+Transaction Summary
+%s
+""") % ('=' * self.opts.output_width))
+        summary_data =  (
+            (_('Install'), len(tsInfo.installed),
+             len(tsInfo.depinstalled)),
+            (_('Upgrade'), len(tsInfo.updated),
+             len(tsInfo.depupdated)),
+            (_('Remove'), len(tsInfo.removed),
+             len(tsInfo.depremoved)),
+            (_('Reinstall'), len(tsInfo.reinstalled), 0),
+            (_('Downgrade'), len(tsInfo.downgraded), 0),
+            # (_('Skipped (dependency problems)'), len(self.skipped_packages), 0),
+            # (_('Not installed'), len(self._not_found_i.values()), 0),
+            # (_('Not available'), len(self._not_found_a.values()), 0),
+        )
+        max_msg_action   = 0
+        max_msg_count    = 0
+        max_msg_pkgs     = 0
+        max_msg_depcount = 0
+        for action, count, depcount in summary_data:
+            if not count and not depcount:
+                continue
+
+            msg_pkgs = P_('Package', 'Packages', count)
+            len_msg_action   = utf8_width(action)
+            len_msg_count    = utf8_width(str(count))
+            len_msg_pkgs     = utf8_width(msg_pkgs)
+
+            if depcount:
+                len_msg_depcount = utf8_width(str(depcount))
+            else:
+                len_msg_depcount = 0
+
+            max_msg_action   = max(len_msg_action,   max_msg_action)
+            max_msg_count    = max(len_msg_count,    max_msg_count)
+            max_msg_pkgs     = max(len_msg_pkgs,     max_msg_pkgs)
+            max_msg_depcount = max(len_msg_depcount, max_msg_depcount)
+
+        for action, count, depcount in summary_data:
+            msg_pkgs = P_('Package', 'Packages', count)
+            if depcount:
+                msg_deppkgs = P_('Dependent package', 'Dependent packages',
+                                 depcount)
+                if count:
+                    msg = '%s  %*d %s (+%*d %s)\n'
+                    out.append(msg % (utf8_width_fill(action, max_msg_action),
+                                      max_msg_count, count,
+                                      utf8_width_fill(msg_pkgs, max_msg_pkgs),
+                                      max_msg_depcount, depcount, msg_deppkgs))
+                else:
+                    msg = '%s  %*s %s ( %*d %s)\n'
+                    out.append(msg % (utf8_width_fill(action, max_msg_action),
+                                      max_msg_count, '',
+                                      utf8_width_fill('', max_msg_pkgs),
+                                      max_msg_depcount, depcount, msg_deppkgs))
+            elif count:
+                msg = '%s  %*d %s\n'
+                out.append(msg % (utf8_width_fill(action, max_msg_action),
+                                  max_msg_count, count, msg_pkgs))
+
+        return ''.join(out)
+
+
+class EmailEmitter(UpdateEmitter):
+    """Emitter class to send messages via email."""
+
+    def __init__(self, opts):
+        super(EmailEmitter, self).__init__(opts)        
+        self.subject = ""
+
+    def updatesAvailable(self, tsInfo):
+        """Appends a message to the output list stating that there are
+        updates available, and set an appropriate subject line.
+
+        :param tsInfo: A :class:`yum.transactioninfo.TransactionData`
+           instance that contains information about the transaction.
+        """
+        super(EmailEmitter, self).updatesAvailable(tsInfo)
+        self.subject = "Yum: Updates Available on %s" % self.opts.system_name
+
+    def updatesDownloaded(self):
+        """Append a message to the output list stating that updates
+        have been downloaded successfully, and set an appropriate
+        subject line.
+        """
+        self.subject = "Yum: Updates downloaded on %s" % self.opts.system_name
+        super(EmailEmitter, self).updatesDownloaded()
+
+    def updatesInstalled(self):
+        """Append a message to the output list stating that updates
+        have been installed successfully, and set an appropriate
+        subject line.
+        """
+        self.subject = "Yum: Updates installed on %s" % self.opts.system_name
+        super(EmailEmitter, self).updatesInstalled()
+
+    def setupFailed(self, errmsg):
+        """Append a message to the output list stating that setup
+        failed, and then call sendMessages to emit the output, and set
+        an appropriate subject line.
+
+        :param errmsgs: a string that contains the error message
+        """
+        self.subject = "Yum: Failed to perform setup on %s" % self.opts.system_name
+        super(EmailEmitter, self).setupFailed(errmsg)
+
+    def lockFailed(self, errmsg):
+        """Append a message to the output list stating that the
+        program failed to acquire the yum lock, then call sendMessages
+        to emit the output, and set an appropriate subject line.
+
+        :param errmsg: a string that contains the error message
+        """
+        self.subject = "Yum: Failed to  acquire the yum lock on %s" % self.opts.system_name
+        super(EmailEmitter, self).lockFailed(errmsg)
+
+    def checkFailed(self, errmsg):
+        """Append a message to the output stating that checking for
+        updates failed, then call sendMessages to emit the output, and
+        set an appropriate subject line.
+
+        :param errmsgs: a string that contains the error message
+        """
+        self.subject = "Yum: Failed to check for updates on %s" % self.opts.system_name
+        super(EmailEmitter, self).checkFailed(errmsg)
+
+    def downloadFailed(self, errmsg):
+        """Append a message to the output list stating that checking
+        for group updates failed, then call sendMessages to emit the
+        output, and add an appropriate subject line.
+
+        :param errmsgs: a string that contains the error message
+        """
+        self.subject = "Yum: Failed to download updates on %s" % self.opts.system_name
+        super(EmailEmitter, self).lockFailed(errmsg)
+
+    def updatesFailed(self, errmsg):
+        """Append a message to the output list stating that installing
+        updates failed, then call sendMessages to emit the output, and
+        add an appropriate subject line.
+
+        :param errmsgs: a string that contains the error message
+        """
+        self.subject = "Yum: Failed to install updates on %s" % self.opts.system_name
+        super(EmailEmitter, self).updatesFailed(errmsg)
+
+    def sendMessages(self):
+        """Combine the stored messages that have been stored into a
+        single email message, and send this message.
+        """
+        # Build up the email to be sent
+        msg = MIMEText(''.join(self.output))
+        msg['Subject'] = self.subject
+        msg['From'] = self.opts.email_from
+        msg['To'] = ",".join(self.opts.email_to)
+
+        # Send the email
+        s = smtplib.SMTP()
+        s.connect(self.opts.email_host)
+        s.sendmail(self.opts.email_from, self.opts.email_to, msg.as_string())
+        s.close()
+
+
+class StdIOEmitter(UpdateEmitter):
+    """Emitter class to send messages to syslog."""
+
+    def __init__(self, opts):
+        super(StdIOEmitter, self).__init__(opts)
+        
+    def sendMessages(self) :
+        """Combine the stored messages that have been stored into a
+        single email message, and send this message to standard output.
+        """
+        print "".join(self.output)
+
+
+class YumCronConfig(BaseConfig):
+    """Class to parse configuration information from the config file, and
+    to store this information.
+    """
+    nonroot_workdir = Option("/var/tmp/yum-cron")
+    system_name = Option(gethostname())
+    output_width = IntOption(80)
+    random_sleep = IntOption(0)
+    emit_via = ListOption(['email','stdio'])
+    email_to = ListOption(["root"])
+    email_from = Option("root")
+    email_host = Option("localhost")
+    email_port = IntOption(25)
+    update_messages = BoolOption(False)
+    apply_updates = BoolOption(False)
+    download_updates = BoolOption(False)
+    yum_config_file = Option("/etc/yum.conf")
+    group_list = ListOption([])
+    group_package_types = ListOption(['mandatory', 'default'])
+
+
+class YumCronBase(yum.YumBase):
+    """Main class to check for and apply the updates."""
+
+    def __init__(self, config_file_name = None):
+        """Create a YumCronBase object, and perform initial setup.
+
+        :param config_file_name: a String specifying the name of the
+           config file to use.
+        """
+        yum.YumBase.__init__(self)
+
+        # Read the config file
+        self.readConfigFile(config_file_name)
+
+
+        # Create the emitters, and add them to the list
+        self.emitters = []
+        if 'email' in self.opts.emit_via:
+            self.emitters.append(EmailEmitter(self.opts))
+        if 'stdio' in self.opts.emit_via:
+            self.emitters.append(StdIOEmitter(self.opts))
+
+        self.updateInfo = []
+        self.updateInfoTime = None
+
+    def readConfigFile(self, config_file_name = None):
+        """Reads the given config file, or if none is given, the
+        default config file.
+
+        :param config_file_name: a String specifying the name of the
+           config file to read.
+        """
+        # Create ConfigParser and UDConfig Objects
+        confparser = ConfigParser()
+        self.opts = YumCronConfig()
+
+        #If no config file name is given, fall back to the default
+        if config_file_name == None:
+            config_file_name = default_config_file
+            
+        # Attempt to read the config file.  confparser.read will return a
+        # list of the files that were read successfully, so check that it
+        # contains config_file
+        if config_file_name not in confparser.read(config_file_name):
+            print >> sys.stderr, "Error reading config file"
+            sys.exit(1)
+
+        # Populate the values into  the opts object
+        self.opts.populate(confparser, 'commands')
+        self.opts.populate(confparser, 'emitters')
+        self.opts.populate(confparser, 'email')
+        self.opts.populate(confparser, 'groups')
+
+        #If the system name is not given, set it by getting the hostname
+        if self.opts.system_name == 'None' :
+            self.opts.system_name = gethostname()
+
+        if 'None' in self.opts.group_list:
+            self.opts.group_list = []
+
+
+    def randomSleep(self, duration):
+        """Sleep for a random amount of time up to *duration*.
+        
+        :param duration: the maximum amount of time to sleep, in
+           minutes.  The actual time slept will be between 0 and
+           *duration* minutes
+           """
+        if duration > 0:
+            sleep(random() * 60 * duration)
+
+    def doSetup(self):
+        """Perform set up, including setting up directories and
+        parsing options.
+
+        :return: boolean that indicates whether setup has completed
+           successfully
+        """
+        try :
+            # Set the configuration file
+            self.preconf.fn = self.opts.yum_config_file
+
+            # if we are not root do the special subdir thing
+            if os.geteuid() != 0:
+                if not os.path.exists(self.opts.nonroot_workdir):
+                    os.makedirs(self.opts.nonroot_workdir)
+                self.repos.setCacheDir(self.opts.nonroot_workdir)
+
+            # Create the configuration
+            self.conf
+
+        except Exception, e:
+            # If there are any exceptions, send a message about them,
+            # and return False
+            self.emitSetupFailed('%s' % e)
+            sys.exit(1)
+
+    def acquireLock(self):
+        """ Wrapper method around doLock to emit errors correctly."""
+
+        try:
+            self.doLock()
+        except yum.Errors.LockError, e:
+            self.emitLockFailed("%s" % e)
+            sys.exit(1)
+
+    def populateUpdateMetadata(self):
+        """Populate the metadata for the packages in the update."""
+
+        self.updateMetadata = UpdateMetadata()
+        repos = []
+
+        for (new, old) in self.up.getUpdatesTuples():
+            pkg = self.getPackageObject(new)
+            if pkg.repoid not in repos:
+                repo = self.repos.getRepo(pkg.repoid)
+                repos.append(repo.id)
+                try: # grab the updateinfo.xml.gz from the repodata
+                    md = repo.retrieveMD('updateinfo')
+                except Exception: # can't find any; silently move on
+                    continue
+                md = gzip.open(md)
+                self.updateMetadata.add(md)
+                md.close()
+
+    def refreshUpdates(self):
+        """Check whether updates are available.
+
+        :return: Boolean indicating whether any updates are
+           available
+        """
+        try:
+            updatesTuples = self.up.getUpdatesTuples()
+            # If there are no updates, return False
+            if not updatesTuples:
+                return False
+
+            # figure out the updates
+            for (new, old) in updatesTuples:
+                updates_available = True
+                updating = self.getPackageObject(new)
+                updated = self.rpmdb.searchPkgTuple(old)[0]
+            
+                self.tsInfo.addUpdate(updating, updated)
+
+            # and the obsoletes
+            if self.conf.obsoletes:
+                for (obs, inst) in self.up.getObsoletesTuples():
+                    obsoleting = self.getPackageObject(obs)
+                    installed = self.rpmdb.searchPkgTuple(inst)[0]
+                
+                    self.tsInfo.addObsoleting(obsoleting, installed)
+                    self.tsInfo.addObsoleted(installed, obsoleting)
+
+        except Exception, e:
+            self.emitCheckFailed("%s" %(e,))
+            sys.exit(1)
+
+        else:
+            return True
+
+    def refreshGroupUpdates(self):
+        """Check for group updates, and add them to the
+        transaction.
+
+        :return: Boolean indicating whether there are any updates to
+           the group available
+        """
+        update_available = False
+        try:
+            for group_string in self.opts.group_list:
+                group_matched = False
+                for group in self.comps.return_groups(group_string):
+                    group_matched = True
+                    try:
+                        txmbrs = self.selectGroup(group.groupid,
+                                                  self.opts.group_package_types,
+                                                  upgrade=True)
+                        
+                        # If updates are available from a previous
+                        # group, or there are updates are available
+                        # from this group, set update_available to True
+                        update_available |= (txmbrs != [])
+                        
+                    except yum.Errors.GroupsError:
+                        self.emitGroupError('Warning: Group %s does not exist.' % group_string)
+                        continue
+
+                if not group_matched:
+                    self.emitGroupError('Warning: Group %s does not exist.' % group_string)
+                    continue
+
+        except Exception, e:
+            self.emitGroupFailed("%s" % e)
+            return False
+
+        else:
+            return update_available
+
+    def findDeps(self):
+        """Build the transaction to resolve the dependencies for the update."""
+
+        try:
+            (res, resmsg) = self.buildTransaction()
+        except yum.Errors.RepoError, e:
+            self.emitCheckFailed("%s" %(e,))
+            sys.exit()
+        if res != 2:
+            self.emitCheckFailed("Failed to build transaction: %s" %(str.join("\n", resmsg),))
+            sys.exit(1)
+
+    def downloadUpdates(self, emit):
+        """Download the update.
+
+        :param emit: Boolean indicating whether to emit messages
+           about the download
+        """
+        # Emit a message that that updates will be downloaded
+        if emit :
+            self.emitDownloading()
+        dlpkgs = map(lambda x: x.po, filter(lambda txmbr:
+                                            txmbr.ts_state in ("i", "u"),
+                                            self.tsInfo.getMembers()))
+        try:
+            # Download the updates
+            self.downloadPkgs(dlpkgs)
+        except Exception, e:
+            self.emitDownloadFailed("%s" % e)
+            sys.exit(1)
+        else :
+            # Emit a message that the packages have been downloaded
+            # successfully
+            if emit :
+                self.emitDownloaded()
+                self.emitMessages()
+
+    def installUpdates(self, emit):
+        """Apply the available updates.
+        
+        :param emit: Boolean indicating whether to emit messages about
+           the installation
+        """
+        # Emit a message  that 
+        if emit :
+            self.emitInstalling()
+
+        dlpkgs = map(lambda x: x.po, filter(lambda txmbr:
+                                            txmbr.ts_state in ("i", "u"),
+                                            self.tsInfo.getMembers()))
+
+        for po in dlpkgs:
+            result, err = self.sigCheckPkg(po)
+            if result == 0:
+                continue
+            elif result == 1:
+                try:
+                    self.getKeyForPackage(po)
+                except yum.Errors.YumBaseError, errmsg:
+                    self.emitUpdateFailed([str(errmsg)])
+                    return False
+
+        del self.ts
+        self.initActionTs() # make a new, blank ts to populate
+        self.populateTs(keepold=0)
+        self.ts.check() #required for ordering
+        self.ts.order() # order
+        cb = callback.RPMInstallCallback(output = 0)
+        cb.filelog = True
+            
+        cb.tsInfo = self.tsInfo
+        try:
+            self.runTransaction(cb=cb)
+        except yum.Errors.YumBaseError, err:
+            
+            self.emitUpdateFailed([str(err)])
+            sys.exit(1)
+
+        self.emitInstalled()
+        self.emitMessages()
+
+    def updatesCheck(self):
+        """Check to see whether updates are available for any
+        installed packages. If updates are available, install them,
+        download them, or just emit a message, depending on what
+        options are selected in the configuration file.
+        """
+        # Sleep a random time
+        self.randomSleep(self.opts.random_sleep)
+
+        # Perform the initial setup
+        self.doSetup()
+
+        # Acquire the yum lock
+        self.acquireLock()
+
+        # Update the metadata
+        self.populateUpdateMetadata()
+
+        # Exit if we don't need to send messages, or there are no
+        # updates
+        if not (self.opts.update_messages and (self.refreshUpdates()
+                                             or self.refreshGroupUpdates())):
+                sys.exit(0)
+
+        # Build the transaction to find the additional dependencies
+        self.findDeps()
+
+        # download if set up to do so, else tell about the updates and exit
+        if not self.opts.download_updates:
+            self.emitAvailable()
+            self.emitMessages()
+            self.releaseLocks()
+            sys.exit(0)
+
+        self.downloadUpdates(not self.opts.apply_updates)
+        
+        # now apply if we're set up to do so; else just tell that things are
+        # available
+        if not self.opts.apply_updates:
+            self.releaseLocks()
+            sys.exit(0)
+
+        self.installUpdates(True)
+
+        self.releaseLocks()
+        sys.exit(0)
+
+    def releaseLocks(self):
+        """Close the rpm database, and release the yum lock."""
+        self.closeRpmDB()
+        self.doUnlock()
+
+    def emitAvailable(self):
+        """Emit a notice stating whether updates are available."""
+        map(lambda x: x.updatesAvailable(self.tsInfo), self.emitters)
+
+    def emitDownloading(self):
+        """Emit a notice stating that updates are downloading."""
+        map(lambda x: x.updatesDownloading(self.tsInfo), self.emitters)
+
+    def emitDownloaded(self):
+        """Emit a notice stating that updates have downloaded."""
+        map(lambda x: x.updatesDownloaded(), self.emitters)
+
+    def emitInstalling(self):
+        """Emit a notice stating that automatic updates are about to
+        be applied.
+        """
+        map(lambda x: x.updatesInstalling(self.tsInfo), self.emitters)
+
+    def emitInstalled(self):
+        """Emit a notice stating that automatic updates have been applied."""
+        map(lambda x: x.updatesInstalled(), self.emitters)
+
+    def emitSetupFailed(self, error):
+        """Emit a notice stating that checking for updates failed."""
+        map(lambda x: x.setupFailed(error), self.emitters)
+
+    def emitLockFailed(self, errmsg):
+        """Emit a notice that we failed to acquire the yum lock."""
+        map(lambda x: x.lockFailed(errmsg), self.emitters)
+
+    def emitCheckFailed(self, error):
+        """Emit a notice stating that checking for updates failed."""
+        map(lambda x: x.checkFailed(error), self.emitters)
+
+    def emitGroupError(self, error):
+        """Emit a notice stating that there was an error checking for
+        group updates.
+        """
+        map(lambda x: x.groupError(error), self.emitters)
+
+    def emitGroupFailed(self, error):
+        """Emit a notice stating that checking for group updates failed."""
+        map(lambda x: x.groupFailed(error), self.emitters)
+
+    def emitDownloadFailed(self, error):
+        """Emit a notice stating that downloading the updates failed."""
+        map(lambda x: x.downloadFailed(error), self.emitters)
+
+    def emitUpdateFailed(self, errmsgs):
+        """Emit a notice stating that automatic updates failed."""
+        map(lambda x: x.updatesFailed(errmsgs), self.emitters)
+
+    def emitMessages(self):
+        """Emit the messages from the emitters."""
+        map(lambda x: x.sendMessages(), self.emitters)
+
+
+def main():
+    """Configure and run the update check."""
+    # If a file name was passed in, use it as the config file name.
+    base = None
+    if len(sys.argv) > 1:
+        base = YumCronBase(sys.argv[1])
+    else:
+        base = YumCronBase()
+
+    #Run the update check
+    base.updatesCheck()
+
+if __name__ == "__main__":
+    main()
diff --git a/yum-cron/yum-cron.sh b/yum-cron/yum-cron.sh
deleted file mode 100755
index e300fa7..0000000
--- a/yum-cron/yum-cron.sh
+++ /dev/null
@@ -1,170 +0,0 @@
-#!/bin/bash
-
-# This script is designed to be run from cron to automatically keep your
-# system up to date with the latest security patches and bug fixes. It
-# can download and/or apply package updates as configured in
-# /etc/sysconfig/yum-cron.
-
-
-# This is used by /etc/init.d/yum-cron on shutdown to protect against
-# abruptly shutting down mid-transaction. Therefore, you shouldn't change
-# it without changing that.
-PIDFILE=/var/lock/yum-cron.pid
-
-# This is the home of the yum scripts which power the various actions the
-# yum-cron system performs.
-SCRIPTDIR=/usr/share/yum-cron/
-
-# If no command line options were given, exit with a usage message.
-if [[ -z "$1" ]]; then
-  echo "Usage: yum-cron {update|cleanup|...}"
-  exit 1
-else
-  ACTION=$1
-fi
-
-# If a command line option was given, it must match a yum script.
-YUMSCRIPT=${SCRIPTDIR}/${ACTION}.yum
-if [[ ! -r $YUMSCRIPT ]]; then
-  echo "Script for action \"$ACTION\" is not readable in $SCRIPTDIR."
-  exit 1
-fi  
-
-# This must be set above. But there a couple of places where horrible
-# things might happen if it's unset, so, let's check.
-if [[ -z "$PIDFILE" ]]; then
-  echo "Error! \$PIDFILE variable must be set in yum-cron."
-  exit 1
-fi
-
-# Read the settings from our config file.
-if [[ -f /etc/sysconfig/yum-cron ]]; then
-  source /etc/sysconfig/yum-cron
-fi
-
-# If no system name is set, use the hostname.
-[[ -z "$SYSTEMNAME" ]] && SYSTEMNAME=$( hostname ) 
-
-# If DOWNLOAD_ONLY is set, then we force CHECK_ONLY too.
-# Gotta check for updates before we can possibly download them.
-[[ "$DOWNLOAD_ONLY" == "yes" ]] && CHECK_ONLY=yes
-
-# This holds the output from the "meat" of this script, so that it can
-# be nicely mailed to the configured destination when we're done.
-YUMOUTPUT=$(mktemp /var/run/yum-cron.XXXXXX)
-touch $YUMOUTPUT 
-[[ -x /sbin/restorecon ]] && /sbin/restorecon $YUMOUTPUT
-
-# Here is the gigantic block of lockfile logic.
-#
-# Note: the lockfile code doesn't currently try and use YUMOUTPUT to email
-# messages nicely, so this gets handled by normal cron error mailing.
-#
-	
-# We use noclobber for the pidfile, as this will test for and if possible 
-# create the file in one atomic action. (So there's no race condition.) The 
-# current process ID is stored in the file so we can check for staleness 
-# later.
-if (set -o noclobber; echo "$$" > $PIDFILE) 2>/dev/null; then
-  # We got the lock. So now, set a trap to clean up locks and the output
-  # tempfile when the script exits or is killed.
-  trap "rm -f $PIDFILE" INT TERM EXIT
-else
-  # Lock failed -- check if a running process exists.  
-  # First, if there's no PID file in the lock directory, something bad has
-  # happened.  We can't know the process name, so, restart.
-  if [[ ! -f $PIDFILE ]]; then
-    echo "yum-cron: no lock PID, restarting myself" >&2
-    exec $0 "$@"
-  fi
-  OTHERPID="$(< $PIDFILE)" 2>/dev/null
-  # if cat wasn't able to read the file anymore, another instance probably is
-  # about to remove the lock -- exit, we're *still* locked
-  if [[ $? != 0 ]]; then
-    echo "yum-cron: lock failed, PID ${OTHERPID} is active" >&2
-    exit 0
-  fi
-  if ! kill -0 $OTHERPID &>/dev/null; then
-    # Lock is stale. Remove it and restart.
-    echo "yum-cron: removing stale lock of nonexistant PID ${OTHERPID}" >&2
-    rm -f $PIDFILE
-    echo "yum-cron: restarting myself" >&2
-    exec $0 "$@"
-  else
-    # Remove lockfiles more than a day old -- they must be stale.
-    find $PIDFILE -type f -amin +1440 -exec rm -f $PIDFILE \;
-    # If it's still there, it *wasn't* too old. Bail!
-    if [[ -f $PIDFILE ]]; then
-      # Lock is valid and OTHERPID is active -- exit, we're locked!
-      echo "yum-cron: lock failed, PID ${OTHERPID} is active" >&2
-      exit 0
-    else
-      # Lock was invalid. Restart.
-      echo "yum-cron: removed stale lock belonging to PID ${OTHERPID}; restarting." >&2
-      exec $0 "$@"
-    fi
-  fi
-fi
-
-# Now, do the actual work.
-
-# We special case "update" because it has complicated conditionals; for
-# everything else we just run yum with the right parameters and
-# corresponding script.  Right now, that's just "cleanup" but theoretically
-# there could be other actions.
-{
-  case "$ACTION" in
-    update)
-        # There's three broad possibilties here:
-        #   CHECK_ONLY (possibly with DOWNLOAD_ONLY)
-        #   CHECK_FIRST (exits _silently_ if we can't access the repos)
-        #   nothing special -- just do it
-        # Note that in all cases, yum is updated first, and then 
-        # everything else.
-        if [[ "$CHECK_ONLY" == "yes" ]]; then
-          /usr/bin/yum $YUM_PARAMETER -e 0 -d 0 -y check-update 1> /dev/null 2>&1
-          case $? in
-            1)   exit 1;;
-            100) echo "New updates available for host $SYSTEMNAME";
-                 /usr/bin/yum $YUM_PARAMETER -e ${ERROR_LEVEL:-0} -d ${DEBUG_LEVEL:-0} -y -C check-update
-                 if [[ "$DOWNLOAD_ONLY" == "yes" ]]; then
-                     /usr/bin/yum $YUM_PARAMETER -e ${ERROR_LEVEL:-0} -d ${DEBUG_LEVEL:-0} -y --downloadonly update
-                     echo "Updates downloaded. Use \"yum -C update\" manually to install them."
-                 fi
-                 ;;
-          esac
-        elif [[ "$CHECK_FIRST" == "yes" ]]; then
-          # Don't run if we can't access the repos -- if this is set, 
-          # and there's a problem, we exit silently (but return an error
-          # code).
-          /usr/bin/yum $YUM_PARAMETER -e 0 -d 0 check-update 2>&-
-          case $? in
-            1)   exit 1;;
-            100) /usr/bin/yum $YUM_PARAMETER -e ${ERROR_LEVEL:-0} -d ${DEBUG_LEVEL:-0} -y update yum
-                 /usr/bin/yum $YUM_PARAMETER -e ${ERROR_LEVEL:-0} -d ${DEBUG_LEVEL:-0} -y shell $YUMSCRIPT
-                 ;;
-          esac
-        else
-          # and here's the "just do it".
-          /usr/bin/yum $YUM_PARAMETER -e ${ERROR_LEVEL:-0} -d ${DEBUG_LEVEL:-0} -y update yum
-          /usr/bin/yum $YUM_PARAMETER -e ${ERROR_LEVEL:-0} -d ${DEBUG_LEVEL:-0} -y shell $YUMSCRIPT
-        fi
-        ;;
-    *)
-        /usr/bin/yum $YUM_PARAMETER -e ${ERROR_LEVEL:-0} -d ${DEBUG_LEVEL:-0} -y shell $YUMSCRIPT
-        ;;
-  esac       
-
-} >> $YUMOUTPUT 2>&1
-
-if [[ ! -z "$MAILTO" && -x /bin/mail ]]; then 
-# If MAILTO is set, use mail command for prettier output.
-  [[ -s "$YUMOUTPUT" ]] && \
-    mail -s "System update: $SYSTEMNAME" $MAILTO < $YUMOUTPUT && \
-    rm -f $YUMOUTPUT
-else 
-# The default behavior is to use cron's internal mailing of output.
-  cat $YUMOUTPUT && rm -f $YUMOUTPUT
-fi 
-
-exit 0
diff --git a/yum.spec b/yum.spec
index 3f73b4a..728071f 100644
--- a/yum.spec
+++ b/yum.spec
@@ -282,8 +282,10 @@ exit 0
 %defattr(-,root,root)
 %doc COPYING
 %config(noreplace) %{_sysconfdir}/cron.daily/0yum-update.cron
+%config(noreplace) %{_sysconfdir}/yum/yum-cron.conf
 %{_sysconfdir}/rc.d/init.d/yum-cron
 %{_sbindir}/yum-cron
+%{_mandir}/man*/yum-cron.*
 
 %if %{yum_updatesd}
 %files updatesd
commit 4fc370a9f9f3837caaaddf41745dc016badab7b2
Author: Zdeněk Pavlas <zpavlas at redhat.com>
Date:   Thu Oct 25 13:33:51 2012 +0200

    yum-cron: remove files we don't need
    
    update.yum, cleanup.yum: we no longer run "yum shell"
    yum-cron.sysconfig: config stuff goes to /etc/yum-cron.conf
    yum-cleanup.cron.sh: no separate cleanup job

diff --git a/yum-cron/Makefile b/yum-cron/Makefile
index 34cb397..4c26969 100644
--- a/yum-cron/Makefile
+++ b/yum-cron/Makefile
@@ -8,14 +8,8 @@ install:
 	mkdir -p $(DESTDIR)/etc/cron.daily
 	mkdir -p $(DESTDIR)/etc/rc.d/init.d
 	mkdir -p $(DESTDIR)/usr/sbin
-	mkdir -p $(DESTDIR)/etc/sysconfig
-	mkdir -p $(DESTDIR)/usr/share/yum-cron
 # Install yum-update.cron as 0yum-update.cron so it runs before items like
 # manpage update, mlocate, and prelink
 	install -D -m 755 yum-update.cron.sh $(DESTDIR)/etc/cron.daily/0yum-update.cron
-	install -D -m 755 yum-cleanup.cron.sh $(DESTDIR)/etc/cron.daily/yum-cleanup.cron
 	install -D -m 755 yum-cron.sysvinit $(DESTDIR)/etc/rc.d/init.d/yum-cron
 	install -D -m 755 yum-cron.sh $(DESTDIR)/usr/sbin/yum-cron
-	install -D -m 644 yum-cron.sysconfig $(DESTDIR)/etc/sysconfig/yum-cron
-	install -D -m 644 update.yum $(DESTDIR)/usr/share/yum-cron/update.yum
-	install -D -m 644 cleanup.yum $(DESTDIR)/usr/share/yum-cron/cleanup.yum
diff --git a/yum-cron/cleanup.yum b/yum-cron/cleanup.yum
deleted file mode 100644
index c60fa08..0000000
--- a/yum-cron/cleanup.yum
+++ /dev/null
@@ -1,4 +0,0 @@
-clean packages
-clean expire-cache
-ts run
-exit
diff --git a/yum-cron/update.yum b/yum-cron/update.yum
deleted file mode 100644
index 5d4e874..0000000
--- a/yum-cron/update.yum
+++ /dev/null
@@ -1,3 +0,0 @@
-update
-ts run
-exit
diff --git a/yum-cron/yum-cleanup.cron.sh b/yum-cron/yum-cleanup.cron.sh
deleted file mode 100755
index e38e80f..0000000
--- a/yum-cron/yum-cleanup.cron.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-
-# Only run if this flag is set. The flag is created by the yum-cron init
-# script when the service is started -- this allows one to use chkconfig and
-# the standard "service stop|start" commands to enable or disable yum-cron.
-if [[ ! -f /var/lock/subsys/yum-cron ]]; then
-  exit 0
-fi
-
-# Read configuration settings from the sysconfig directory.
-if [[ -f /etc/sysconfig/yum-cron ]]; then
-  source /etc/sysconfig/yum-cron
-fi
-
-# Only run on certain days of the week, based on the
-# settings in the above-mentioned sysconfig file.
-dow=`date +%w` 
-DAYS_OF_WEEK=${DAYS_OF_WEEK:-0123456} 
-if [[ "${DAYS_OF_WEEK/$dow/}" == "${DAYS_OF_WEEK}" ]]; then 
-  exit 0 
-fi 
-
-# And only _clean_ on a subset of the configured days.
-CLEANDAY=${CLEANDAY:-0}
-if [[ "${CLEANDAY/$dow/}" == "${CLEANDAY}" ]]; then
-  exit 0
-fi
-
-# Action!
-exec /usr/sbin/yum-cron cleanup
diff --git a/yum-cron/yum-cron.sysconfig b/yum-cron/yum-cron.sysconfig
deleted file mode 100644
index 53477d3..0000000
--- a/yum-cron/yum-cron.sysconfig
+++ /dev/null
@@ -1,92 +0,0 @@
-# This is the configuration file for yum-cron, a simple system for
-# keeping your machine up to date. These options are used variously by
-# the main script, by the cron scripts, and by the init script.
-
-# Main Options
-#--------------------------------------------------------------------------
-
-# Pass any given parameter to yum, as run in all the scripts invoked by
-# this package. Be aware that this is global, and yum is invoked in
-# several modes by these scripts, and your parameter might not be
-# appropriate in all cases.
-YUM_PARAMETER=
-
-# Don't install; just check and report. 
-# (Valid options: yes|no)
-CHECK_ONLY=no
-
-# Don't install; just check for and download any pending updates. This
-# implies CHECK_ONLY=yes, as we've gotta check first to see what to
-# download.
-# (Valid options: yes|no)
-DOWNLOAD_ONLY=no
-
-# Check to see if we can reach the repos before attempting an update.
-# If there is an error, exit silently with no output. You might want
-# this if you know your network connectivity is sporadic.
-# (Valid options: yes|no)
-CHECK_FIRST=no
-
-
-# Yum error level. The practical range is 0-10, where 0 means print
-# only critical errors, and 10 means print all errors, even ones that
-# are not important. Level 0 is the default if nothing is set.
-ERROR_LEVEL=0
-
-# Yum debug level. The practical range is 0-10; a higher number means
-# more output. Level 1 is a useful level if you want to see what's been
-# done and don't want to read /var/log/yum.log. Level 0 is the default
-# if no value is set here.
-DEBUG_LEVEL=0
-
-# If MAILTO is set and the /bin/mail command is available, the mail
-# command is used to deliver yum output. If MAILTO is unset, crond will
-# send the output by itself, usually to root (but with a less useful
-# subject line).
-MAILTO=root
-
-# The reports generated by this command generally use the hostname of
-# the system as reported by the hostname command. If you'd prefer to
-# use something else, you can set that here.
-#SYSTEMNAME="" 
-
-# Scheduling Options (used by the default cron scripts,
-# /etc/cron.daily/yum-cleanup.cron and /etc/cron.daily/0yum-update.cron)
-# 
-#   Note that if you use a different cron configuration (for example,
-#   removing the default scripts and adding an entry in /etc/cron.d),
-#   these values will have no effect -- unless you read and act on them
-#   in your new configuration.
-#--------------------------------------------------------------------------
-
-# Wait for a random time up to the given number of minutes before
-# applying updates. With a value of 60, yum-cron will delay between 1
-# and 60 minutes. A value of 0 will result in no delay, which is handy
-# if you want to ensure that updates happen at a known time, but could
-# be bad for update servers to be hit by all clients at exactly the
-# same time.
-RANDOMWAIT=60
-
-# You may set DAYS_OF_WEEK to the numeric days of the week you want to
-# run, where 0 is Sunday and 6 is Saturday. The default is to run every
-# day.
-#DAYS_OF_WEEK="0123456" 
-
-# The cleanup task (which clears the package cache) can run on a subset
-# of the days above. (If the value chosen here doesn't appear in
-# DAYS_OF_WEEK, the cleanup task will never happen.)
-CLEANDAY="0"
-
-# Init System Options (used by /etc/init.d/yum-cron)
-#--------------------------------------------------------------------------
-
-# If SERVICE_WAITS is set to "yes", and a transaction is in progress
-# when the yum-cron service is stopped, the init script will wait 
-# up to SERVICE_WAIT_TIME seconds before killing the task. Without
-# this, system shutdown continues as normal, potentially breaking
-# in-progress transactions.
-# (Valid options: yes|no)
-SERVICE_WAITS=yes
-
-# 300 is the default.
-SERVICE_WAIT_TIME=300
diff --git a/yum-cron/yum-cron.sysvinit b/yum-cron/yum-cron.sysvinit
index ee531c6..7b44d5e 100755
--- a/yum-cron/yum-cron.sysvinit
+++ b/yum-cron/yum-cron.sysvinit
@@ -7,21 +7,18 @@
 # description:  This controls whether yum-cron runs. If this service is \
 #               off, the yum-cron scripts in /etc/cron.daily exit \
 #               immediately; otherwise, they download and/or apply package \
-#               updates as configured in /etc/sysconfig/yum-cron.
+#               updates as configured in /etc/yum/yum-cron.conf.
 # processname:  yum-cron
-# config: /etc/yum/yum-daily.yum
 #
 
 # source function library
 . /etc/rc.d/init.d/functions
-
-test -f /etc/sysconfig/yum-cron && . /etc/sysconfig/yum-cron
-
 lockfile=/var/lock/subsys/yum-cron
 
 # This is generated by /usr/sbin/yum-cron and will exist when that script
 # is running and not otherwise.
 pidfile=/var/lock/yum-cron.pid
+SERVICE_WAITS=yes
 
 RETVAL=0
 
diff --git a/yum-cron/yum-update.cron.sh b/yum-cron/yum-update.cron.sh
index b9edddf..06dee5f 100755
--- a/yum-cron/yum-update.cron.sh
+++ b/yum-cron/yum-update.cron.sh
@@ -7,29 +7,5 @@ if [[ ! -f /var/lock/subsys/yum-cron ]]; then
   exit 0
 fi
 
-# Read configuration settings from the sysconfig directory.
-if [[ -f /etc/sysconfig/yum-cron ]]; then
-  source /etc/sysconfig/yum-cron
-fi
-
-# Only run on certain days of the week, based on the
-# settings in the above-mentioned sysconfig file.
-dow=`date +%w` 
-DAYS_OF_WEEK=${DAYS_OF_WEEK:-0123456} 
-if [[ "${DAYS_OF_WEEK/$dow/}" == "${DAYS_OF_WEEK}" ]]; then 
-  exit 0 
-fi 
-
-# Wait a random number of minutes, again based on
-# the setting in the sysconfig file.
-[[ $RANDOMWAIT -gt 0 ]] && sleep $(( $RANDOM % ($RANDOMWAIT * 60) + 1 ))
-
-# Double-check to make sure that we're still supposed to be 
-# active after the random wait.
-if [[ ! -f /var/lock/subsys/yum-cron ]]; then
-  exit 0
-fi
-
-
 # Action!
-exec /usr/sbin/yum-cron update
+exec /usr/sbin/yum-cron
diff --git a/yum.spec b/yum.spec
index 4ad6804..3f73b4a 100644
--- a/yum.spec
+++ b/yum.spec
@@ -282,13 +282,8 @@ exit 0
 %defattr(-,root,root)
 %doc COPYING
 %config(noreplace) %{_sysconfdir}/cron.daily/0yum-update.cron
-%config(noreplace) %{_sysconfdir}/cron.daily/yum-cleanup.cron
 %{_sysconfdir}/rc.d/init.d/yum-cron
 %{_sbindir}/yum-cron
-%config(noreplace) %{_sysconfdir}/sysconfig/yum-cron
-%dir %{_datadir}/yum-cron   
-%{_datadir}/yum-cron/update.yum
-%{_datadir}/yum-cron/cleanup.yum
 
 %if %{yum_updatesd}
 %files updatesd


More information about the Yum-commits mailing list