#!/usr/bin/env python2
#

from sys                                import argv, stdout, stderr
from getopt                             import getopt, GetoptError
from lpltk.LaunchpadService             import LaunchpadService
import re

# error
#
# Print strings to standard out preceded by "error:".
#
def error(out):
    stderr.write("\n ** Error: %s\n" % out)
    stderr.flush()

# CmdlineError
#
# The type of exception that will be raised by Cmdline.process() if there
# are command line processing errors.
#
class CmdlineError(Exception):
    # __init__
    #
    def __init__(self, error):
        self.msg = error

# Cmdline
#
# Do all the command line processing.
#
class Cmdline(object):
    # __init__
    #
    def __init__(self):
        self.cfg = {}

    # error
    #
    def error(self, e, defaults):
        if e != '':
            print(e)
        self.usage(defaults)

    # usage
    #
    # Prints out the help text which explains the command line options.
    #
    def usage(self, defaults):
        print("                                                                                             ")
        print("  This utility creates a 'CVE tracking bug' which is associated with a particular CVE and    ")
        print("  the associated patches that are applied to the relevant series. This utility will:         ")
        print("     - Create a new bug using the supplied CVE number as part of the title.                  ")
        print("     - Mark the bug as a security vulnerability.                                             ")
        print("     - Add all relevant tags.                                                                ")
        print("     - Nominate for all active distro series.                                                ")
        print("     - Link to the related CVE.                                                              ")
        print("                                                                                             ")
        print("    Usage:                                                                                   ")
        print("        %s [options]                                                                         " % self.cfg['app_name'])
        print("                                                                                             ")
        print("    Options:                                                                                 ")
        print("        --help           Prints this text.                                                   ")
        print("                                                                                             ")
        print("        --staging        Use the staging LP server to create the bug. This is just for       ")
        print("                         testing. The bug created on the staging LP service will disappear   ")
        print("                         when the database is reloaded (which happens weekly).               ")
        print("                                                                                             ")
        print("        --verbose        Print messages indicating what the script is doing.                 ")
        print("                                                                                             ")
        print("        --private        Mark bug as private.                                                ")
        print("                                                                                             ")
        print("        --cve            The CVE that this is to create a tracking bug for. This parameter   ")
        print("                         is required.                                                        ")
        print("                                                                                             ")
        print("    Examples:                                                                                ")
        print("        %s --cve=2010-4079                                                                   " % self.cfg['app_name'])
        print("        %s --cve=2010-4079 --staging                                                         " % self.cfg['app_name'])

    # process
    #
    # As you can probably tell from the name, this method is responsible
    # for calling the getopt function to process the command line. All
    # parameters are processed into class variables for use by other
    # methods.
    #
    def process(self, argv, defaults):
        self.cfg['app_name'] = argv[0]
        result = True
        try:
            optsShort = ''
            optsLong  = ['help', 'staging', 'cve=', 'verbose', 'private']
            opts, args = getopt(argv[1:], optsShort, optsLong)

            for opt, val in opts:
                if (opt == '--help'):
                    raise CmdlineError('')

                elif (opt == '--staging'):
                    self.cfg['staging'] = True

                elif (opt == '--verbose'):
                    self.cfg['verbose'] = True

                elif (opt == '--private'):
                    self.cfg['private'] = True

                elif (opt == '--cve'):
                    self.cfg['cve'] = val

        except GetoptError as error:
            print(error, defaults)
            raise CmdlineError('')

        return self.cfg

    # verify_options
    #
    def verify_options(self, cfg):

        if 'cve' not in self.cfg:
            raise CmdlineError('You are required to specify a CVE.')

        if self.cfg['cve'] == '':
            raise CmdlineError('You are required to specify a CVE.')

        m = re.match('[0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]', self.cfg['cve'])
        if m == None:
            raise CmdlineError('The specified CVE does not match a CVE format.')

        return


# AppError
#
# A general exception that can be raised when an error is encountered in the app.
#
class AppError(Exception):
    # __init__
    #
    def __init__(self, error=''):
        self.msg = error

