Howdy - my name's Arjun Roy, and this is my first time posting on this list.
I'm an undergraduate cs student, and am interested in yum (among various other things).

Alright, with that out of the way, I'd like to submit a small plugin for yum. It's a tiny history plugin - it serializes the record of a yum transaction after the transaction completes,
and can pretty print the record on demand.

For example:

yum install AllegroOgg --note 'installed allegroOgg'
This will install allegroogg and the dependencies, and save the results to an xml file in a place
   specified by the plugin conf file (default /var/lib/yum/history/).
yum history --date=Jan-26-2008
This will tell you what happened on the 26th of Jan 2008. So if we installed AllegroOgg on that day,
   it will tell you.

My coding style might be a bit out of whack, I might have done some kooky things to get it to work, (though I like to think that I didn't) so feel free to flame it / critique it / submit feature requests. Hope it is useful to
somebody.

On an unrelated note, conduit.registerCommand needs more documentation.

INSTALLATION INSTRUCTIONS:
I was too lazy to make a script, but it is easy.
1. Copy history.conf to /etc/yum/pluginconf.d
2. Copy history.py to /usr/lib/yum/yum-plugins
3. mkdir /var/lib/yum/history
Make sure the directory in step3 matches the directory specified in history.conf, and
also make sure the directory specified in history.conf ends with a '/'.

Thanks,
-Arjun Roy
[main]
enabled = 1
history_directory = /var/lib/yum/history/
'''
Yum History Plugin
Author: Arjun Roy
Date: 1/11/2008
License: GPL2

A simple history plugin that serializes relevant transaction data from yum to xml,
and pretty prints it on command.

    TODO:
    1. Find user initiating action, so we can store that too. 
    2. Write prettier printer for history viewer
    3. Cleanup
'''
from yum.plugins import PluginYumExit, TYPE_CORE, TYPE_INTERACTIVE
from yum.transactioninfo import TransactionMember
from xml.dom.minidom import Document, parse
import sys
from ConfigParser import ConfigParser

requires_api_version = '2.6'
plugin_type  = (TYPE_CORE, TYPE_INTERACTIVE)

#Is there a better way to do this, without depending on yum-cli? Enlighten me...
sys.path.insert(0, '/usr/share/yum-cli')
from yumcommands import YumCommand

class HistoryCommand(YumCommand):
    def getNames(self):
        return ['history']
    def getUsage(self):
        return 'yum history --date=16-Jan-2008'
    def doCommand(self, base, basecmd, extcmds):
        '''
        Check if a history date was specified. If so, and if format is correct, print it.
        '''
        import re
        date_regex = re.compile('\d\d-\D\D\D-\d\d\d\d') #16-Jan-2008
        opts, command = base.optparser.parse_args()
        if opts.date:
            if not date_regex.match(opts.date):
                print "Required date format for history is dd-mon-yyyy eg. 16-Jan-2008"
            else:
                try:
                    conf_parser = ConfigParser()
                    conf_parser.read('/etc/yum/pluginconf.d/history.conf')
                    history_directory = conf_parser.get('main', 'history_directory') 
                except (NoSectionError, NoOptionError, IOError):
                    history_directory = '/var/lib/yum/history/'
                printHistory(date=opts.date, history_directory=history_directory)
        return 0, ['history command has executed.']
    
#Here we register command line options for our plugin, and the history command itself
#'yum history --date 02/19/2007'
#'yum install AllegroOGG --note 'installed allegroOgg''
def config_hook(conduit):
    '''
    Register command line options.
    '''
    parser = conduit.getOptParser()
    parser.add_option('--date', dest='date', help='Specify date in dd-mon-yyyy format to get history information.')
    parser.add_option('-n', '--note', dest='notes', help='Specify some notes describing this yum transaction.')
    conduit.registerCommand(HistoryCommand())

def printHistory(date, history_directory):
    '''
    Pretty Print what happened on date. Looks in history directory as specified.
    '''
    path = history_directory + date
    try:
        xml = parse(path)
        root = xml.childNodes[0]
        for pkg in root.childNodes:
            #The following are guaranteed to be here
            action  = pkg.getElementsByTagName('action')[0].getAttribute('value')
            name    = pkg.getElementsByTagName('name')[0].getAttribute('value')
            arch    = pkg.getElementsByTagName('arch')[0].getAttribute('value')
            epoch   = pkg.getElementsByTagName('epoch')[0].getAttribute('value')
            version = pkg.getElementsByTagName('version')[0].getAttribute('value')
            release = pkg.getElementsByTagName('release')[0].getAttribute('value')
            time    = pkg.getElementsByTagName('time')[0].getAttribute('value')
            repo    = pkg.getElementsByTagName('repo')[0].getAttribute('value')
            #There might be 0 or more dependency reasons
            reasons = pkg.getElementsByTagName('reason')
            #These might or might not be specified
            user    = pkg.getElementsByTagName('user') #TO BE IMPLEMENTED
            notes   = pkg.getElementsByTagName('notes')
            print 'Package ' + name + ' was ' + action + ' at ' + time
            print '\tDetails: '
            print '\tName:    ' + name
            print '\tArch:    ' + arch
            print '\tEpoch:   ' + epoch
            print '\tVersion: ' + version
            print '\tRelease: ' + release
            print '\tTime:    ' + time
            print '\tRepo:    ' + repo
            for reason in reasons:
                print '\tbecause it ' + reason.getAttribute('relationType') + ' ' + reason.getAttribute('relatedTo')
            if(len(user) != 0):
                print '\tUser: ' + user[0].getAttribute('value')
            if(len(notes) != 0):
                print '\tNotes: ' + notes[0].getAttribute('value')
    except IOError:
        print "No history data available for " + date
    
#Here we add the dependencies to the transaction history
def posttrans_hook(conduit):
    '''
    This hook executes after a successful transaction. 
    We already have the user requested packages in tsInfo, 
    awaiting the following information to be added:
    1. The current system time
    2. The installing user
    3. A short, optional logfile that the user is asked to supply at runtime
    '''
    from time import localtime, strftime
    completion_time = strftime("%d-%b-%Y,%H:%M:%S", localtime())
    tsInfo = conduit.getTsInfo()
    
    opts, commands = conduit.getCmdLine()
    notes = opts.notes

    #Create list of lists containing transaction archive objects
    history = map(lambda (x, y): createTransactionArchive(x, y, completion_time, notes),
                  ((tsInfo.removed, 'removed'),
                   (tsInfo.depremoved, 'depremoved'),
                   (tsInfo.installed, 'installed'),
                   (tsInfo.depinstalled, 'depinstalled'),
                   (tsInfo.updated, 'updated'),
                   (tsInfo.depupdated, 'depupdated'),
                   (tsInfo.obsoleted, 'obsoleted')
                   )
                  )
    #Flatten list
    history = reduce(lambda x, y: x + y, history)

    history_directory = conduit.confString('main', 'history_directory',
            default='/var/lib/yum/history/')

    #Write to file with name specified by date
    writeXML(date=completion_time.split(',')[0], 
                history=history,
                history_directory=history_directory)

def createTransactionArchive(transactionList, action, time, notes):
    '''
    Creates a list of transaction archive objects for a 
    transaction with a given action and time.
    '''
    transactions = []
    for pkg in transactionList:
        naevr = (pkg.name, pkg.arch, pkg.epoch, pkg.version, pkg.release)
        archive = TransactionArchive(action=action, 
                                     naevr=naevr, 
                                     reason=[], 
                                     repo=getattr(pkg.po, 'repoid'),
                                     notes=notes)
        archive.time = time
        archive.reason = pkg.relatedto
        transactions.append(archive)
    return transactions

def writeToFile(date, history, history_directory):
    '''
    DEPRECATED
    Writes the transaction record to file for future access.
    '''
    path = history_directory + date
    file = open(path, 'a')
    for pkg in history:
        file.write('!\n')
        file.write('name='+pkg.pkg_naevr[0])
        file.write('\narch='+pkg.pkg_naevr[1])
        file.write('\nepoch='+pkg.pkg_naevr[2])
        file.write('\nversion='+pkg.pkg_naevr[3])
        file.write('\nrelease='+pkg.pkg_naevr[4])
        file.write('\naction='+pkg.action)
        file.write('\ntime='+pkg.time.__str__())
        for reason in pkg.reason:
            file.write('\n'+reason.__str__())
        file.write('\n#\n')
    file.close()

def writeXML(date, history, history_directory):
    '''
    Serializes the transaction record to xml for future access.
    '''
    path = history_directory + date
    try:
        xml = parse(path)
        root = xml.childNodes[0]
    except IOError:
        xml = Document()
        root = xml.createElement('root')
    for pkg in history:
        pkg_xml, pkg_root = pkg.toXML()
        root.appendChild(pkg_root)
    file = open(path, 'w')
    file.write(root.toxml())
    file.close()

class TransactionArchive:
    '''
    Wrapper class for package info for yum history plugin.
    '''
    def __init__(self, 
                 action,      #(u)pgrade, (i)nstall, (e)rase
                 naevr,       #Name, Arch, Epoch, Version, Release
                 reason=[],   #removed, depremoved, installed, 
                              #depinstalled, updated, depupdated, obsoleted
                 time=None,   #date-month-year,hour:minute:second
                 user=None,   #The installer
                 notes=None,
                 repo='unknown'): #User supplied Notes
        self.action = action
        self.pkg_naevr = naevr
        self.reason = reason
        self.time = time
        self.user = user
        self.notes = notes
        self.repo = repo

    def toXML(self):
        '''
        Return an XML representation of this TransactionArchive
        '''
        xml = Document()
        root = xml.createElement('TransactionArchive')
        
        #Set action
        action = xml.createElement('action')
        action.setAttribute('value', self.action)
        root.appendChild(action)
        
        #Set package naevr
        n = xml.createElement('name')
        n.setAttribute('value', self.pkg_naevr[0])
        root.appendChild(n)

        a = xml.createElement('arch')
        a.setAttribute('value', self.pkg_naevr[1])
        root.appendChild(a)

        e = xml.createElement('epoch')
        e.setAttribute('value', self.pkg_naevr[2])
        root.appendChild(e)

        v = xml.createElement('version')
        v.setAttribute('value', self.pkg_naevr[3])
        root.appendChild(v)

        r = xml.createElement('release')
        r.setAttribute('value', self.pkg_naevr[4])
        root.appendChild(r)

        #Set time
        time = xml.createElement('time')
        time.setAttribute('value', self.time)
        root.appendChild(time)

        #Set repo
        repo = xml.createElement('repo')
        repo.setAttribute('value', self.repo)
        root.appendChild(repo)        
        
        #If we tracked it, set user
        if self.user != None: 
            user = xml.createElement('user')
            user.setAttribute('value', self.user)
            root.appendChild(user)
            
        #If we wrote one, set note
        if self.notes != None: 
            notes = xml.createElement('notes')
            notes.setAttribute('value', self.notes)
            root.appendChild(notes)
        
        #Set reasons for transaction
        for reason in self.reason:
            reason_node = xml.createElement('reason')
            reason_node.setAttribute('relatedTo', ';'.join(reason[0]))
            reason_node.setAttribute('relationType', reason[1])
            root.appendChild(reason_node)

        #Return what we have done
        return (xml, root)
_______________________________________________
Yum-devel mailing list
[email protected]
https://lists.dulug.duke.edu/mailman/listinfo/yum-devel

Reply via email to