#!/usr/bin/python
#
# Compares prior build logs with current build logs.
#
# Copyright (C) 2009-2010, Canonical, Ltd.
# Author: Kees Cook <kees@ubuntu.com>

import sys, optparse, time, os, tempfile, subprocess, shutil, urllib2
import lpl_common
import apt_pkg
from launchpadbugs import http_connection
from configobj import ConfigObj


# Cookie for protected file downloads
cookie_processor = http_connection.LPCookieProcessor()
config = ConfigObj(os.path.expanduser("~/.ubuntu-cve-tracker.conf"))
cookie_file = config["plb_authentication"]
# Work around Firefox 3.5's dumb sqlite locking problems by copying cookies out:
sql = None
if cookie_file.endswith('.sqlite'):
    sql = tempfile.NamedTemporaryFile(prefix='cookies-XXXXXX', suffix='.sqlite')
    cookie_file = sql.name
    shutil.copy(config["plb_authentication"], cookie_file)
cookie_processor.load_file(cookie_file)
opener = urllib2.build_opener(cookie_processor)
sql = None


apt_pkg.InitSystem();

parser = optparse.OptionParser()
parser.add_option("--ppa", help="The PPA to load from (default: ubuntu-security/ppa)", metavar="GROUP[/PPA]", action='store', default='ubuntu-security/ppa')
parser.add_option("-r","--release", help="Limit unembargo to a specific set of comma-separate releases", metavar="SERIES", action='store', default=None)
parser.add_option("-n","--dry-run", help="Do not actually fetch logs", action='store_true')
parser.add_option("--lpnet", help="Use lpnet instead of edge for LP API", action='store_true')
parser.add_option("-b", "--base", help="The archive to load prior logs from (default: Ubuntu Archive)", metavar="GROUP[/PPA]", action='store', default='ubuntu')
(opt, args) = parser.parse_args()

if len(args) < 1:
    print >>sys.stderr, 'Usage: %s [OPTIONS] PKG [PKG...]' % (sys.argv[0])
    sys.exit(1)

print "Loading Ubuntu Distribution ..."
lp = lpl_common.connect(use_edge=(not opt.lpnet))
ubuntu = lp.distributions['ubuntu']

def split_ppa(group):
    if '/' in group:
        group, ppa = group.split('/',1)
    else:
        ppa = 'ppa'
    return group, ppa

def get_archive(name):
    if name == 'ubuntu':
        print "Loading Ubuntu Archive ..."
        archive, partner = ubuntu.archives
        group = 'ubuntu'
        ppa = None
    else:
        group, ppa = split_ppa(name)
        print "Loading %s '%s' PPA ..." % (group, ppa)
        archive = lp.people[group].getPPAByName(name=ppa)
    return archive, group, ppa

base_archive, base_group, base_ppa = get_archive(opt.base)
origin_archive, origin_group, origin_ppa = get_archive(opt.ppa)

series = dict()
if opt.release:
    serieses = opt.release.split(',')
    for series_name in serieses:
        series.setdefault(series_name, ubuntu.getSeries(name_or_version=series_name))

def get_latest_versions(pkg_name, archive, series):
    matches = dict()
    seen = dict()

    # Locate the package to copy
    print "Locating %s in %s ..." % (pkg_name, archive)
    sources = archive.getPublishedSources(source_name=pkg_name,
                                          status='Published',
                                          exact_match=True)

    # Filter for the series we want
    for source_item in sources:
        series_name = source_item.distro_series.name
        if len(series) > 0 and not series_name in series:
            continue
        if not seen.has_key(series_name):
            seen[series_name] = []
        seen[series_name].append(source_item)

    # Identify most recent package version across all pockets
    for series_name in seen.keys():
        if len(seen[series_name]) > 1:
            seen[series_name] = sorted(seen[series_name], cmp=lambda x, y: apt_pkg.VersionCompare(x.source_package_version, y.source_package_version), reverse=True)
        if len(seen[series_name]) > 0:
            matches.setdefault(series_name, seen[series_name][0])

    return matches

def get_builds(source_item):
    builds = dict()

    build_collection = source_item.getBuilds()
    for build in build_collection:
        builds.setdefault(build.arch_tag, build)

    return builds

def compare_build_logs(pkg_name, series_name, current, prior):
    rc = True

    current_builds = get_builds(current)
    prior_builds   = get_builds(prior)

    for arch in sorted(current_builds.keys()):
        print '\tcurrent: %s' % (arch)
    for arch in sorted(prior_builds.keys()):
        print '\tprior: %s' % (arch)

    # Verify we have all archs in each build history
    missing = set(current_builds.keys()) ^ set(prior_builds.keys())
    for arch in missing:
        if arch not in prior_builds.keys():
            version = prior.source_package_version
        else:
            version = current.source_package_version
        print >>sys.stderr, "\tCannot find arch '%s' for '%s' version '%s'!?" % (arch, pkg_name, version)
        rc = False
    if len(missing)>0:
        return rc

    for arch in sorted(current_builds.keys()):
        print '\t%s(%s) %s(%s): %s' % (pkg_name, series_name, current.source_package_version, arch, current_builds[arch].build_log_url)
        print '\t%s(%s) %s(%s): %s' % (pkg_name, series_name, prior.source_package_version, arch, prior_builds[arch].build_log_url)
        print ''

        if opt.dry_run:
            continue

        pwd = os.getcwd()
        dir = tempfile.mkdtemp(prefix='log-compare-')
        os.chdir(dir)
        logs = []
        for url in [prior_builds[arch].build_log_url, current_builds[arch].build_log_url]:
            base = os.path.basename(url)
            file(base, 'w').write(opener.open(url).read())

            if base.endswith('.gz'):
                if subprocess.call(['gunzip',base]) != 0:
                    if os.path.exists(base):
                        os.unlink(base)
                    rc = False
                    break
                base = base[:-3]
            logs.append(os.path.join(dir,base))
        os.chdir(pwd)

        if len(logs) == 2:
            #print 'buildlog-compare %s %s %s vs %s' % (pkg_name, arch, prior.source_package_version, current.source_package_version)
            subprocess.call([os.path.join(os.environ['UST'],'build-tools/buildlog-compare'), pkg_name, prior.source_package_version, logs[0], current.source_package_version, logs[1]])

        # Clean up
        for log in logs:
            os.unlink(log)
        os.rmdir(dir)

    return rc


rc = 0
for pkg_name in args:
    current = get_latest_versions(pkg_name, origin_archive, series)
    prior   = get_latest_versions(pkg_name, base_archive, series)

    # Verify we have the each current series available in prior
    missing = set(current.keys()) - set(prior.keys())
    for series_name in missing:
        print >>sys.stderr, "Cannot find version before '%s' for '%s' in '%s'!?" % (current[series_name].source_package_version, pkg_name, series_name)
        rc = 1
    if len(missing) > 0:
        continue

    for series_name in sorted(current.keys()):
        if current[series_name].source_package_version == prior[series_name].source_package_version:
            print >>sys.stderr, "Oops, same version '%s' for '%s' in '%s'!" % (current[series_name].source_package_version, pkg_name, series_name)
            rc = 1
            continue

        print '%s: %s vs %s' % (series_name, current[series_name].source_package_version, prior[series_name].source_package_version)

        if not compare_build_logs(pkg_name, series_name, current[series_name], prior[series_name]):
            rc = 1

        print ''

sys.exit(rc)
