Hi Everyone,

Someone expressed interest in my hacked up version of the centos
errata script available here:

http://www.bioss.ac.uk/staff/davidn/spacewalk-stuff/

I've attached my version, but its really not that well done. I changed
it so that it will download messages for centos-announce from mail-archive.com
, then augment the data from RHN. It will also properly create errata
valid for multiple architectures. I think ive broken the other methods
of loading data in the process as well, so if you want those i suggest
you use the original script.

Just edit the config file to your liking, then run it using the
following:

./centos-errata.py -f scrape blah

You need any second argument since im lazy and didnt update the
arguments parsing parts of the script.

I doubt anyone will actually find this useful, but perhaps some of the
functions could be useful to someone who wanted to write a proper
script :)

-R



ZettaServe Disclaimer: This email and any files transmitted with it are 
confidential and intended solely for the use of the individual or entity to 
whom they are addressed. If you are not the named addressee you should not 
disseminate, distribute or copy this e-mail. Please notify the sender 
immediately if you have received this email by mistake and delete this email 
from your system. Computer viruses can be transmitted via email. The recipient 
should check this email and any attachments for the presence of viruses. 
ZettaServe Pty Ltd accepts no liability for any damage caused by any virus 
transmitted by this email.

Attachment: centos-errata.cfg
Description: centos-errata.cfg

#!/usr/bin/python
#
# Script which can process CentOS errata announcements and convert
# them to errata in spacewalk. Based on rhn-tool.py by Lars Jonsson
#
# Copyright (C) 2009  Biomathematics and Statistics Scotland
#               
# Author: Lars Jonsson (ljons...@redhat.com)
#         David Nutter (dav...@bioss.ac.uk)
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
#

from optparse import OptionParser
from datetime import datetime
import sys
import re
import os
import xmlrpclib
import libxml2
import email
import traceback
import rpm
import ConfigParser
import getpass
import urllib

#Config constants. 
CONFIG_FILE="centos-errata.cfg"
VALID_ARCH=set(["i386","x86_64","ia64","ppc", "alpha", "sparc", "s390", "s390(x)"])

#Things to match in centos-announce messages
DIGEST_BEGIN="----------------------------------------------------------------------\n\n"
DIGEST_SEPARATOR="------------------------------\n\n"
ARCHIVE_SEPARATOR="From .*[A-Za-z]{3,3} [A-Za-z]{3,3} [ 0-9]{2,2} \d{2,2}:\d{2,2}:\d{2,2} \d{4,4}\n"
ERRATA_SUBJECT="\[CentOS-announce\] (?P<errata_type>\w{4,4})-(?P<year>\d{4,4})(:|-)(?P<errata_id>\d{4,4})\s+(?P<other_info>.*)$"
PACKAGE_LIST="([\.\w-]+.rpm)"
URL_BASE="http://www.mail-archive.com/centos-annou...@centos.org/";

#Tags for the different errata types in email
SECURITY_ERRATA="CESA"
BUG_ERRATA="CEBA"
ENHANCE_ERRATA="CEEA"

#Special names for errata types in spacewalk
SECURITY='Security Advisory'
ENHANCEMENT='Product Enhancement Advisory'
BUGFIX='Bug Fix Advisory'


# Library used to represent a system ID in spacewalk
class RHNSystem:
    def __init__(self,sysid,name,lastCheckin):
        self.systemid=sysid
        self.name=name
        self.lastCheckin=lastCheckin

# Module to represent package info as returned by findByNVREA API function
# TODO: more substantial class required for package details
# TODO: maybe add epoch to getNVRA (if present), also rename method
class RHNPackage:
    def __init__(self,name,version,release,epoch,archLabel):
        self.id = None
        self.name = name
        self.version = version
        self.release = release
        self.epoch = epoch
        self.archLabel = archLabel
        self.path = None
        self.provider = None
        self.lastModified = datetime.today()

    def getNVRA(self):
        result = "%s-%s-%s.%s" % (self.name,self.version,self.release,self.archLabel)
        return result

# Library which represents an Errata in spacewalk.
# Used for creating and retrieving errata information
# TODO: Channel and Bugs should probably be types in their own right
class RHNErrata:
    def __init__(self):
        self.synopsis = None
        self.advisoryName = None
        self.advisoryRelease = 1
        self.advisoryType = SECURITY
        self.product = None
        self.topic = None
        self.description = None
        self.references = ""
        self.notes = ""
        self.solution = None

        self.publish = False

        self.channelLabel = []
        
        self.keywords=[]
        self.bugs=[]
        self.packages=[]

        self.issueDate = datetime.now()
        self.modifiedDate = datetime.now()
        self.updateDate = datetime.now()


    def getPackageIds(self):
        result=map((lambda pkg: pkg.id),self.packages)
        return result
    
    def getInfoDict(self):
        result={}
        result['synopsis']=self.synopsis
        result['advisory_name']=self.advisoryName
        result['advisory_release']=self.advisoryRelease        
        result['advisory_type']=self.advisoryType
        result['product']=self.product
        result['topic']=self.topic
        result['description']=self.description
        result['references']=self.references
        result['notes']=self.notes
        result['solution']=self.solution                
        return result

    def readyToCreate(self):
        info = self.getInfoDict()
        for required_attr in ['synopsis','advisory_name','advisory_release','advisory_type','product','topic','description','solution']:
            if info[required_attr] is None:
                return False

        return True
    
    def printOut(self):
        print "%-20s = %s" % ("Name:",self.advisoryName)
        print "%-20s = %s" % ("Release:",self.advisoryRelease)
        print "%-20s = %s" % ("Product:",self.product)
        print "%-20s = %s" % ("Synopsis:",self.synopsis)
        print "%-20s = %s" % ("Topic:",self.topic)
        print "%-20s = %s" % ("Description:",self.description)
        print "%-20s = %s" % ("Solution:",self.solution)
        print "%-20s = %s" % ("Target Channel:",self.channelLabel)

        for pkg in self.packages:
            print "  Package: %s" % pkg.getNVRA()

# Library used to communicate with Spacewalk
class RHNSession:
    def __init__(self, servername, user, passwd):
        self.rhnServerName = servername
        self.login = user
        self.password = passwd
        self.rhnUrl = 'https://'+self.rhnServerName+'/rpc/api'
        self.server = xmlrpclib.Server(self.rhnUrl)
        self.rhnSessionKey=self.rhnLogin(self.login,self.password)

    def rhnLogin(self, login, password): 
        try:
            rhnSessionKey=self.server.auth.login(login,password)
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                print "Session expired"
                self.rhnLogin(login,password)
            else:
                print "Failed to login",f
                raise
        return rhnSessionKey

    def getSystemByName(self,profileName):
        out=[]
        try:
            out=self.server.system.getId(self.rhnSessionKey,profileName)                
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.getSystemByName(profileName)
            else:
                raise

        systemObj=None
        if (len(out) > 0):
            systemObj = RHNSystem(out[0]['id'],out[0]['name'],out[0]['last_checkin'])
        return systemObj    
        

    def getSystemByID(self,systemid):
        out=[]
        try:
            out = self.server.system.getName(self.rhnSessionKey,systemid)
        except xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.getServerByID(systemid)
            else:
                raise

        systemObj=None
        if (len(out) > 0):
            systemObj = RHNSystem(out[0]['id'],out[0]['name'],out[0]['last_checkin'])
        return systemObj

    def getThisMachine(self):
       out=[]
       try:
           p = libxml2.parseDoc(file("/etc/sysconfig/rhn/systemid").read())
           systemid = p.xpathEval('string(//member[* = "system_id"]/value/string)').split('-')[1]
       except IOError:
           print "systemid file not found."
           raise

       systemObj = self.getSystemByID(systemid)
       return systemObj


    #TODO: this should probably return an object rather than a dictionary
    def getSystemDetails(self,systemObj):
        out={}
        try:
            out=self.server.system.getDetails(self.rhnSessionKey,int(systemObj.systemid))
        except xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.getSystemDetails(systemObj)
            else:
                raise

        return out

    def listGroups(self,systemObj):
        out=[]
        try:
            out=self.server.system.listGroups(self.rhnSessionKey,int(systemObj.systemid))
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.listGroups(systemObj)
            else:
                raise
        return out

    def listUserSystems(self):
        out=[]
        try:
            out=self.server.system.listUserSystems(self.rhnSessionKey)
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.listUserSystems()
            else:
                raise
        return out

    def listActivationKeys(self):
        out=[]
        try:
            out=self.server.activationkey.listActivationKeys(self.rhnSessionKey)
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.listActivationKeys()
            else:
                raise
        return out

    def deleteSystems(self,systemObj):
        out=[]
        try:
            out=self.server.system.deleteSystems(self.rhnSessionKey,systemObj.systemid)
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.deleteSystems(systemObj)
            else:
                raise
        return out

    def setGroupMembership(self,systemObj,groupid,join):
        out=[]
        try:
            out = self.server.system.setGroupMembership(self.rhnSessionKey,int(systemObj.systemid),groupid,join)
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.setGroupMembership(systemObj,groupid,join)
            else:
                raise
        return out

    def addNote(self,systemObj,label,msg):
        out=[]
        try:
            out = self.server.system.addNote(self.rhnSessionKey,int(systemObj.systemid),label,msg)
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.addNote(systemObj,label,msg)
            else:
                raise
        return out
        
    def setCustomValues(self,systemObj,customInfoDict):
        out=[]
        if not customInfoDict is None:
            try:
                out = self.server.system.setCustomValues(self.rhnSessionKey,int(systemObj.systemid),customInfoDict)
            except  xmlrpclib.Fault, f:
                if f.faultCode==-20:
                    self.rhnLogin(self.login,self.password)
                    return self.setCustomValues(systemObj,customInfoDict)
                else:
                    raise
        return out

    def setCustomValue(self,systemObj,label,value):
        customInfoArgs={label:value[0]}
        return self.setCustomValues(systemObj,customInfoArgs)
    
    def setSystemDetails(self,systemObj,detailsDict):
        out=0
        if not detailsDict is None:
            try:
                out = self.server.system.setDetails(self.rhnSessionKey,int(systemObj.systemid),detailsDict)
            except xmlrpclib.Fault, f:
                if f.faultCode==-20:
                    self.rhnLogin(self.login,self.password)
                    return self.setSystemDetails(systemObj,detailsDict)
                else:
                    raise
        return out
        
    def setNewProfileName(self,systemObj,name):
        out=[]
        try:
            systemObj.name=name
            out = self.server.system.setProfileName(self.rhnSessionKey,int(systemObj.systemid),name)
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.setProfileName(systemObj,name)
            else:
                raise
        return out

    def setGroup(self, systemObj, groupname, join=1): 
        for c in self.listGroups():
            if c['system_group_name'] == groupname:
                sgid=c['sgid']
                if int(c['subscribed']) == 1:
                    join = 0
                if join == 1:
                    self.setGroupMembership(systemObj, int(sgid),join)
                    print "System %s has joined %s " % systemObj.name, groupname
                else:
                    self.setGroupMembership(systemObj, int(sgid),join)
                    print "System %s has left %s" % systemObj.name, groupname

    def getCustomValues(self,systemObj):
        out={}
        try:
            out = self.server.system.getCustomValues(self.rhnSessionKey,int(systemObj.systemid))
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.getCustomKeyLabels()
            else:
                raise
        return out

    def addCustomKey(self,keyLabel,keyDescription):
        out=0
        try:
            out = self.server.system.custominfo.createKey(self.rhnSessionKey,keyLabel,keyDescription)
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.addCustomKey(keyLabel,keyDescription)
            else:
                raise
        return out
    
    def getCustomKeyLabels(self):
        out=set()
        result=[]
        try:
            result = self.server.system.custominfo.listAllKeys(self.rhnSessionKey)
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.getCustomKeyLabels()
            else:
                raise

        for keyInfo in result:
            out.add(keyInfo['label'])

        return out

    def getErrataDetails(self,advisoryName):
        result=None
        try:
            result = self.server.errata.getDetails(self.rhnSessionKey,advisoryName)
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.getErrataDetails(advisoryName)
            elif f.faultCode==-208: #This seems to be the fault returned when the errata does not exist
                result = None                
            else:
                raise

        if not result is None:
            errata = RHNErrata()
            errata.advisoryName=advisoryName
            errata.issueDate = result['issue_date']
            errata.modifiedDate = result['update_date']
            errata.updateDate = result['last_modified_date']
            errata.description=result['description']
            errata.synopsis=result['synopsis']
            errata.topic=result['topic']
            errata.references=result['references']
            errata.notes=result['notes']
            errata.advisoryType=result['type']
        else:
            errata = None

        return errata

        
    
    def findPackageByNVREA(self,pkg_info):
        result= None
        pkg_details = None
        try:
            #Fortunately this RPC method returns an empty list if the package does not exist, no need to handle an undocumented exception
            if pkg_info.epoch:
                result = self.server.packages.findByNvrea(self.rhnSessionKey,pkg_info.name,pkg_info.version,pkg_info.release,pkg_info.epoch,pkg_info.archLabel)
            else:
                result = self.server.packages.findByNvrea(self.rhnSessionKey,pkg_info.name,pkg_info.version,pkg_info.release,"",pkg_info.archLabel)

            if len(result) > 0:
                pkg_details = result[0]
                
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.findPackageByNVREA(pkg_info)
            else:
                raise

        
        if not pkg_details is None:
            server_pkg = RHNPackage(pkg_details['name'],pkg_details['version'],pkg_details['release'],pkg_details['epoch'],pkg_details['arch_label'])
            server_pkg.id = pkg_details['id']
            server_pkg.path = pkg_details['path']
            server_pkg.provider = pkg_details['provider']
            server_pkg.lastModified=pkg_details['last_modified']
            return server_pkg

        return None


    def createErrata(self,erratum):
        result= None

        #Note: this method has not been tested when the errata has any bugs or keywords.
        #Sending the value [{}] for bugs seems to cause a crash - maybe you need [{id:"12345",name:"foobug"}] or similar for it to work
        if not erratum.readyToCreate():
            raise

        try:
                result = self.server.errata.create(self.rhnSessionKey,erratum.getInfoDict(),erratum.bugs,erratum.keywords,erratum.getPackageIds(),erratum.publish,erratum.channelLabel)
            
        except  xmlrpclib.Fault, f:
            if f.faultCode==-20:
                self.rhnLogin(self.login,self.password)
                return self.createErrata(erratum)
            else:
                raise

        return result




def establishSession(options,cmdName):
    if (options.server and options.login) is None:
        print "Please specify --server and --login. Try: "+cmdName+" --help"
        sys.exit(2)

    try:
        while options.passwd is None:
            options.passwd = getpass.getpass("RHN Password: ")   
    except Exception, e:
        print "Terminal does not seem to be functional. You should specify a password with --password. Aborting."
        sys.exit(2)
        
    mySession = RHNSession(options.server,options.login,options.passwd)
    return mySession
    

def process_args():

    config = ConfigParser.SafeConfigParser()
    config.readfp(open(CONFIG_FILE))
    
    parser = OptionParser(usage="%prog [options] file",version="%prog 0.1")
    parser.add_option("-s", "--server", type="string", dest="server",
                      help="RHN Satellite server hostname")
    parser.add_option("-l", "--login", type="string", dest="login",
                      help="RHN Login") 
    parser.add_option("-p", "--password", type="string", dest="passwd",
                      help="RHN password (cleartext)")
    
    parser.add_option("-f","--format",type="string",dest="format",default="digest",
                      help="Select input format for tool. Default is digest. Valid options are digest, archive, mbox")
    parser.add_option("-t","--test",action="store_true",dest="testmode", default=False,
                      help="Do not connect to the RHN server, just process the input file and print errata information. Will also print configuration information")
    parser.add_option("","--centos-version",type="string",dest="centos_version",
                      help="The centos version (e.g. '5' for Centos 5.3) ")
    parser.add_option("","--centos-arch",type="string",dest="centos_arch",
                      help="The CentOS architecture (e.g. i386, x86_64 etc)")
    parser.add_option("","--packagedir",type="string",dest="package_dir",
                      help="Where RPM files from CentOS updates are stored")
    parser.add_option("","--update-channel",type="string",dest="update_channel",
                      help="Imported errata will be published to this channel")         

    parser.set_defaults(centos_version=config.get("centos errata","version"),
                        centos_arch=config.get("centos errata","arch"),
                        package_dir=config.get("centos errata","package_dir"),
                        update_channel=config.get("centos errata","update_channel"))
    if config.has_option("spacewalk","server"):
        parser.set_defaults(server=config.get("spacewalk","server"))
    if config.has_option("spacewalk","password"):
        parser.set_defaults(password=config.get("spacewalk","password"))
    if config.has_option("spacewalk","login"):
        parser.set_defaults(login=config.get("spacewalk","login"))

    (options,args) = parser.parse_args()
    return (options,args,config)

def process_pkg_file(pkgfile):
    #Swiped from http://www.sharms.org/blog/2009/05/21/python-rpm/ as rpm-python has no documentation
    #TODO: better error handling/reporting here
    rpmQuery = rpm.ts()
    try:
        fd = os.open(pkgfile, os.O_RDONLY)
        header = rpmQuery.hdrFromFdno(fd)
        os.close(fd)
    except:
        return None

    pkgInfo = RHNPackage(header['name'],header['version'],header['release'],header['epoch'],header['arch'])

    return pkgInfo

#Load email and decide on whether to act on it
#Return None if no useful errata information is found in the email or a RHNErrata object if info was found
def process_msg(msg_text,options):
    subject_re = re.compile(ERRATA_SUBJECT)
    packagelist_re = re.compile(PACKAGE_LIST)
    sec_info_re = re.compile("(?P<severity>\w+) CentOS "+options.centos_version+" "+options.centos_arch+"\s+(?P<synopsis>.*)$")
    bug_info_re = re.compile("CentOS "+options.centos_version+" "+options.centos_arch+"\s+(?P<synopsis>.*)$" )
    enhance_info_re = re.compile("CentOS "+options.centos_version+" "+options.centos_arch+"\s+(?P<synopsis>.*)$" )

    
    try:
        errataMsg = email.message_from_string(msg_text)
        stripNewLine = re.compile('\n')
        subject = errataMsg.get("Subject")
        
        if subject is None:
            return None
        
        subject = stripNewLine.sub("",subject)

        subject_match = subject_re.match(subject)
        if not subject_match is None:

            errata_type = subject_match.group('errata_type')
            
            erratum = RHNErrata()
            erratum.product = "CentOS "+options.centos_version+" "+options.centos_arch
            erratum.advisoryName = errata_type+"-"+subject_match.group('year')+":"+subject_match.group('errata_id')
            erratum.channelLabel = options.update_channel
            erratum.publish = True
            
            if errata_type == SECURITY_ERRATA:
                info_match = sec_info_re.match(subject_match.group('other_info'))
                erratum.advisoryType=SECURITY
            elif errata_type == BUG_ERRATA:
                info_match = bug_info_re.match(subject_match.group('other_info'))
                erratum.advisoryType=BUGFIX
            elif errata_type == ENHANCE_ERRATA:
                info_match = enhance_info_re.match(subject_match.group('other_info'))
                erratum.advisoryType=ENHANCEMENT
            else:
                print "Unknown errata type %s, assuming type BUG" % subject_match.group('errata_type')
                errata_type = BUG_ERRATA
                erratum.advisoryType=BUGFIX
                info_match = bug_info_re.match(subject_match.group('other_info'))

            if not info_match is None:
                rhn_url = "https://rhn.redhat.com/errata/"+errata_type+"-"+subject_match.group('year')+"-"+subject_match.group('errata_id')+".html"
                rhn_url=re.sub(r"/CE","/RH",rhn_url)
                erratum.topic=rhn_url
                erratum.description="Automatically imported CentOS erratum"
                erratum.solution="Install these packages to correct the erratum"
                
                if info_match.groupdict().has_key('severity'):
                    erratum.synopsis= info_match.group('severity')+": "+info_match.group('synopsis')
                else:
                    erratum.synopsis= info_match.group('synopsis')

                #Process the packages list, ignoring SRPMs
                for pkgfile in packagelist_re.findall(errataMsg.get_payload()):
                    if not pkgfile.endswith(".src.rpm"):
                        pkg_info = process_pkg_file(options.package_dir+pkgfile)
                        if not pkg_info is None:
                            erratum.packages.append(pkg_info)
                        else:
                            print "Package %s%s does not exist or cannot be read. Skipping errata %s" % (options.package_dir,pkgfile,erratum.advisoryName)
                            return None
                
                return erratum
            else:
                pass
                print "Errata '%s' is inapplicable to the targeted CentOS release " % re.sub("\s+"," ",subject)
                
        else:
            print "Message with subject '%s' does not appear to be an errata" % subject
            
    except Exception, e:
        print "Failed to process message. Reason:"
        print e
        traceback.print_exc(file=sys.stdout)

    return None