# CreateCveTracker
#
class CreateCveTracker(object):
    # __init__
    #
    def __init__(self):
        self.defaults = {}

    # initialize
    #
    def initialize(self):
        self.defaults['launchpad_client_name'] = 'kernel-team-create-cve-tracker'
        if 'staging' in self.cfg:
            self.defaults['launchpad_services_root'] = 'qastaging'
        self.verbose("Connecting to Launchpad ...\n")
        self.lp = LaunchpadService(self.defaults)
        self.verbose("   (connected)\n")

    # main
    #
    def main(self):
        cmdline = Cmdline()
        try:
            self.merge_config_options(self.defaults, cmdline.process(argv, self.defaults))
            cmdline.verify_options(self.cfg)
            self.initialize()

            lp = self.lp.launchpad

            # Title: CVE-xxxx-xxxx
            #
            # lpltk doesn't allow us the create a private bug from the start so
            # we must create the bug with a fake title and then set the real
            # title after making the bug private.
            if 'private' in self.cfg:
                title = "CVE-YYYY-NNNN"
            else:
                title = "CVE-%s" % (self.cfg['cve'])

            # Description:
            #    This bug is for tracking the <version> upload package. This bug will
            #    contain status and testing results related to that upload.
            #
            description = "Placeholder"

            try:
                self.verbose("Creating the bug.\n")
                bug = self.lp.create_bug(project='ubuntu', package='linux', title=title, description=description)

                if 'staging' in self.cfg:
                    print("https://bugs.qastaging.launchpad.net/bugs/%s" % (bug.id))
                else:
                    print("https://bugs.launchpad.net/bugs/%s" % (bug.id))

                try:
                    # Immediately make the bug private, if necessary, before
                    # doing anything else and fix up the title with the correct
                    # CVE number.
                    if 'private' in self.cfg:
                        self.verbose("Marking as private ...\n")
                        bug.private = True

                        title = "CVE-%s" % (self.cfg['cve'])
                        self.verbose("Setting title to %s ...\n" % (title))
                        bug.title = title

                    self.verbose("Updating tags ...\n")
                    bug.tags.append('kernel-cve-tracking-bug')
                    self.verbose("Marking as security ...\n")
                    bug.security_related = True

                    # Link the appropriate cve to the bug
                    # Cannot safely use 'linkCVE' due to LP: #439470
                    self.verbose("Linking to %s ..." % (title))
                    bug.add_comment(content=title)

                    lp = self.lp.launchpad
                    ubuntu = lp.distributions["ubuntu"]
                    # Add bug tasks for related source packages
                    #
                    self.verbose("Adding additional packages")
                    pkgs = ['linux-ti-omap4', 'linux-raspi2', 'linux-snapdragon']
                    for p in pkgs:
                        self.verbose("Finding source package '%s' ..." % (p))
                        pkg = ubuntu.getSourcePackage(name=p)
                        self.verbose("Adding bug task for '%s' ..." % (p))
                        t = bug.lpbug.addTask(target=pkg)

                    # Nominate for all active series
                    #
                    sc = ubuntu.series_collection
                    for s in sc:
                        if s.active:
                            self.verbose("Adding nominations '%s' \n" % s.name)
                            nomination = bug.lpbug.addNomination(target=s)
                            if nomination.canApprove():
                                nomination.approve()
                                self.verbose("    nomination approved.\n")

                except:
                    error("An exception was thrown after creating the bug, therefore, one should have been created. And you may have to fix it up by hand.")
                    if 'staging' in self.cfg:
                        print("https://bugs.qastaging.launchpad.net/bugs/%s" % (bug.id))
                    else:
                        print("https://bugs.launchpad.net/bugs/%s" % (bug.id))
                    print(" ")
                    raise
            except:
                error("An exception was thrown while creating the bug, therefore, none should have been created.")
                raise

        # Handle the user presses <ctrl-C>.
        #
        except KeyboardInterrupt:
            pass

        # Handle ommand line errors.
        #
        except CmdlineError as e:
            cmdline.error(e.msg, self.defaults)

        return

if __name__ == '__main__':
    app = CreateCveTracker()
    app.main()

# vi:set ts=4 sw=4 expandtab:

