#!/usr/bin/env python import sys, os, re, atexit, time, urllib, shutil import apt_pkg archive_root = 'http://archive.ubuntu.com/ubuntu' ports_root = 'http://ports.ubuntu.com/ubuntu' tempdir = None components = ('main', 'restricted', 'universe', 'multiverse') suites = ('dapper', 'hardy', 'intrepid', 'jaunty', 'karmic') # note that only 'LP: #1234' is official, but sometimes people get it wrong changelog_bug_pattern = re.compile('(?:lp:? ?#|href="/bugs/)([0-9]+)') #changelog_bug_pattern = re.compile('#([0-9]+)') published_date_pattern = re.compile('Published.*\n.*on ([-0-9]+)') def decompress_open(tagfile): if tagfile.startswith('http:') or tagfile.startswith('ftp:'): url = tagfile tagfile = urllib.urlretrieve(url)[0] atexit.register(os.unlink, tagfile) if tagfile.endswith('.gz'): import gzip import tempfile global tempdir if not tempdir: tempdir = tempfile.mkdtemp(prefix='suite-diff') atexit.register(shutil.rmtree, tempdir, True) decompressed = tempfile.mktemp(dir=tempdir) fin = gzip.GzipFile(filename=tagfile) fout = open(decompressed, 'wb') fout.write(fin.read()) fout.close() atexit.register(os.unlink, decompressed) return open(decompressed, 'r') else: return open(tagfile, 'r') def tagfiletodict(tagfile): suite = {} p = apt_pkg.ParseTagFile(decompress_open(tagfile)) while p.Step() == 1: suite[p.Section["Package"]] = p.Section["Version"] return suite def get_changelog_info(url): '''Parse LP per-version/per-release page URL and return tuple (date, bugs) with a publishing date (time record) and a bug list string.''' # enable this for quick testing #return (time.localtime(), '') chlog = urllib.urlopen(url).read() m = published_date_pattern.search(chlog) if m: date = time.strptime(m.group(1), '%Y-%m-%d') else: # less than a day ago (Soyuz uses "... hours ago") date = time.localtime() # cut out the actual changelog chlog = chlog[chlog.find('
')]

    bugnums = set()
    for m in changelog_bug_pattern.finditer(chlog):
	bugnums.add(m.group(1))
    bugs = ''
    for bug in bugnums:
        bugdata = urllib.urlopen('https://bugs.launchpad.net/bugs/%s' % bug).read()
	if 'field.tag=verification-failed' in bugdata:
           cls=' class="verificationfailed"'
        elif 'field.tag=verification-done' in bugdata:
           cls=' class="verified"'
        else:
           cls=''
	if 'field.tag=hw-specific' in bugdata:
           tag='(hw)'  
	else:
           tag=''
        bugs += '#%s%s ' % \
            (bug, cls, bug, tag)

    return (date, bugs)

def main():
    apt_pkg.InitSystem()

    pending = {} # component -> suite -> package -> (release_ver, proposed_ver, update_ver)
    security_superseded = {} # component -> suite -> package -> (proposed_ver, security_ver)
    cleanup = {} # suite -> [(package, version)]
    for suite in suites:
        for component in components:
            release = tagfiletodict('%s/dists/%s/%s/source/Sources.gz' %
                (archive_root, suite, component))
            proposed = tagfiletodict('%s/dists/%s-proposed/%s/source/Sources.gz' %
                (archive_root, suite, component))
            updates = tagfiletodict('%s/dists/%s-updates/%s/source/Sources.gz' %
                (archive_root, suite, component))
            security = tagfiletodict('%s/dists/%s-security/%s/source/Sources.gz' %
                (archive_root, suite, component))

            for package in sorted(proposed.keys()):
                update_ver = updates.get(package, '')
                if apt_pkg.VersionCompare(proposed[package], update_ver) > 0:
                    pending.setdefault(component, {}).setdefault(
                        suite, {})[package] = (release.get(package, ''),
                        proposed[package], update_ver)

                    security_ver = security.get(package, '')
                    if apt_pkg.VersionCompare(proposed[package], security_ver) < 0:
                        security_superseded.setdefault(component, {}).setdefault(
                            suite, {})[package] = (proposed[package], security_ver)
                else:
                    cleanup.setdefault(suite, []).append((package, proposed[package]))


#    a:link { color: blue; background: #CCCCB0; }
#    a:visited { color: blue; background: #CCCCB0; }
#    a:hover { color: red; background: #CCCCB0; }

    print '''



  
  Pending Ubuntu SRUs
  


Pending Ubuntu stable release updates

''' print '

Generated: %s by sru-report

' % time.strftime('%x %X UTC', time.gmtime()) print '

Jump to:', for component in components: print '%s' % (component, component), print 'security-superseded cleanup

' print '''

A stable release update is currently in progress for the following packages, i. e. they have a newer version in -proposed than in -updates.

Bugs in green are verified by QA, bugs in red failed verification.

''' for component in components: if not component in pending: continue print '

%s

' % (component, component) for suite in suites: if not suite in pending[component]: continue print '''

%s

''' % suite pkgs = pending[component][suite].keys() pkgs.sort() for pkg in pkgs: if pkg.startswith('language-pack-') and pkg != 'language-pack-en': continue lpurl = 'https://launchpad.net/ubuntu/%s/+source/%s/' % (suite, pkg) (vrel, vprop, vupd) = pending[component][suite][pkg] (date, bugs) = get_changelog_info(lpurl+vprop) age = int((time.time()-time.mktime(date))/86400) print ' \ \ \ \ ' % ( (lpurl, pkg, lpurl + vrel, vrel, lpurl+vupd, vupd, lpurl+vprop, vprop, bugs, age) ) print '
Package-release-updates-proposed changelog bugsdays
%s%s%s%s%s%i
' print '

Superseded by -security

' print '

The following SRUs have been shadowed by a security update and need to be re-merged:

' for component in components: if not component in security_superseded: continue print '

%s

' % (component, component) for suite in suites: if not suite in security_superseded[component]: continue print '''

%s

''' % suite pkgs = security_superseded[component][suite].keys() pkgs.sort() for pkg in pkgs: lpurl = 'https://launchpad.net/ubuntu/%s/+source/%s/' % (suite, pkg) (vprop, vsec) = security_superseded[component][suite][pkg] print ' \ \ ' % ( (lpurl, pkg, lpurl+vprop, vprop, lpurl+vsec, vsec) ) print '
Package-proposed-security
%s%s%s
' print '

-proposed cleanup

' print '

The following packages have an equal or higher version in -updates and should be removed from -proposed:

' for suite in suites: if suite not in cleanup: continue print '

%s

\n
' % suite
        for (pkg, ver) in cleanup[suite]:
            print 'lp-remove-package.py -y -u $SUDO_USER -m "moved to -updates" -s %s-proposed -e %s %s' % (
                suite, ver, pkg)
        print '
' print ''' ''' if __name__ == '__main__': main()