def download_errata(config):

    parsed_errata = 0
    max_errata = 10000
    
    if config.has_option("centos errata", "max_errata") is True:
        max_errata = config.getint("centos errata", "max_errata")

    messages_list_f = urllib.urlopen(URL_BASE+"maillist.html")
    s = messages_list_f.read()
    messages_list_f.close()
    
    lines = s.split("\n")
    
    subjects_re = re.compile("<span class=\"subject\"><a name=\"(?P<msgid>\d+)\" href=\"(?P<relurl>[\w.]+)\">(?P<subject>[^<]+)</a></span>")
    erratum_subject_re = re.compile(ERRATA_SUBJECT)
    packagelist_re = re.compile("([\.\w-]+.rpm)")
    sec_info_re = re.compile("(?P<severity>\w+) CentOS\s+(?P<version>\d)\s+(?P<arch>\w+)\s+(?P<synopsis>.*)$")
    bug_info_re = re.compile("CentOS\s+(?P<version>\d)\s+(?P<arch>\w+)\s+(?P<synopsis>.*)$" )
    enhance_info_re = re.compile("CentOS\s+(?P<version>\d)\s+(?P<arch>\w+)\s+(?P<synopsis>.*)$" )
    
    rhn_details_re = re.compile("<h2>Details</h2>(\s+)<div class=\"page-summary\">(?P<details>[\w\W\s]+)</div>(\s+)<br />(\s+)<h2>Solution</h2>")
    rhn_solution_re = re.compile("<h2>Solution</h2>(\s+)<div class=\"page-summary\">(?P<solution>[\w\W\s]+)</div>(\s+)<br />(\s+)<h2>Updated packages</h2>")
    
    errata_list = {}
    
    for line in lines:
        subjects_match = subjects_re.match(line)
    
        if not subjects_match is None and parsed_errata < max_errata:

            erratum_subject_match = erratum_subject_re.match(subjects_match.group('subject'))
    
            if not erratum_subject_match is None:
                advisory_name = erratum_subject_match.group('errata_type')+"-"+erratum_subject_match.group('year')+":"+erratum_subject_match.group('errata_id')
                
                if errata_list.has_key(advisory_name) is False:
                    erratum = RHNErrata()
                else:
                    erratum = errata_list[advisory_name]
                    
                erratum.advisoryName = advisory_name
                relurl = subjects_match.group('relurl')
                errata_type = erratum_subject_match.group('errata_type')
    
                if errata_type == SECURITY_ERRATA:
                    info_match = sec_info_re.match(erratum_subject_match.group('other_info'))
                    erratum.advisoryType=SECURITY
                elif errata_type == BUG_ERRATA:
                    info_match = bug_info_re.match(erratum_subject_match.group('other_info'))
                    erratum.advisoryType=BUGFIX
                elif errata_type == ENHANCE_ERRATA:
                    info_match = enhance_info_re.match(erratum_subject_match.group('other_info'))
                    erratum.advisoryType=ENHANCEMENT
                else:
                    print "Unknown errata type %s, assuming type BUG" % erratum_subject_match.group('errata_type')
                    errata_type = BUG_ERRATA
                    erratum.advisoryType=BUGFIX
                    info_match = bug_info_re.match(erratum_subject_match.group('other_info'))
                
                if not info_match is None:
                    erratum_arch = info_match.group('arch')
                
                    if config.has_option(erratum_arch, "channel") is True and info_match.group('version') == config.get("centos errata", "version"):
                        erratum.channelLabel.append(config.get(erratum_arch, "channel"))
                        erratum.product = "CentOS "+info_match.group('version')
                        erratum.publish = True

                        rhn_url = "https://rhn.redhat.com/errata/"+errata_type+"-"+erratum_subject_match.group('year')+"-"+erratum_subject_match.group('errata_id')+".html"
                        rhn_url=re.sub(r"/CE","/RH",rhn_url)
                        erratum.topic = rhn_url
                        
                        if info_match.groupdict().has_key('severity'):
                            erratum.synopsis = info_match.group('severity')+": "+info_match.group('synopsis').replace('\t', ' ')
                        else:
                            erratum.synopsis = info_match.group('synopsis').replace('\t', ' ')
        
                        print "Downloading package data for " + advisory_name 
                                           
                        message_f = urllib.urlopen(URL_BASE+subjects_match.group('relurl'))
                        message_src = message_f.read()
                        message_f.close()
                                  
                        pkg_dir = config.get(erratum_arch,"package_dir")
                                  
                        #Process the packages list, ignoring SRPMs
                        for pkgfile in packagelist_re.findall(message_src):
                            if not pkgfile.endswith(".src.rpm"):
                                pkg_info = process_pkg_file(pkg_dir+pkgfile)
                                if not pkg_info is None:
                                    erratum.packages.append(pkg_info)
                                else:
                                    print "Package %s%s does not exist or cannot be read. Errata may %s be incomplete" % (pkg_dir,pkgfile,erratum.advisoryName)
                                    #print "Package %s%s does not exist or cannot be read. Skipping errata %s" % (pkg_dir,pkgfile,erratum.advisoryName)
                                    #return None
                            
                        if erratum.description is None:
                        
                            print "Downloading RHN data for " + advisory_name 
                                           
                            message_f = urllib.urlopen(rhn_url)
                            message_src = message_f.read()
                            message_f.close()
                        
                            for details in rhn_details_re.findall(message_src):
                                erratum.description = details[1]
                                erratum.description = erratum.description.replace("<br />*", "\n*")
                                erratum.description = erratum.description.replace("<br />", "\n")
                                erratum.description = erratum.description.replace("<p>", "")
                                erratum.description = erratum.description.replace("</p>", "")
                                erratum.description = erratum.description.replace("&quot;", "'")
                                erratum.description = erratum.description.replace("Red Hat Enterprise Linux", "CentOS")
        
                            if erratum.description is None:
                                erratum.description ="Automatically imported CentOS erratum"
                        
                        if erratum.solution is None:
                            erratum.solution = "Install these packages to correct the erratum"                
                        
                        errata_list[advisory_name] = erratum
                        parsed_errata += 1
                    else:
                        print "Errata '%s' is inapplicable to the targeted CentOS release " % subjects_match.group('subject')
                else:
                    print "Errata '%s' doesnt look like an erratum " % line
              
    return errata_list

def main():
    (options,args,config) = process_args()

    inputFile = args[0]
    errata=[]
    
    if not options.format == "scrape":
        if inputFile is None:
            print "I need an input filename. See %s --help" % sys.argv[0]
            sys.exit(2)
        elif not os.path.exists(inputFile):
            print "Input file %s does not exist" % inputFile
            sys.exit(2)
        elif not os.path.isfile(inputFile):
            print "Input file %s is not a normal file" % inputFile
            sys.exit(2)
        elif not os.access(inputFile,os.R_OK):
            print "Input file %s is not readable" % inputFile
            sys.exit(2)

    if not os.path.exists(options.package_dir):
        print "Package directory %s does not exist" % options.package_dir
        sys.exit(2)
    elif not os.path.isdir(options.package_dir):
        print "Package dir %s is not a directory" % options.package_dir
        sys.exit(2)
    elif not os.access(options.package_dir,os.R_OK):
        print "Package dir %s is not readable" % options.package_dir
        sys.exit(2)

    if not options.centos_arch in VALID_ARCH:
        print "%s does not appear to be a valid architecture" % options.centos_arch
        sys.exit(2)
        
    if options.testmode:
        print "Current configuration:"
        for option,value in options.__dict__.items():
            print "%-20s = %s" % (option,value)
            
        print "Max errata = " + config.get("centos errata", "max_errata")
    else:
        session = establishSession(options,sys.argv[0])
    
    if not options.format == "scrape":
        try:
            inputData=open(inputFile).read()
        except IOError,msg:
            print "Failed to read %s. Reason"
            print msg
            sys.exit(2)
        
    if options.format == "digest":
        digestMsg=email.message_from_string(inputData)
        if digestMsg.is_multipart():
            print "Don't know how to handle multipart messages"
            sys.exit(2)


        #TODO: catch index exception here if we don't have a digest
        digestPayloads = digestMsg.get_payload().split(DIGEST_BEGIN)[1]
        for msg in digestPayloads.split(DIGEST_SEPARATOR):
            new_errata = process_msg(msg,options)
            if not new_errata is None:
                errata.append(new_errata)

        
    elif options.format == "mbox":
        print "Not supported just now"
        sys.exit(2)
    elif options.format == "archive":
        #Split on lines formmated thusly: From kbsingh at centos.org  Thu Jan  8 16:25:09 2009
        splitter = re.compile(ARCHIVE_SEPARATOR)

        for msg in splitter.split(inputData):
            new_errata = process_msg(msg,options)
            if not new_errata is None:
                errata.append(new_errata)
    elif options.format == "scrape":
        errata_dict = download_errata(config)
        errata = []
        for key in errata_dict.keys():
            errata.append(errata_dict[key])
    else:
        print "Unknown input format %s" % options.format
        sys.exit(2)


    #Process any errata we have
    if len(errata) > 0:
        print "Processing %d errata..." % len(errata)

        for erratum in errata:
            try:
                if options.testmode:
                    print "In test mode. Not checking server for existing erratum %s" % erratum.advisoryName
                    erratum.printOut()
                    print "------"
                else:
                    skip = False
                    if not session.getErrataDetails(erratum.advisoryName) is None:
                        print "Errata %s already exists on server, skipping" % erratum.advisoryName
                        continue

                    for pkg_info in erratum.packages:
                        rhn_pkg_info = session.findPackageByNVREA(pkg_info)
                        if not rhn_pkg_info is None:
                            pkg_info.id=rhn_pkg_info.id
                        else:
                            print "Package %s is not available on the server. " % pkg_info.getNVRA 
                            skip = True
                
                    if skip:
                        print "Skipping erratum %s due to missing packages" % erratum.advisoryName
                        continue
                    else:
                        print "Posting erratum %s to spacewalk server." % erratum.advisoryName
                        session.createErrata(erratum)

            except Exception,e:
                print "An exception occured when communicating with the server. Skipping erratum %s. Reason:" % erratum.advisoryName
                print e
                traceback.print_exc(file=sys.stdout)

    
if __name__ == "__main__":
    main() 
_______________________________________________
Spacewalk-list mailing list
Spacewalk-list@redhat.com
https://www.redhat.com/mailman/listinfo/spacewalk-list

Reply via email to