Exemple #1
0
def do_bxa_notification(new, upload, session):
    cnf = Config()

    new = set([o['package'] for o in new if o['type'] == 'deb'])
    if len(new) == 0:
        return

    key = session.query(MetadataKey).filter_by(key='Description').one()
    summary = ""
    for binary in upload.binaries:
        if binary.package not in new:
            continue
        description = session.query(BinaryMetadata).filter_by(
            binary=binary, key=key).one().value
        summary += "\n"
        summary += "Package: {0}\n".format(binary.package)
        summary += "Description: {0}\n".format(description)

    subst = {
        '__DISTRO__': cnf['Dinstall::MyDistribution'],
        '__BCC__': 'X-DAK: dak process-new',
        '__BINARY_DESCRIPTIONS__': summary,
    }

    bxa_mail = utils.TemplateSubst(
        subst,
        os.path.join(cnf["Dir::Templates"], "process-new.bxa_notification"))
    utils.send_mail(bxa_mail)
Exemple #2
0
def announce_reject(upload, reason, rejected_by=None):
    """ Announce a reject.

    @type  upload: L{daklib.upload.Source} or L{daklib.upload.Binary}
    @param upload: upload to handle

    @type  reason: string
    @param reason: Reject reason

    @type  rejected_by: string
    @param rejected_by: Who is doing the reject.
    """
    cnf = Config()
    subst = _subst_for_upload(upload)
    whitelists = _whitelists(upload)

    automatic = rejected_by is None

    subst['__CC__'] = 'X-DAK-Rejection: {0}'.format('automatic' if automatic else 'manual')
    subst['__REJECT_MESSAGE__'] = reason

    if rejected_by:
        subst['__REJECTOR_ADDRESS__'] = rejected_by

    if not automatic:
        subst['__BCC__'] = '{0}\nBcc: {1}'.format(subst['__BCC__'], subst['__REJECTOR_ADDRESS__'])

    message = TemplateSubst(subst, os.path.join(cnf['Dir::Templates'], 'queue.rejected'))
    send_mail(message, whitelists=whitelists)
Exemple #3
0
def announce_new(upload):
    cnf = Config()
    subst = _subst_for_upload(upload)
    whitelists = _whitelists(upload)

    message = TemplateSubst(subst, os.path.join(cnf['Dir::Templates'], 'process-unchecked.new'))
    send_mail(message, whitelists=whitelists)
Exemple #4
0
def do_bxa_notification(new, upload, session):
    cnf = Config()

    new = set([ o['package'] for o in new if o['type'] == 'deb' ])
    if len(new) == 0:
        return

    key = session.query(MetadataKey).filter_by(key='Description').one()
    summary = ""
    for binary in upload.binaries:
        if binary.package not in new:
            continue
        description = session.query(BinaryMetadata).filter_by(binary=binary, key=key).one().value
        summary += "\n"
        summary += "Package: {0}\n".format(binary.package)
        summary += "Description: {0}\n".format(description)

    subst = {
        '__DISTRO__': cnf['Dinstall::MyDistribution'],
        '__BCC__': 'X-DAK: dak process-new',
        '__BINARY_DESCRIPTIONS__': summary,
        }

    bxa_mail = utils.TemplateSubst(subst,os.path.join(cnf["Dir::Templates"], "process-new.bxa_notification"))
    utils.send_mail(bxa_mail)
Exemple #5
0
def package_to_queue(u, summary, short_summary, queue, chg, session, announce=None):
    cnf = Config()
    dir = queue.path

    print "Moving to %s policy queue" % queue.queue_name.upper()
    u.logger.log(["Moving to %s" % queue.queue_name, u.pkg.changes_file])

    u.move_to_queue(queue)
    chg.in_queue_id = queue.policy_queue_id
    session.add(chg)

    # send to build queues
    if queue.send_to_build_queues:
        for suite_name in u.pkg.changes["distribution"].keys():
            suite = get_suite(suite_name, session)
            for q in suite.copy_queues:
                q.add_changes_from_policy_queue(queue, chg)

    session.commit()

    # Check for override disparities
    u.check_override()

    # Send accept mail, announce to lists and close bugs
    if announce:
        template = os.path.join(cnf["Dir::Templates"], announce)
        u.update_subst()
        mail_message = utils.TemplateSubst(u.Subst, template)
        utils.send_mail(mail_message)
        u.announce(short_summary, True)
Exemple #6
0
def do_bxa_notification(upload):
    files = upload.pkg.files
    summary = ""
    for f in files.keys():
        if files[f]["type"] == "deb":
            control = apt_pkg.TagSection(utils.deb_extract_control(utils.open_file(f)))
            summary += "\n"
            summary += "Package: %s\n" % (control.find("Package"))
            summary += "Description: %s\n" % (control.find("Description"))
    upload.Subst["__BINARY_DESCRIPTIONS__"] = summary
    bxa_mail = utils.TemplateSubst(upload.Subst,Config()["Dir::Templates"]+"/process-new.bxa_notification")
    utils.send_mail(bxa_mail)
Exemple #7
0
def send_email(commands, simulate=False):
    global Cnf

    Subst = {'__COMMANDS__': commands,
             "__DAK_ADDRESS__": Cnf["Dinstall::MyAdminAddress"]}

    bts_mail_message = utils.TemplateSubst(
        Subst, Cnf["Dir::Templates"] + "/bts-categorize")

    if simulate:
        print(bts_mail_message)
    else:
        utils.send_mail(bts_mail_message)
Exemple #8
0
def announce_new(upload):
    """ Announce an upload going to NEW.

    @type  upload: L{daklib.upload.Source} or L{daklib.upload.Binary}
    @param upload: upload to handle
    """

    cnf = Config()
    subst = _subst_for_upload(upload)
    whitelists = _whitelists(upload)

    message = TemplateSubst(subst, os.path.join(cnf['Dir::Templates'], 'process-unchecked.new'))
    send_mail(message, whitelists=whitelists)
Exemple #9
0
def send_email(commands, simulate=False):
    global Cnf

    Subst = {'__COMMANDS__': commands,
             "__DAK_ADDRESS__": Cnf["Dinstall::MyAdminAddress"]}

    bts_mail_message = utils.TemplateSubst(
        Subst, Cnf["Dir::Templates"] + "/bts-categorize")

    if simulate:
        print(bts_mail_message)
    else:
        utils.send_mail(bts_mail_message)
Exemple #10
0
def announce_reject(upload, reason, rejected_by=None):
    cnf = Config()
    subst = _subst_for_upload(upload)
    whitelists = _whitelists(upload)

    automatic = rejected_by is None

    subst['__CC__'] = 'X-DAK-Rejection: {0}'.format('automatic' if automatic else 'manual')
    subst['__REJECT_MESSAGE__'] = reason

    if rejected_by:
        subst['__REJECTOR_ADDRESS__'] = rejected_by

    if not automatic:
        subst['__BCC__'] = '{0}\nBcc: {1}'.format(subst['__BCC__'], subst['__REJECTOR_ADDRESS__'])

    message = TemplateSubst(subst, os.path.join(cnf['Dir::Templates'], 'queue.rejected'))
    send_mail(message, whitelists=whitelists)
Exemple #11
0
def acknowledge_new(u, summary, short_summary, chg, session):
    cnf = Config()

    print "Moving to NEW queue."
    u.logger.log(["Moving to new", u.pkg.changes_file])

    q = get_policy_queue('new', session)

    u.move_to_queue(q)
    chg.in_queue_id = q.policy_queue_id
    session.add(chg)
    session.commit()

    print "Sending new ack."
    template = os.path.join(cnf["Dir::Templates"], 'process-unchecked.new')
    u.update_subst()
    u.Subst["__SUMMARY__"] = summary
    new_ack_message = utils.TemplateSubst(u.Subst, template)
    utils.send_mail(new_ack_message)
Exemple #12
0
def acknowledge_new(u, summary, short_summary, chg, session):
    cnf = Config()

    print "Moving to NEW queue."
    u.logger.log(["Moving to new", u.pkg.changes_file])

    q = get_policy_queue('new', session)

    u.move_to_queue(q)
    chg.in_queue_id = q.policy_queue_id
    session.add(chg)
    session.commit()

    print "Sending new ack."
    template = os.path.join(cnf["Dir::Templates"], 'process-unchecked.new')
    u.update_subst()
    u.Subst["__SUMMARY__"] = summary
    new_ack_message = utils.TemplateSubst(u.Subst, template)
    utils.send_mail(new_ack_message)
Exemple #13
0
    def _notify_uploader(self):
        cnf = Config()

        bcc = 'X-DAK: dak process-command'
        if 'Dinstall::Bcc' in cnf:
            bcc = '{0}\nBcc: {1}'.format(bcc, cnf['Dinstall::Bcc'])

        cc = set(fix_maintainer(address)[1] for address in self.cc)

        subst = {
            '__DAK_ADDRESS__': cnf['Dinstall::MyEmailAddress'],
            '__MAINTAINER_TO__': fix_maintainer(self.uploader)[1],
            '__CC__': ", ".join(cc),
            '__BCC__': bcc,
            '__RESULTS__': "\n".join(self.result),
            '__FILENAME__': self.filename,
            }

        message = TemplateSubst(subst, os.path.join(cnf['Dir::Templates'], 'process-command.processed'))

        send_mail(message)
Exemple #14
0
    def _notify_uploader(self):
        cnf = Config()

        bcc = "X-DAK: dak process-command"
        if "Dinstall::Bcc" in cnf:
            bcc = "{0}\nBcc: {1}".format(bcc, cnf["Dinstall::Bcc"])

        cc = set(fix_maintainer(address)[1] for address in self.cc)

        subst = {
            "__DAK_ADDRESS__": cnf["Dinstall::MyEmailAddress"],
            "__MAINTAINER_TO__": fix_maintainer(self.uploader)[1],
            "__CC__": ", ".join(cc),
            "__BCC__": bcc,
            "__RESULTS__": "\n".join(self.result),
            "__FILENAME__": self.filename,
        }

        message = TemplateSubst(subst, os.path.join(cnf["Dir::Templates"], "process-command.processed"))

        send_mail(message)
Exemple #15
0
    def _notify_uploader(self):
        cnf = Config()

        bcc = 'X-DAK: dak process-command'
        if 'Dinstall::Bcc' in cnf:
            bcc = '{0}\nBcc: {1}'.format(bcc, cnf['Dinstall::Bcc'])

        cc = set(fix_maintainer(address)[1] for address in self.cc)

        subst = {
            '__DAK_ADDRESS__': cnf['Dinstall::MyEmailAddress'],
            '__MAINTAINER_TO__': fix_maintainer(self.uploader)[1],
            '__CC__': ", ".join(cc),
            '__BCC__': bcc,
            '__RESULTS__': "\n".join(self.result),
            '__FILENAME__': self.filename,
            }

        message = TemplateSubst(subst, os.path.join(cnf['Dir::Templates'], 'process-command.processed'))

        send_mail(message)
Exemple #16
0
def announce_accept(upload):
    """ Announce an upload.

    @type  upload: L{daklib.upload.Source} or L{daklib.upload.Binary}
    @param upload: upload to handle
    """

    cnf = Config()
    subst = _subst_for_upload(upload)
    whitelists = _whitelists(upload)

    accepted_to_real_suite = any(suite.policy_queue is None or suite in upload.from_policy_suites for suite in upload.suites)

    suite_names = []
    for suite in upload.suites:
        if suite.policy_queue:
            suite_names.append("{0}->{1}".format(suite.suite_name, suite.policy_queue.queue_name))
        else:
            suite_names.append(suite.suite_name)
    suite_names.extend(suite.suite_name for suite in upload.from_policy_suites)
    subst['__SUITE__'] = ', '.join(suite_names) or '(none)'

    message = TemplateSubst(subst, os.path.join(cnf['Dir::Templates'], 'process-unchecked.accepted'))
    send_mail(message, whitelists=whitelists)

    if accepted_to_real_suite and upload.sourceful:
        # send mail to announce lists and tracking server
        announce = set()
        for suite in upload.suites:
            if suite.policy_queue is None or suite in upload.from_policy_suites:
                announce.update(suite.announce or [])

        announce_list_address = ", ".join(announce)

        # according to #890944 this email shall be sent to dispatch@<TrackingServer> to avoid
        # bouncing emails
        # the package email alias is not yet created shortly after accepting the package
        tracker = cnf.get('Dinstall::TrackingServer')
        if tracker:
            announce_list_address = "{0}\nBcc: dispatch@{1}".format(announce_list_address, tracker)

        if len(announce_list_address) != 0:
            my_subst = subst.copy()
            my_subst['__ANNOUNCE_LIST_ADDRESS__'] = announce_list_address

            message = TemplateSubst(my_subst, os.path.join(cnf['Dir::Templates'], 'process-unchecked.announce'))
            send_mail(message, whitelists=whitelists)

    close_bugs_default = cnf.find_b('Dinstall::CloseBugs')
    close_bugs = any(s.close_bugs if s.close_bugs is not None else close_bugs_default for s in upload.suites)
    if accepted_to_real_suite and upload.sourceful and close_bugs:
        for bug in upload.bugs:
            my_subst = subst.copy()
            my_subst['__BUG_NUMBER__'] = str(bug)

            message = TemplateSubst(my_subst, os.path.join(cnf['Dir::Templates'], 'process-unchecked.bug-close'))
            send_mail(message, whitelists=whitelists)
Exemple #17
0
def package_to_queue(u,
                     summary,
                     short_summary,
                     queue,
                     chg,
                     session,
                     announce=None):
    cnf = Config()
    dir = queue.path

    print "Moving to %s policy queue" % queue.queue_name.upper()
    u.logger.log(["Moving to %s" % queue.queue_name, u.pkg.changes_file])

    u.move_to_queue(queue)
    chg.in_queue_id = queue.policy_queue_id
    session.add(chg)

    # send to build queues
    if queue.send_to_build_queues:
        for suite_name in u.pkg.changes["distribution"].keys():
            suite = get_suite(suite_name, session)
            for q in suite.copy_queues:
                q.add_changes_from_policy_queue(queue, chg)

    session.commit()

    # Check for override disparities
    u.check_override()

    # Send accept mail, announce to lists and close bugs
    if announce:
        template = os.path.join(cnf["Dir::Templates"], announce)
        u.update_subst()
        mail_message = utils.TemplateSubst(u.Subst, template)
        utils.send_mail(mail_message)
        u.announce(short_summary, True)
Exemple #18
0
def announce_accept(upload):
    cnf = Config()
    subst = _subst_for_upload(upload)
    whitelists = _whitelists(upload)

    accepted_to_real_suite = any(suite.policy_queue is None or suite in upload.from_policy_suites for suite in upload.suites)

    suite_names = []
    for suite in upload.suites:
        if suite.policy_queue:
            suite_names.append("{0}->{1}".format(suite.suite_name, suite.policy_queue.queue_name))
        else:
            suite_names.append(suite.suite_name)
    suite_names.extend(suite.suite_name for suite in upload.from_policy_suites)
    subst['__SUITE__'] = ', '.join(suite_names) or '(none)'

    message = TemplateSubst(subst, os.path.join(cnf['Dir::Templates'], 'process-unchecked.accepted'))
    send_mail(message, whitelists=whitelists)

    if accepted_to_real_suite and upload.sourceful:
        # senf mail to announce lists and tracking server
        announce = set()
        for suite in upload.suites:
            if suite.policy_queue is None or suite in upload.from_policy_suites:
                announce.update(suite.announce or [])

        announce_list_address = ", ".join(announce)

        tracking = cnf.get('Dinstall::TrackingServer')
        if tracking:
            announce_list_address = "{0}\nBcc: {1}@{2}".format(announce_list_address, upload.source, tracking)

        if len(announce_list_address) != 0:
            my_subst = subst.copy()
            my_subst['__ANNOUNCE_LIST_ADDRESS__'] = announce_list_address

            message = TemplateSubst(my_subst, os.path.join(cnf['Dir::Templates'], 'process-unchecked.announce'))
            send_mail(message, whitelists=whitelists)

    close_bugs_default = cnf.find_b('Dinstall::CloseBugs')
    close_bugs = any(s.close_bugs if s.close_bugs is not None else close_bugs_default for s in upload.suites)
    if accepted_to_real_suite and upload.sourceful and close_bugs:
        for bug in upload.bugs:
            my_subst = subst.copy()
            my_subst['__BUG_NUMBER__'] = str(bug)

            message = TemplateSubst(my_subst, os.path.join(cnf['Dir::Templates'], 'process-unchecked.bug-close'))
            send_mail(message, whitelists=whitelists)
Exemple #19
0
def check_transitions(transitions):
    """
    Check if the defined transitions still apply and remove those that no longer do.
    @note: Asks the user for confirmation first unless -a has been set.

    """
    global Cnf

    to_dump = 0
    to_remove = []
    info = {}

    session = DBConn().session()

    # Now look through all defined transitions
    for trans in transitions:
        t = transitions[trans]
        source = t["source"]
        expected = t["new"]

        # Will be an empty list if nothing is in testing.
        sourceobj = get_source_in_suite(source, "testing", session)

        info[trans] = get_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
        print info[trans]

        if sourceobj is None:
            # No package in testing
            print "Transition source %s not in testing, transition still ongoing." % (source)
        else:
            current = sourceobj.version
            compare = apt_pkg.version_compare(current, expected)
            if compare < 0:
                # This is still valid, the current version in database is older than
                # the new version we wait for
                print "This transition is still ongoing, we currently have version %s" % (current)
            else:
                print "REMOVE: This transition is over, the target package reached testing. REMOVE"
                print "%s wanted version: %s, has %s" % (source, expected, current)
                to_remove.append(trans)
                to_dump = 1
        print "-------------------------------------------------------------------------"

    if to_dump:
        prompt = "Removing: "
        for remove in to_remove:
            prompt += remove
            prompt += ","

        prompt += " Commit Changes? (y/N)"
        answer = ""

        if Options["no-action"]:
            answer="n"
        elif Options["automatic"]:
            answer="y"
        else:
            answer = utils.our_raw_input(prompt).lower()

        if answer == "":
            answer = "n"

        if answer == 'n':
            print "Not committing changes"
            sys.exit(0)
        elif answer == 'y':
            print "Committing"
            subst = {}
            subst['__SUBJECT__'] = "Transitions completed: " + ", ".join(sorted(to_remove))
            subst['__TRANSITION_MESSAGE__'] = "The following transitions were removed:\n"
            for remove in sorted(to_remove):
                subst['__TRANSITION_MESSAGE__'] += info[remove] + '\n'
                del transitions[remove]

            # If we have a mail address configured for transitions,
            # send a notification
            subst['__TRANSITION_EMAIL__'] = Cnf.get("Transitions::Notifications", "")
            if subst['__TRANSITION_EMAIL__'] != "":
                print "Sending notification to %s" % subst['__TRANSITION_EMAIL__']
                subst['__DAK_ADDRESS__'] = Cnf["Dinstall::MyEmailAddress"]
                subst['__BCC__'] = 'X-DAK: dak transitions'
                if Cnf.has_key("Dinstall::Bcc"):
                    subst["__BCC__"] += '\nBcc: %s' % Cnf["Dinstall::Bcc"]
                message = utils.TemplateSubst(subst,
                                              os.path.join(Cnf["Dir::Templates"], 'transition.removed'))
                utils.send_mail(message)

            edit_file = temp_transitions_file(transitions)
            write_transitions_from_file(edit_file)

            print "Done"
        else:
            print "WTF are you typing?"
            sys.exit(0)
Exemple #20
0
def main():
    global Cnf
    keyrings = None

    Cnf = utils.get_conf()

    Arguments = [
        ('h', "help", "Add-User::Options::Help"),
        ('k', "key", "Add-User::Options::Key", "HasArg"),
        ('u', "user", "Add-User::Options::User", "HasArg"),
    ]

    for i in ["help"]:
        key = "Add-User::Options::%s" % i
        if key not in Cnf:
            Cnf[key] = ""

    apt_pkg.parse_commandline(Cnf, Arguments, sys.argv)

    Options = Cnf.subtree("Add-User::Options")
    if Options["help"]:
        usage()

    session = DBConn().session()

    if not keyrings:
        keyrings = get_active_keyring_paths()

    cmd = [
        "gpg", "--with-colons", "--no-secmem-warning",
        "--no-auto-check-trustdb", "--with-fingerprint", "--no-default-keyring"
    ]
    cmd.extend(utils.gpg_keyring_args(keyrings).split())
    cmd.extend(["--list-key", "--", Cnf["Add-User::Options::Key"]])
    output = subprocess.check_output(cmd).rstrip()
    m = re_gpg_fingerprint_colon.search(output)
    if not m:
        print(output)
        utils.fubar(
            "0x%s: (1) No fingerprint found in gpg output but it returned 0?\n%s"
            % (Cnf["Add-User::Options::Key"],
               utils.prefix_multi_line_string(output, " [GPG output:] ")))
    primary_key = m.group(1)
    primary_key = primary_key.replace(" ", "")

    uid = ""
    if "Add-User::Options::User" in Cnf and Cnf["Add-User::Options::User"]:
        uid = Cnf["Add-User::Options::User"]
        name = Cnf["Add-User::Options::User"]
    else:
        u = re_user_address.search(output)
        if not u:
            print(output)
            utils.fubar(
                "0x%s: (2) No userid found in gpg output but it returned 0?\n%s"
                % (Cnf["Add-User::Options::Key"],
                   utils.prefix_multi_line_string(output, " [GPG output:] ")))
        uid = u.group(1)
        n = re_user_name.search(output)
        name = n.group(1)

# Look for all email addresses on the key.
    emails = []
    for line in output.split('\n'):
        e = re_user_mails.search(line)
        if not e:
            continue
        emails.append(e.group(2))

    print("0x%s -> %s <%s> -> %s -> %s" %
          (Cnf["Add-User::Options::Key"], name, emails[0], uid, primary_key))

    prompt = "Add user %s with above data (y/N) ? " % (uid)
    yn = utils.our_raw_input(prompt).lower()

    if yn == "y":
        # Create an account for the user?
        summary = ""

        # Now add user to the database.
        # Note that we provide a session, so we're responsible for committing
        uidobj = get_or_set_uid(uid, session=session)
        uid_id = uidobj.uid_id
        session.commit()

        # Lets add user to the email-whitelist file if its configured.
        if "Dinstall::MailWhiteList" in Cnf and Cnf[
                "Dinstall::MailWhiteList"] != "":
            f = utils.open_file(Cnf["Dinstall::MailWhiteList"], "a")
            for mail in emails:
                f.write(mail + '\n')
            f.close()

        print("Added:\nUid:\t %s (ID: %s)\nMaint:\t %s\nFP:\t %s" %
              (uid, uid_id, name, primary_key))

        # Should we send mail to the newly added user?
        if Cnf.find_b("Add-User::SendEmail"):
            mail = name + "<" + emails[0] + ">"
            Subst = {}
            Subst["__NEW_MAINTAINER__"] = mail
            Subst["__UID__"] = uid
            Subst["__KEYID__"] = Cnf["Add-User::Options::Key"]
            Subst["__PRIMARY_KEY__"] = primary_key
            Subst["__FROM_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"]
            Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"]
            Subst["__HOSTNAME__"] = Cnf["Dinstall::MyHost"]
            Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"]
            Subst["__SUMMARY__"] = summary
            new_add_message = utils.TemplateSubst(
                Subst, Cnf["Dir::Templates"] + "/add-user.added")
            utils.send_mail(new_add_message)

    else:
        uid = None
Exemple #21
0
def remove(session, reason, suites, removals,
           whoami=None, partial=False, components=None, done_bugs=None, date=None,
           carbon_copy=None, close_related_bugs=False):
    """Batch remove a number of packages
    Verify that the files listed in the Files field of the .dsc are
    those expected given the announced Format.

    @type session: SQLA Session
    @param session: The database session in use

    @type reason: string
    @param reason: The reason for the removal (e.g. "[auto-cruft] NBS (no longer built by <source>)")

    @type suites: list
    @param suites: A list of the suite names in which the removal should occur

    @type removals: list
    @param removals: A list of the removals.  Each element should be a tuple (or list) of at least the following
        for 4 items from the database (in order): package, version, architecture, (database) id.
        For source packages, the "architecture" should be set to "source".

    @type partial: bool
    @param partial: Whether the removal is "partial" (e.g. architecture specific).

    @type components: list
    @param components: List of components involved in a partial removal.  Can be an empty list to not restrict the
        removal to any components.

    @type whoami: string
    @param whoami: The person (or entity) doing the removal.  Defaults to utils.whoami()

    @type date: string
    @param date: The date of the removal. Defaults to commands.getoutput("date -R")

    @type done_bugs: list
    @param done_bugs: A list of bugs to be closed when doing this removal.

    @type close_related_bugs: bool
    @param done_bugs: Whether bugs related to the package being removed should be closed as well.  NB: Not implemented
      for more than one suite.

    @type carbon_copy: list
    @param carbon_copy: A list of mail addresses to CC when doing removals.  NB: all items are taken "as-is" unlike
        "dak rm".

    @rtype: None
    @return: Nothing
    """
    # Generate the summary of what's to be removed
    d = {}
    summary = ""
    sources = []
    binaries = []
    whitelists = []
    versions = []
    suite_ids_list = []
    suites_list = utils.join_with_commas_and(suites)
    cnf = utils.get_conf()
    con_components = ''

    #######################################################################################################

    if not reason:
        raise ValueError("Empty removal reason not permitted")

    if not removals:
        raise ValueError("Nothing to remove!?")

    if not suites:
        raise ValueError("Removals without a suite!?")

    if whoami is None:
        whoami = utils.whoami()

    if date is None:
        date = commands.getoutput("date -R")

    if partial and components:

        component_ids_list = []
        for componentname in components:
            component = get_component(componentname, session=session)
            if component is None:
                raise ValueError("component '%s' not recognised." % componentname)
            else:
                component_ids_list.append(component.component_id)
        if component_ids_list:
            con_components = "AND component IN (%s)" % ", ".join([str(i) for i in component_ids_list])

    for i in removals:
        package = i[0]
        version = i[1]
        architecture = i[2]
        if package not in d:
            d[package] = {}
        if version not in d[package]:
            d[package][version] = []
        if architecture not in d[package][version]:
            d[package][version].append(architecture)

    for package in sorted(d):
        versions = sorted(d[package], cmp=apt_pkg.version_compare)
        for version in versions:
            d[package][version].sort(utils.arch_compare_sw)
            summary += "%10s | %10s | %s\n" % (package, version, ", ".join(d[package][version]))

    for package in summary.split("\n"):
        for row in package.split("\n"):
            element = row.split("|")
            if len(element) == 3:
                if element[2].find("source") > 0:
                    sources.append("%s_%s" % tuple(elem.strip(" ") for elem in element[:2]))
                    element[2] = sub("source\s?,?", "", element[2]).strip(" ")
                if element[2]:
                    binaries.append("%s_%s [%s]" % tuple(elem.strip(" ") for elem in element))

    dsc_type_id = get_override_type('dsc', session).overridetype_id
    deb_type_id = get_override_type('deb', session).overridetype_id

    for suite in suites:
        s = get_suite(suite, session=session)
        if s is not None:
            suite_ids_list.append(s.suite_id)
            whitelists.append(s.mail_whitelist)

    #######################################################################################################
    log_filename = cnf["Rm::LogFile"]
    log822_filename = cnf["Rm::LogFile822"]
    with utils.open_file(log_filename, "a") as logfile, utils.open_file(log822_filename, "a") as logfile822:
        fcntl.lockf(logfile, fcntl.LOCK_EX)
        fcntl.lockf(logfile822, fcntl.LOCK_EX)

        logfile.write("=========================================================================\n")
        logfile.write("[Date: %s] [ftpmaster: %s]\n" % (date, whoami))
        logfile.write("Removed the following packages from %s:\n\n%s" % (suites_list, summary))
        if done_bugs:
            logfile.write("Closed bugs: %s\n" % (", ".join(done_bugs)))
        logfile.write("\n------------------- Reason -------------------\n%s\n" % reason)
        logfile.write("----------------------------------------------\n")

        logfile822.write("Date: %s\n" % date)
        logfile822.write("Ftpmaster: %s\n" % whoami)
        logfile822.write("Suite: %s\n" % suites_list)

        if sources:
            logfile822.write("Sources:\n")
            for source in sources:
                logfile822.write(" %s\n" % source)

        if binaries:
            logfile822.write("Binaries:\n")
            for binary in binaries:
                logfile822.write(" %s\n" % binary)

        logfile822.write("Reason: %s\n" % reason.replace('\n', '\n '))
        if done_bugs:
            logfile822.write("Bug: %s\n" % (", ".join(done_bugs)))

        for i in removals:
            package = i[0]
            architecture = i[2]
            package_id = i[3]
            for suite_id in suite_ids_list:
                if architecture == "source":
                    session.execute("DELETE FROM src_associations WHERE source = :packageid AND suite = :suiteid",
                                    {'packageid': package_id, 'suiteid': suite_id})
                else:
                    session.execute("DELETE FROM bin_associations WHERE bin = :packageid AND suite = :suiteid",
                                    {'packageid': package_id, 'suiteid': suite_id})
                # Delete from the override file
                if not partial:
                    if architecture == "source":
                        type_id = dsc_type_id
                    else:
                        type_id = deb_type_id
                    # TODO: Fix this properly to remove the remaining non-bind argument
                    session.execute("DELETE FROM override WHERE package = :package AND type = :typeid AND suite = :suiteid %s" % (con_components), {'package': package, 'typeid': type_id, 'suiteid': suite_id})

        session.commit()
        # ### REMOVAL COMPLETE - send mail time ### #

        # If we don't have a Bug server configured, we're done
        if "Dinstall::BugServer" not in cnf:
            if done_bugs or close_related_bugs:
                utils.warn("Cannot send mail to BugServer as Dinstall::BugServer is not configured")

            logfile.write("=========================================================================\n")
            logfile822.write("\n")
            return

        # read common subst variables for all bug closure mails
        Subst_common = {}
        Subst_common["__RM_ADDRESS__"] = cnf["Dinstall::MyEmailAddress"]
        Subst_common["__BUG_SERVER__"] = cnf["Dinstall::BugServer"]
        Subst_common["__CC__"] = "X-DAK: dak rm"
        if carbon_copy:
            Subst_common["__CC__"] += "\nCc: " + ", ".join(carbon_copy)
        Subst_common["__SUITE_LIST__"] = suites_list
        Subst_common["__SUBJECT__"] = "Removed package(s) from %s" % (suites_list)
        Subst_common["__ADMIN_ADDRESS__"] = cnf["Dinstall::MyAdminAddress"]
        Subst_common["__DISTRO__"] = cnf["Dinstall::MyDistribution"]
        Subst_common["__WHOAMI__"] = whoami

        # Send the bug closing messages
        if done_bugs:
            Subst_close_rm = Subst_common
            bcc = []
            if cnf.find("Dinstall::Bcc") != "":
                bcc.append(cnf["Dinstall::Bcc"])
            if cnf.find("Rm::Bcc") != "":
                bcc.append(cnf["Rm::Bcc"])
            if bcc:
                Subst_close_rm["__BCC__"] = "Bcc: " + ", ".join(bcc)
            else:
                Subst_close_rm["__BCC__"] = "X-Filler: 42"
            summarymail = "%s\n------------------- Reason -------------------\n%s\n" % (summary, reason)
            summarymail += "----------------------------------------------\n"
            Subst_close_rm["__SUMMARY__"] = summarymail

            for bug in done_bugs:
                Subst_close_rm["__BUG_NUMBER__"] = bug
                if close_related_bugs:
                    mail_message = utils.TemplateSubst(Subst_close_rm,cnf["Dir::Templates"]+"/rm.bug-close-with-related")
                else:
                    mail_message = utils.TemplateSubst(Subst_close_rm,cnf["Dir::Templates"]+"/rm.bug-close")
                utils.send_mail(mail_message, whitelists=whitelists)

        # close associated bug reports
        if close_related_bugs:
            Subst_close_other = Subst_common
            bcc = []
            wnpp = utils.parse_wnpp_bug_file()
            versions = list(set([re_bin_only_nmu.sub('', v) for v in versions]))
            if len(versions) == 1:
                Subst_close_other["__VERSION__"] = versions[0]
            else:
                logfile.write("=========================================================================\n")
                logfile822.write("\n")
                raise ValueError("Closing bugs with multiple package versions is not supported.  Do it yourself.")
            if bcc:
                Subst_close_other["__BCC__"] = "Bcc: " + ", ".join(bcc)
            else:
                Subst_close_other["__BCC__"] = "X-Filler: 42"
            # at this point, I just assume, that the first closed bug gives
            # some useful information on why the package got removed
            Subst_close_other["__BUG_NUMBER__"] = done_bugs[0]
            if len(sources) == 1:
                source_pkg = source.split("_", 1)[0]
            else:
                logfile.write("=========================================================================\n")
                logfile822.write("\n")
                raise ValueError("Closing bugs for multiple source packages is not supported.  Please do it yourself.")
            Subst_close_other["__BUG_NUMBER_ALSO__"] = ""
            Subst_close_other["__SOURCE__"] = source_pkg
            merged_bugs = set()
            other_bugs = bts.get_bugs('src', source_pkg, 'status', 'open', 'status', 'forwarded')
            if other_bugs:
                for bugno in other_bugs:
                    if bugno not in merged_bugs:
                        for bug in bts.get_status(bugno):
                            for merged in bug.mergedwith:
                                other_bugs.remove(merged)
                                merged_bugs.add(merged)
                logfile.write("Also closing bug(s):")
                logfile822.write("Also-Bugs:")
                for bug in other_bugs:
                    Subst_close_other["__BUG_NUMBER_ALSO__"] += str(bug) + "-done@" + cnf["Dinstall::BugServer"] + ","
                    logfile.write(" " + str(bug))
                    logfile822.write(" " + str(bug))
                logfile.write("\n")
                logfile822.write("\n")
            if source_pkg in wnpp:
                logfile.write("Also closing WNPP bug(s):")
                logfile822.write("Also-WNPP:")
                for bug in wnpp[source_pkg]:
                    # the wnpp-rm file we parse also contains our removal
                    # bugs, filtering that out
                    if bug != Subst_close_other["__BUG_NUMBER__"]:
                        Subst_close_other["__BUG_NUMBER_ALSO__"] += str(bug) + "-done@" + cnf["Dinstall::BugServer"] + ","
                        logfile.write(" " + str(bug))
                        logfile822.write(" " + str(bug))
                logfile.write("\n")
                logfile822.write("\n")

            mail_message = utils.TemplateSubst(Subst_close_other, cnf["Dir::Templates"]+"/rm.bug-close-related")
            if Subst_close_other["__BUG_NUMBER_ALSO__"]:
                utils.send_mail(mail_message)

        logfile.write("=========================================================================\n")
        logfile822.write("\n")
Exemple #22
0
def main():
    cnf = Config()

    Arguments = [
        ('h', "help", "Override::Options::Help"),
        ('c', "check", "Override::Options::Check"),
        ('d', "done", "Override::Options::Done", "HasArg"),
        ('n', "no-action", "Override::Options::No-Action"),
        ('s', "suite", "Override::Options::Suite", "HasArg"),
    ]
    for i in ["help", "check", "no-action"]:
        if not cnf.has_key("Override::Options::%s" % (i)):
            cnf["Override::Options::%s" % (i)] = ""
    if not cnf.has_key("Override::Options::Suite"):
        cnf["Override::Options::Suite"] = "unstable"

    arguments = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv)
    Options = cnf.subtree("Override::Options")

    if Options["Help"]:
        usage()

    session = DBConn().session()

    if not arguments:
        utils.fubar("package name is a required argument.")

    package = arguments.pop(0)
    suite_name = Options["Suite"]
    if arguments and len(arguments) > 2:
        utils.fubar("Too many arguments")

    suite = get_suite(suite_name, session)
    if suite is None:
        utils.fubar("Unknown suite '{0}'".format(suite_name))

    if arguments and len(arguments) == 1:
        # Determine if the argument is a priority or a section...
        arg = arguments.pop()
        q = session.execute(
            """
        SELECT ( SELECT COUNT(*) FROM section WHERE section = :arg ) AS secs,
               ( SELECT COUNT(*) FROM priority WHERE priority = :arg ) AS prios
               """, {'arg': arg})
        r = q.fetchall()
        if r[0][0] == 1:
            arguments = (arg, ".")
        elif r[0][1] == 1:
            arguments = (".", arg)
        else:
            utils.fubar("%s is not a valid section or priority" % (arg))

    # Retrieve current section/priority...
    oldsection, oldsourcesection, oldpriority = None, None, None
    for packagetype in ['source', 'binary']:
        eqdsc = '!='
        if packagetype == 'source':
            eqdsc = '='
        q = session.execute(
            """
    SELECT priority.priority AS prio, section.section AS sect, override_type.type AS type
      FROM override, priority, section, suite, override_type
     WHERE override.priority = priority.id
       AND override.type = override_type.id
       AND override_type.type %s 'dsc'
       AND override.section = section.id
       AND override.package = :package
       AND override.suite = suite.id
       AND suite.suite_name = :suite_name
        """ % (eqdsc), {
                'package': package,
                'suite_name': suite_name
            })

        if q.rowcount == 0:
            continue
        if q.rowcount > 1:
            utils.fubar("%s is ambiguous. Matches %d packages" %
                        (package, q.rowcount))

        r = q.fetchone()
        if packagetype == 'binary':
            oldsection = r[1]
            oldpriority = r[0]
        else:
            oldsourcesection = r[1]
            oldpriority = 'source'

    if not oldpriority and not oldsourcesection:
        utils.fubar("Unable to find package %s" % (package))

    if oldsection and oldsourcesection and oldsection != oldsourcesection:
        # When setting overrides, both source & binary will become the same section
        utils.warn("Source is in section '%s' instead of '%s'" %
                   (oldsourcesection, oldsection))

    if not oldsection:
        oldsection = oldsourcesection

    if not arguments:
        print "%s is in section '%s' at priority '%s'" % (package, oldsection,
                                                          oldpriority)
        sys.exit(0)

    # At this point, we have a new section and priority... check they're valid...
    newsection, newpriority = arguments

    if newsection == ".":
        newsection = oldsection
    if newpriority == ".":
        newpriority = oldpriority

    s = get_section(newsection, session)
    if s is None:
        utils.fubar("Supplied section %s is invalid" % (newsection))
    newsecid = s.section_id

    p = get_priority(newpriority, session)
    if p is None:
        utils.fubar("Supplied priority %s is invalid" % (newpriority))
    newprioid = p.priority_id

    if newpriority == oldpriority and newsection == oldsection:
        print "I: Doing nothing"
        sys.exit(0)

    if oldpriority == 'source' and newpriority != 'source':
        utils.fubar("Trying to change priority of a source-only package")

    if Options["Check"] and newpriority != oldpriority:
        check_override_compliance(package, p, suite.archive.path, suite_name,
                                  cnf, session)

    # If we're in no-action mode
    if Options["No-Action"]:
        if newpriority != oldpriority:
            print "I: Would change priority from %s to %s" % (oldpriority,
                                                              newpriority)
        if newsection != oldsection:
            print "I: Would change section from %s to %s" % (oldsection,
                                                             newsection)
        if Options.has_key("Done"):
            print "I: Would also close bug(s): %s" % (Options["Done"])

        sys.exit(0)

    if newpriority != oldpriority:
        print "I: Will change priority from %s to %s" % (oldpriority,
                                                         newpriority)

    if newsection != oldsection:
        print "I: Will change section from %s to %s" % (oldsection, newsection)

    if not Options.has_key("Done"):
        pass
        #utils.warn("No bugs to close have been specified. Noone will know you have done this.")
    else:
        print "I: Will close bug(s): %s" % (Options["Done"])

    game_over()

    Logger = daklog.Logger("override")

    dsc_otype_id = get_override_type('dsc').overridetype_id

    # We're already in a transaction
    # We're in "do it" mode, we have something to do... do it
    if newpriority != oldpriority:
        session.execute(
            """
        UPDATE override
           SET priority = :newprioid
         WHERE package = :package
           AND override.type != :otypedsc
           AND suite = (SELECT id FROM suite WHERE suite_name = :suite_name)""",
            {
                'newprioid': newprioid,
                'package': package,
                'otypedsc': dsc_otype_id,
                'suite_name': suite_name
            })

        Logger.log(["changed priority", package, oldpriority, newpriority])

    if newsection != oldsection:
        q = session.execute(
            """
        UPDATE override
           SET section = :newsecid
         WHERE package = :package
           AND suite = (SELECT id FROM suite WHERE suite_name = :suite_name)""",
            {
                'newsecid': newsecid,
                'package': package,
                'suite_name': suite_name
            })

        Logger.log(["changed section", package, oldsection, newsection])

    session.commit()

    if Options.has_key("Done"):
        if not cnf.has_key("Dinstall::BugServer"):
            utils.warn(
                "Asked to send Done message but Dinstall::BugServer is not configured"
            )
            Logger.close()
            return

        Subst = {}
        Subst["__OVERRIDE_ADDRESS__"] = cnf["Dinstall::MyEmailAddress"]
        Subst["__BUG_SERVER__"] = cnf["Dinstall::BugServer"]
        bcc = []
        if cnf.find("Dinstall::Bcc") != "":
            bcc.append(cnf["Dinstall::Bcc"])
        if bcc:
            Subst["__BCC__"] = "Bcc: " + ", ".join(bcc)
        else:
            Subst["__BCC__"] = "X-Filler: 42"
        if cnf.has_key("Dinstall::PackagesServer"):
            Subst["__CC__"] = "Cc: " + package + "@" + cnf[
                "Dinstall::PackagesServer"] + "\nX-DAK: dak override"
        else:
            Subst["__CC__"] = "X-DAK: dak override"
        Subst["__ADMIN_ADDRESS__"] = cnf["Dinstall::MyAdminAddress"]
        Subst["__DISTRO__"] = cnf["Dinstall::MyDistribution"]
        Subst["__WHOAMI__"] = utils.whoami()
        Subst["__SOURCE__"] = package

        summary = "Concerning package %s...\n" % (package)
        summary += "Operating on the %s suite\n" % (suite_name)
        if newpriority != oldpriority:
            summary += "Changed priority from %s to %s\n" % (oldpriority,
                                                             newpriority)
        if newsection != oldsection:
            summary += "Changed section from %s to %s\n" % (oldsection,
                                                            newsection)
        Subst["__SUMMARY__"] = summary

        template = os.path.join(cnf["Dir::Templates"], "override.bug-close")
        for bug in utils.split_args(Options["Done"]):
            Subst["__BUG_NUMBER__"] = bug
            mail_message = utils.TemplateSubst(Subst, template)
            utils.send_mail(mail_message)
            Logger.log(["closed bug", bug])

    Logger.close()
Exemple #23
0
def main ():
    global Options

    cnf = Config()

    Arguments = [('h',"help","Rm::Options::Help"),
                 ('a',"architecture","Rm::Options::Architecture", "HasArg"),
                 ('b',"binary", "Rm::Options::Binary"),
                 ('B',"binary-only", "Rm::Options::Binary-Only"),
                 ('c',"component", "Rm::Options::Component", "HasArg"),
                 ('C',"carbon-copy", "Rm::Options::Carbon-Copy", "HasArg"), # Bugs to Cc
                 ('d',"done","Rm::Options::Done", "HasArg"), # Bugs fixed
                 ('D',"do-close","Rm::Options::Do-Close"),
                 ('R',"rdep-check", "Rm::Options::Rdep-Check"),
                 ('m',"reason", "Rm::Options::Reason", "HasArg"), # Hysterical raisins; -m is old-dinstall option for rejection reason
                 ('n',"no-action","Rm::Options::No-Action"),
                 ('p',"partial", "Rm::Options::Partial"),
                 ('s',"suite","Rm::Options::Suite", "HasArg"),
                 ('S',"source-only", "Rm::Options::Source-Only"),
                 ]

    for i in [ "architecture", "binary", "binary-only", "carbon-copy", "component",
               "done", "help", "no-action", "partial", "rdep-check", "reason",
               "source-only", "Do-Close" ]:
        if not cnf.has_key("Rm::Options::%s" % (i)):
            cnf["Rm::Options::%s" % (i)] = ""
    if not cnf.has_key("Rm::Options::Suite"):
        cnf["Rm::Options::Suite"] = "unstable"

    arguments = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv)
    Options = cnf.subtree("Rm::Options")

    if Options["Help"]:
        usage()

    session = DBConn().session()

    # Sanity check options
    if not arguments:
        utils.fubar("need at least one package name as an argument.")
    if Options["Architecture"] and Options["Source-Only"]:
        utils.fubar("can't use -a/--architecture and -S/--source-only options simultaneously.")
    if ((Options["Binary"] and Options["Source-Only"])
            or (Options["Binary"] and Options["Binary-Only"])
            or (Options["Binary-Only"] and Options["Source-Only"])):
        utils.fubar("Only one of -b/--binary, -B/--binary-only and -S/--source-only can be used.")
    if Options.has_key("Carbon-Copy") and not Options.has_key("Done"):
        utils.fubar("can't use -C/--carbon-copy without also using -d/--done option.")
    if Options["Architecture"] and not Options["Partial"]:
        utils.warn("-a/--architecture implies -p/--partial.")
        Options["Partial"] = "true"
    if Options["Do-Close"] and not Options["Done"]:
        utils.fubar("No.")
    if (Options["Do-Close"]
           and (Options["Binary"] or Options["Binary-Only"] or Options["Source-Only"])):
        utils.fubar("No.")

    # Force the admin to tell someone if we're not doing a 'dak
    # cruft-report' inspired removal (or closing a bug, which counts
    # as telling someone).
    if not Options["No-Action"] and not Options["Carbon-Copy"] \
           and not Options["Done"] and Options["Reason"].find("[auto-cruft]") == -1:
        utils.fubar("Need a -C/--carbon-copy if not closing a bug and not doing a cruft removal.")

    # Process -C/--carbon-copy
    #
    # Accept 3 types of arguments (space separated):
    #  1) a number - assumed to be a bug number, i.e. [email protected]
    #  2) the keyword 'package' - cc's [email protected] for every argument
    #  3) contains a '@' - assumed to be an email address, used unmofidied
    #
    carbon_copy = []
    for copy_to in utils.split_args(Options.get("Carbon-Copy")):
        if copy_to.isdigit():
            if cnf.has_key("Dinstall::BugServer"):
                carbon_copy.append(copy_to + "@" + cnf["Dinstall::BugServer"])
            else:
                utils.fubar("Asked to send mail to #%s in BTS but Dinstall::BugServer is not configured" % copy_to)
        elif copy_to == 'package':
            for package in arguments:
                if cnf.has_key("Dinstall::PackagesServer"):
                    carbon_copy.append(package + "@" + cnf["Dinstall::PackagesServer"])
                if cnf.has_key("Dinstall::TrackingServer"):
                    carbon_copy.append(package + "@" + cnf["Dinstall::TrackingServer"])
        elif '@' in copy_to:
            carbon_copy.append(copy_to)
        else:
            utils.fubar("Invalid -C/--carbon-copy argument '%s'; not a bug number, 'package' or email address." % (copy_to))

    if Options["Binary"]:
        field = "b.package"
    else:
        field = "s.source"
    con_packages = "AND %s IN (%s)" % (field, ", ".join([ repr(i) for i in arguments ]))

    (con_suites, con_architectures, con_components, check_source) = \
                 utils.parse_args(Options)

    # Additional suite checks
    suite_ids_list = []
    whitelists = []
    suites = utils.split_args(Options["Suite"])
    suites_list = utils.join_with_commas_and(suites)
    if not Options["No-Action"]:
        for suite in suites:
            s = get_suite(suite, session=session)
            if s is not None:
                suite_ids_list.append(s.suite_id)
                whitelists.append(s.mail_whitelist)
            if suite in ("oldstable", "stable"):
                print "**WARNING** About to remove from the (old)stable suite!"
                print "This should only be done just prior to a (point) release and not at"
                print "any other time."
                game_over()
            elif suite == "testing":
                print "**WARNING About to remove from the testing suite!"
                print "There's no need to do this normally as removals from unstable will"
                print "propogate to testing automagically."
                game_over()

    # Additional architecture checks
    if Options["Architecture"] and check_source:
        utils.warn("'source' in -a/--argument makes no sense and is ignored.")

    # Additional component processing
    over_con_components = con_components.replace("c.id", "component")

    # Don't do dependency checks on multiple suites
    if Options["Rdep-Check"] and len(suites) > 1:
        utils.fubar("Reverse dependency check on multiple suites is not implemented.")

    to_remove = []
    maintainers = {}

    # We have 3 modes of package selection: binary, source-only, binary-only
    # and source+binary.

    # XXX: TODO: This all needs converting to use placeholders or the object
    #            API. It's an SQL injection dream at the moment

    if Options["Binary"]:
        # Removal by binary package name
        q = session.execute("SELECT b.package, b.version, a.arch_string, b.id, b.maintainer FROM binaries b, bin_associations ba, architecture a, suite su, files f, files_archive_map af, component c WHERE ba.bin = b.id AND ba.suite = su.id AND b.architecture = a.id AND b.file = f.id AND af.file_id = f.id AND af.archive_id = su.archive_id AND af.component_id = c.id %s %s %s %s" % (con_packages, con_suites, con_components, con_architectures))
        to_remove.extend(q)
    else:
        # Source-only
        if not Options["Binary-Only"]:
            q = session.execute("SELECT s.source, s.version, 'source', s.id, s.maintainer FROM source s, src_associations sa, suite su, archive, files f, files_archive_map af, component c WHERE sa.source = s.id AND sa.suite = su.id AND archive.id = su.archive_id AND s.file = f.id AND af.file_id = f.id AND af.archive_id = su.archive_id AND af.component_id = c.id %s %s %s" % (con_packages, con_suites, con_components))
            to_remove.extend(q)
        if not Options["Source-Only"]:
            # Source + Binary
            q = session.execute("""
                    SELECT b.package, b.version, a.arch_string, b.id, b.maintainer
                    FROM binaries b
                         JOIN bin_associations ba ON b.id = ba.bin
                         JOIN architecture a ON b.architecture = a.id
                         JOIN suite su ON ba.suite = su.id
                         JOIN archive ON archive.id = su.archive_id
                         JOIN files_archive_map af ON b.file = af.file_id AND af.archive_id = archive.id
                         JOIN component c ON af.component_id = c.id
                         JOIN source s ON b.source = s.id
                         JOIN src_associations sa ON s.id = sa.source AND sa.suite = su.id
                    WHERE TRUE %s %s %s %s""" % (con_packages, con_suites, con_components, con_architectures))
            to_remove.extend(q)

    if not to_remove:
        print "Nothing to do."
        sys.exit(0)

    # If we don't have a reason; spawn an editor so the user can add one
    # Write the rejection email out as the <foo>.reason file
    if not Options["Reason"] and not Options["No-Action"]:
        (fd, temp_filename) = utils.temp_filename()
        editor = os.environ.get("EDITOR","vi")
        result = os.system("%s %s" % (editor, temp_filename))
        if result != 0:
            utils.fubar ("vi invocation failed for `%s'!" % (temp_filename), result)
        temp_file = utils.open_file(temp_filename)
        for line in temp_file.readlines():
            Options["Reason"] += line
        temp_file.close()
        os.unlink(temp_filename)

    # Generate the summary of what's to be removed
    d = {}
    for i in to_remove:
        package = i[0]
        version = i[1]
        architecture = i[2]
        maintainer = i[4]
        maintainers[maintainer] = ""
        if not d.has_key(package):
            d[package] = {}
        if not d[package].has_key(version):
            d[package][version] = []
        if architecture not in d[package][version]:
            d[package][version].append(architecture)

    maintainer_list = []
    for maintainer_id in maintainers.keys():
        maintainer_list.append(get_maintainer(maintainer_id).name)
    summary = ""
    removals = d.keys()
    removals.sort()
    versions = []
    for package in removals:
        versions = d[package].keys()
        versions.sort(apt_pkg.version_compare)
        for version in versions:
            d[package][version].sort(utils.arch_compare_sw)
            summary += "%10s | %10s | %s\n" % (package, version, ", ".join(d[package][version]))
    print "Will remove the following packages from %s:" % (suites_list)
    print
    print summary
    print "Maintainer: %s" % ", ".join(maintainer_list)
    if Options["Done"]:
        print "Will also close bugs: "+Options["Done"]
    if carbon_copy:
        print "Will also send CCs to: " + ", ".join(carbon_copy)
    if Options["Do-Close"]:
        print "Will also close associated bug reports."
    print
    print "------------------- Reason -------------------"
    print Options["Reason"]
    print "----------------------------------------------"
    print

    if Options["Rdep-Check"]:
        arches = utils.split_args(Options["Architecture"])
        reverse_depends_check(removals, suites[0], arches, session)

    # If -n/--no-action, drop out here
    if Options["No-Action"]:
        sys.exit(0)

    print "Going to remove the packages now."
    game_over()

    whoami = utils.whoami()
    date = commands.getoutput('date -R')

    # Log first; if it all falls apart I want a record that we at least tried.
    logfile = utils.open_file(cnf["Rm::LogFile"], 'a')
    logfile.write("=========================================================================\n")
    logfile.write("[Date: %s] [ftpmaster: %s]\n" % (date, whoami))
    logfile.write("Removed the following packages from %s:\n\n%s" % (suites_list, summary))
    if Options["Done"]:
        logfile.write("Closed bugs: %s\n" % (Options["Done"]))
    logfile.write("\n------------------- Reason -------------------\n%s\n" % (Options["Reason"]))
    logfile.write("----------------------------------------------\n")

    # Do the same in rfc822 format
    logfile822 = utils.open_file(cnf["Rm::LogFile822"], 'a')
    logfile822.write("Date: %s\n" % date)
    logfile822.write("Ftpmaster: %s\n" % whoami)
    logfile822.write("Suite: %s\n" % suites_list)
    sources = []
    binaries = []
    for package in summary.split("\n"):
        for row in package.split("\n"):
            element = row.split("|")
            if len(element) == 3:
                if element[2].find("source") > 0:
                    sources.append("%s_%s" % tuple(elem.strip(" ") for elem in element[:2]))
                    element[2] = sub("source\s?,?", "", element[2]).strip(" ")
                if element[2]:
                    binaries.append("%s_%s [%s]" % tuple(elem.strip(" ") for elem in element))
    if sources:
        logfile822.write("Sources:\n")
        for source in sources:
            logfile822.write(" %s\n" % source)
    if binaries:
        logfile822.write("Binaries:\n")
        for binary in binaries:
            logfile822.write(" %s\n" % binary)
    logfile822.write("Reason: %s\n" % Options["Reason"].replace('\n', '\n '))
    if Options["Done"]:
        logfile822.write("Bug: %s\n" % Options["Done"])

    dsc_type_id = get_override_type('dsc', session).overridetype_id
    deb_type_id = get_override_type('deb', session).overridetype_id

    # Do the actual deletion
    print "Deleting...",
    sys.stdout.flush()

    for i in to_remove:
        package = i[0]
        architecture = i[2]
        package_id = i[3]
        for suite_id in suite_ids_list:
            if architecture == "source":
                session.execute("DELETE FROM src_associations WHERE source = :packageid AND suite = :suiteid",
                                {'packageid': package_id, 'suiteid': suite_id})
                #print "DELETE FROM src_associations WHERE source = %s AND suite = %s" % (package_id, suite_id)
            else:
                session.execute("DELETE FROM bin_associations WHERE bin = :packageid AND suite = :suiteid",
                                {'packageid': package_id, 'suiteid': suite_id})
                #print "DELETE FROM bin_associations WHERE bin = %s AND suite = %s" % (package_id, suite_id)
            # Delete from the override file
            if not Options["Partial"]:
                if architecture == "source":
                    type_id = dsc_type_id
                else:
                    type_id = deb_type_id
                # TODO: Again, fix this properly to remove the remaining non-bind argument
                session.execute("DELETE FROM override WHERE package = :package AND type = :typeid AND suite = :suiteid %s" % (over_con_components), {'package': package, 'typeid': type_id, 'suiteid': suite_id})
    session.commit()
    print "done."

    # If we don't have a Bug server configured, we're done
    if not cnf.has_key("Dinstall::BugServer"):
        if Options["Done"] or Options["Do-Close"]:
            print "Cannot send mail to BugServer as Dinstall::BugServer is not configured"

        logfile.write("=========================================================================\n")
        logfile.close()

        logfile822.write("\n")
        logfile822.close()

        return

    # read common subst variables for all bug closure mails
    Subst_common = {}
    Subst_common["__RM_ADDRESS__"] = cnf["Dinstall::MyEmailAddress"]
    Subst_common["__BUG_SERVER__"] = cnf["Dinstall::BugServer"]
    Subst_common["__CC__"] = "X-DAK: dak rm"
    if carbon_copy:
        Subst_common["__CC__"] += "\nCc: " + ", ".join(carbon_copy)
    Subst_common["__SUITE_LIST__"] = suites_list
    Subst_common["__SUBJECT__"] = "Removed package(s) from %s" % (suites_list)
    Subst_common["__ADMIN_ADDRESS__"] = cnf["Dinstall::MyAdminAddress"]
    Subst_common["__DISTRO__"] = cnf["Dinstall::MyDistribution"]
    Subst_common["__WHOAMI__"] = whoami

    # Send the bug closing messages
    if Options["Done"]:
        Subst_close_rm = Subst_common
        bcc = []
        if cnf.find("Dinstall::Bcc") != "":
            bcc.append(cnf["Dinstall::Bcc"])
        if cnf.find("Rm::Bcc") != "":
            bcc.append(cnf["Rm::Bcc"])
        if bcc:
            Subst_close_rm["__BCC__"] = "Bcc: " + ", ".join(bcc)
        else:
            Subst_close_rm["__BCC__"] = "X-Filler: 42"
        summarymail = "%s\n------------------- Reason -------------------\n%s\n" % (summary, Options["Reason"])
        summarymail += "----------------------------------------------\n"
        Subst_close_rm["__SUMMARY__"] = summarymail

        for bug in utils.split_args(Options["Done"]):
            Subst_close_rm["__BUG_NUMBER__"] = bug
            if Options["Do-Close"]:
                mail_message = utils.TemplateSubst(Subst_close_rm,cnf["Dir::Templates"]+"/rm.bug-close-with-related")
            else:
                mail_message = utils.TemplateSubst(Subst_close_rm,cnf["Dir::Templates"]+"/rm.bug-close")
            utils.send_mail(mail_message, whitelists=whitelists)

    # close associated bug reports
    if Options["Do-Close"]:
        Subst_close_other = Subst_common
        bcc = []
        wnpp = utils.parse_wnpp_bug_file()
        versions = list(set([re_bin_only_nmu.sub('', v) for v in versions]))
        if len(versions) == 1:
            Subst_close_other["__VERSION__"] = versions[0]
        else:
            utils.fubar("Closing bugs with multiple package versions is not supported.  Do it yourself.")
        if bcc:
            Subst_close_other["__BCC__"] = "Bcc: " + ", ".join(bcc)
        else:
            Subst_close_other["__BCC__"] = "X-Filler: 42"
        # at this point, I just assume, that the first closed bug gives
        # some useful information on why the package got removed
        Subst_close_other["__BUG_NUMBER__"] = utils.split_args(Options["Done"])[0]
        if len(sources) == 1:
            source_pkg = source.split("_", 1)[0]
        else:
            utils.fubar("Closing bugs for multiple source packages is not supported.  Do it yourself.")
        Subst_close_other["__BUG_NUMBER_ALSO__"] = ""
        Subst_close_other["__SOURCE__"] = source_pkg
        merged_bugs = set()
        other_bugs = bts.get_bugs('src', source_pkg, 'status', 'open', 'status', 'forwarded')
        if other_bugs:
            for bugno in other_bugs:
                if bugno not in merged_bugs:
                    for bug in bts.get_status(bugno):
                        for merged in bug.mergedwith:
                            other_bugs.remove(merged)
                            merged_bugs.add(merged)
            logfile.write("Also closing bug(s):")
            logfile822.write("Also-Bugs:")
            for bug in other_bugs:
                Subst_close_other["__BUG_NUMBER_ALSO__"] += str(bug) + "-done@" + cnf["Dinstall::BugServer"] + ","
                logfile.write(" " + str(bug))
                logfile822.write(" " + str(bug))
            logfile.write("\n")
            logfile822.write("\n")
        if source_pkg in wnpp.keys():
            logfile.write("Also closing WNPP bug(s):")
            logfile822.write("Also-WNPP:")
            for bug in wnpp[source_pkg]:
                # the wnpp-rm file we parse also contains our removal
                # bugs, filtering that out
                if bug != Subst_close_other["__BUG_NUMBER__"]:
                    Subst_close_other["__BUG_NUMBER_ALSO__"] += str(bug) + "-done@" + cnf["Dinstall::BugServer"] + ","
                    logfile.write(" " + str(bug))
                    logfile822.write(" " + str(bug))
            logfile.write("\n")
            logfile822.write("\n")

        mail_message = utils.TemplateSubst(Subst_close_other,cnf["Dir::Templates"]+"/rm.bug-close-related")
        if Subst_close_other["__BUG_NUMBER_ALSO__"]:
            utils.send_mail(mail_message)


    logfile.write("=========================================================================\n")
    logfile.close()

    logfile822.write("\n")
    logfile822.close()
Exemple #24
0
def main():
    cnf = Config()

    Arguments = [
        ("h", "help", "Override::Options::Help"),
        ("c", "check", "Override::Options::Check"),
        ("d", "done", "Override::Options::Done", "HasArg"),
        ("n", "no-action", "Override::Options::No-Action"),
        ("s", "suite", "Override::Options::Suite", "HasArg"),
    ]
    for i in ["help", "check", "no-action"]:
        if not cnf.has_key("Override::Options::%s" % (i)):
            cnf["Override::Options::%s" % (i)] = ""
    if not cnf.has_key("Override::Options::Suite"):
        cnf["Override::Options::Suite"] = "unstable"

    arguments = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv)
    Options = cnf.subtree("Override::Options")

    if Options["Help"]:
        usage()

    session = DBConn().session()

    if not arguments:
        utils.fubar("package name is a required argument.")

    package = arguments.pop(0)
    suite_name = Options["Suite"]
    if arguments and len(arguments) > 2:
        utils.fubar("Too many arguments")

    suite = get_suite(suite_name, session)
    if suite is None:
        utils.fubar("Unknown suite '{0}'".format(suite_name))

    if arguments and len(arguments) == 1:
        # Determine if the argument is a priority or a section...
        arg = arguments.pop()
        q = session.execute(
            """
        SELECT ( SELECT COUNT(*) FROM section WHERE section = :arg ) AS secs,
               ( SELECT COUNT(*) FROM priority WHERE priority = :arg ) AS prios
               """,
            {"arg": arg},
        )
        r = q.fetchall()
        if r[0][0] == 1:
            arguments = (arg, ".")
        elif r[0][1] == 1:
            arguments = (".", arg)
        else:
            utils.fubar("%s is not a valid section or priority" % (arg))

    # Retrieve current section/priority...
    oldsection, oldsourcesection, oldpriority = None, None, None
    for packagetype in ["source", "binary"]:
        eqdsc = "!="
        if packagetype == "source":
            eqdsc = "="
        q = session.execute(
            """
    SELECT priority.priority AS prio, section.section AS sect, override_type.type AS type
      FROM override, priority, section, suite, override_type
     WHERE override.priority = priority.id
       AND override.type = override_type.id
       AND override_type.type %s 'dsc'
       AND override.section = section.id
       AND override.package = :package
       AND override.suite = suite.id
       AND suite.suite_name = :suite_name
        """
            % (eqdsc),
            {"package": package, "suite_name": suite_name},
        )

        if q.rowcount == 0:
            continue
        if q.rowcount > 1:
            utils.fubar("%s is ambiguous. Matches %d packages" % (package, q.rowcount))

        r = q.fetchone()
        if packagetype == "binary":
            oldsection = r[1]
            oldpriority = r[0]
        else:
            oldsourcesection = r[1]
            oldpriority = "source"

    if not oldpriority and not oldsourcesection:
        utils.fubar("Unable to find package %s" % (package))

    if oldsection and oldsourcesection and oldsection != oldsourcesection:
        # When setting overrides, both source & binary will become the same section
        utils.warn("Source is in section '%s' instead of '%s'" % (oldsourcesection, oldsection))

    if not oldsection:
        oldsection = oldsourcesection

    if not arguments:
        print "%s is in section '%s' at priority '%s'" % (package, oldsection, oldpriority)
        sys.exit(0)

    # At this point, we have a new section and priority... check they're valid...
    newsection, newpriority = arguments

    if newsection == ".":
        newsection = oldsection
    if newpriority == ".":
        newpriority = oldpriority

    s = get_section(newsection, session)
    if s is None:
        utils.fubar("Supplied section %s is invalid" % (newsection))
    newsecid = s.section_id

    p = get_priority(newpriority, session)
    if p is None:
        utils.fubar("Supplied priority %s is invalid" % (newpriority))
    newprioid = p.priority_id

    if newpriority == oldpriority and newsection == oldsection:
        print "I: Doing nothing"
        sys.exit(0)

    if oldpriority == "source" and newpriority != "source":
        utils.fubar("Trying to change priority of a source-only package")

    if Options["Check"] and newpriority != oldpriority:
        check_override_compliance(package, p, suite.archive.path, suite_name, cnf, session)

    # If we're in no-action mode
    if Options["No-Action"]:
        if newpriority != oldpriority:
            print "I: Would change priority from %s to %s" % (oldpriority, newpriority)
        if newsection != oldsection:
            print "I: Would change section from %s to %s" % (oldsection, newsection)
        if Options.has_key("Done"):
            print "I: Would also close bug(s): %s" % (Options["Done"])

        sys.exit(0)

    if newpriority != oldpriority:
        print "I: Will change priority from %s to %s" % (oldpriority, newpriority)

    if newsection != oldsection:
        print "I: Will change section from %s to %s" % (oldsection, newsection)

    if not Options.has_key("Done"):
        pass
        # utils.warn("No bugs to close have been specified. Noone will know you have done this.")
    else:
        print "I: Will close bug(s): %s" % (Options["Done"])

    game_over()

    Logger = daklog.Logger("override")

    dsc_otype_id = get_override_type("dsc").overridetype_id

    # We're already in a transaction
    # We're in "do it" mode, we have something to do... do it
    if newpriority != oldpriority:
        session.execute(
            """
        UPDATE override
           SET priority = :newprioid
         WHERE package = :package
           AND override.type != :otypedsc
           AND suite = (SELECT id FROM suite WHERE suite_name = :suite_name)""",
            {"newprioid": newprioid, "package": package, "otypedsc": dsc_otype_id, "suite_name": suite_name},
        )

        Logger.log(["changed priority", package, oldpriority, newpriority])

    if newsection != oldsection:
        q = session.execute(
            """
        UPDATE override
           SET section = :newsecid
         WHERE package = :package
           AND suite = (SELECT id FROM suite WHERE suite_name = :suite_name)""",
            {"newsecid": newsecid, "package": package, "suite_name": suite_name},
        )

        Logger.log(["changed section", package, oldsection, newsection])

    session.commit()

    if Options.has_key("Done"):
        if not cnf.has_key("Dinstall::BugServer"):
            utils.warn("Asked to send Done message but Dinstall::BugServer is not configured")
            Logger.close()
            return

        Subst = {}
        Subst["__OVERRIDE_ADDRESS__"] = cnf["Dinstall::MyEmailAddress"]
        Subst["__BUG_SERVER__"] = cnf["Dinstall::BugServer"]
        bcc = []
        if cnf.find("Dinstall::Bcc") != "":
            bcc.append(cnf["Dinstall::Bcc"])
        if bcc:
            Subst["__BCC__"] = "Bcc: " + ", ".join(bcc)
        else:
            Subst["__BCC__"] = "X-Filler: 42"
        if cnf.has_key("Dinstall::PackagesServer"):
            Subst["__CC__"] = "Cc: " + package + "@" + cnf["Dinstall::PackagesServer"] + "\nX-DAK: dak override"
        else:
            Subst["__CC__"] = "X-DAK: dak override"
        Subst["__ADMIN_ADDRESS__"] = cnf["Dinstall::MyAdminAddress"]
        Subst["__DISTRO__"] = cnf["Dinstall::MyDistribution"]
        Subst["__WHOAMI__"] = utils.whoami()
        Subst["__SOURCE__"] = package

        summary = "Concerning package %s...\n" % (package)
        summary += "Operating on the %s suite\n" % (suite_name)
        if newpriority != oldpriority:
            summary += "Changed priority from %s to %s\n" % (oldpriority, newpriority)
        if newsection != oldsection:
            summary += "Changed section from %s to %s\n" % (oldsection, newsection)
        Subst["__SUMMARY__"] = summary

        template = os.path.join(cnf["Dir::Templates"], "override.bug-close")
        for bug in utils.split_args(Options["Done"]):
            Subst["__BUG_NUMBER__"] = bug
            mail_message = utils.TemplateSubst(Subst, template)
            utils.send_mail(mail_message)
            Logger.log(["closed bug", bug])

    Logger.close()
Exemple #25
0
def main():
    global Cnf
    keyrings = None

    Cnf = utils.get_conf()

    Arguments = [('h', "help", "Add-User::Options::Help"),
                 ('k', "key", "Add-User::Options::Key", "HasArg"),
                 ('u', "user", "Add-User::Options::User", "HasArg"),
                 ]

    for i in ["help"]:
        key = "Add-User::Options::%s" % i
        if key not in Cnf:
            Cnf[key] = ""

    apt_pkg.parse_commandline(Cnf, Arguments, sys.argv)

    Options = Cnf.subtree("Add-User::Options")
    if Options["help"]:
        usage()

    session = DBConn().session()

    if not keyrings:
        keyrings = get_active_keyring_paths()

    cmd = ["gpg", "--with-colons", "--no-secmem-warning",
           "--no-auto-check-trustdb", "--with-fingerprint",
           "--no-default-keyring"]
    cmd.extend(utils.gpg_keyring_args(keyrings).split())
    cmd.extend(["--list-key", "--", Cnf["Add-User::Options::Key"]])
    output = subprocess.check_output(cmd).rstrip()
    m = re_gpg_fingerprint_colon.search(output)
    if not m:
        print(output)
        utils.fubar("0x%s: (1) No fingerprint found in gpg output but it returned 0?\n%s"
                                        % (Cnf["Add-User::Options::Key"], utils.prefix_multi_line_string(output,
                                                                                                                                                                " [GPG output:] ")))
    primary_key = m.group(1)
    primary_key = primary_key.replace(" ", "")

    uid = ""
    if "Add-User::Options::User" in Cnf and Cnf["Add-User::Options::User"]:
        uid = Cnf["Add-User::Options::User"]
        name = Cnf["Add-User::Options::User"]
    else:
        u = re_user_address.search(output)
        if not u:
            print(output)
            utils.fubar("0x%s: (2) No userid found in gpg output but it returned 0?\n%s"
                        % (Cnf["Add-User::Options::Key"], utils.prefix_multi_line_string(output, " [GPG output:] ")))
        uid = u.group(1)
        n = re_user_name.search(output)
        name = n.group(1)

# Look for all email addresses on the key.
    emails = []
    for line in output.split('\n'):
        e = re_user_mails.search(line)
        if not e:
            continue
        emails.append(e.group(2))

    print("0x%s -> %s <%s> -> %s -> %s" % (Cnf["Add-User::Options::Key"], name, emails[0], uid, primary_key))

    prompt = "Add user %s with above data (y/N) ? " % (uid)
    yn = utils.our_raw_input(prompt).lower()

    if yn == "y":
        # Create an account for the user?
        summary = ""

        # Now add user to the database.
        # Note that we provide a session, so we're responsible for committing
        uidobj = get_or_set_uid(uid, session=session)
        uid_id = uidobj.uid_id
        session.commit()

        # Lets add user to the email-whitelist file if its configured.
        if "Dinstall::MailWhiteList" in Cnf and Cnf["Dinstall::MailWhiteList"] != "":
            f = utils.open_file(Cnf["Dinstall::MailWhiteList"], "a")
            for mail in emails:
                f.write(mail + '\n')
            f.close()

        print("Added:\nUid:\t %s (ID: %s)\nMaint:\t %s\nFP:\t %s" % (uid, uid_id,
                     name, primary_key))

        # Should we send mail to the newly added user?
        if Cnf.find_b("Add-User::SendEmail"):
            mail = name + "<" + emails[0] + ">"
            Subst = {}
            Subst["__NEW_MAINTAINER__"] = mail
            Subst["__UID__"] = uid
            Subst["__KEYID__"] = Cnf["Add-User::Options::Key"]
            Subst["__PRIMARY_KEY__"] = primary_key
            Subst["__FROM_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"]
            Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"]
            Subst["__HOSTNAME__"] = Cnf["Dinstall::MyHost"]
            Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"]
            Subst["__SUMMARY__"] = summary
            new_add_message = utils.TemplateSubst(Subst, Cnf["Dir::Templates"] + "/add-user.added")
            utils.send_mail(new_add_message)

    else:
        uid = None
Exemple #26
0
def remove(session,
           reason,
           suites,
           removals,
           whoami=None,
           partial=False,
           components=None,
           done_bugs=None,
           date=None,
           carbon_copy=None,
           close_related_bugs=False):
    """Batch remove a number of packages
    Verify that the files listed in the Files field of the .dsc are
    those expected given the announced Format.

    @type session: SQLA Session
    @param session: The database session in use

    @type reason: string
    @param reason: The reason for the removal (e.g. "[auto-cruft] NBS (no longer built by <source>)")

    @type suites: list
    @param suites: A list of the suite names in which the removal should occur

    @type removals: list
    @param removals: A list of the removals.  Each element should be a tuple (or list) of at least the following
        for 4 items from the database (in order): package, version, architecture, (database) id.
        For source packages, the "architecture" should be set to "source".

    @type partial: bool
    @param partial: Whether the removal is "partial" (e.g. architecture specific).

    @type components: list
    @param components: List of components involved in a partial removal.  Can be an empty list to not restrict the
        removal to any components.

    @type whoami: string
    @param whoami: The person (or entity) doing the removal.  Defaults to utils.whoami()

    @type date: string
    @param date: The date of the removal. Defaults to commands.getoutput("date -R")

    @type done_bugs: list
    @param done_bugs: A list of bugs to be closed when doing this removal.

    @type close_related_bugs: bool
    @param done_bugs: Whether bugs related to the package being removed should be closed as well.  NB: Not implemented
      for more than one suite.

    @type carbon_copy: list
    @param carbon_copy: A list of mail addresses to CC when doing removals.  NB: all items are taken "as-is" unlike
        "dak rm".

    @rtype: None
    @return: Nothing
    """
    # Generate the summary of what's to be removed
    d = {}
    summary = ""
    sources = []
    binaries = []
    whitelists = []
    versions = []
    suite_ids_list = []
    suites_list = utils.join_with_commas_and(suites)
    cnf = utils.get_conf()
    con_components = ''

    #######################################################################################################

    if not reason:
        raise ValueError("Empty removal reason not permitted")

    if not removals:
        raise ValueError("Nothing to remove!?")

    if not suites:
        raise ValueError("Removals without a suite!?")

    if whoami is None:
        whoami = utils.whoami()

    if date is None:
        date = commands.getoutput("date -R")

    if partial and components:

        component_ids_list = []
        for componentname in components:
            component = get_component(componentname, session=session)
            if component is None:
                raise ValueError("component '%s' not recognised." %
                                 componentname)
            else:
                component_ids_list.append(component.component_id)
        if component_ids_list:
            con_components = "AND component IN (%s)" % ", ".join(
                [str(i) for i in component_ids_list])

    for i in removals:
        package = i[0]
        version = i[1]
        architecture = i[2]
        if package not in d:
            d[package] = {}
        if version not in d[package]:
            d[package][version] = []
        if architecture not in d[package][version]:
            d[package][version].append(architecture)

    for package in sorted(d):
        versions = sorted(d[package], cmp=apt_pkg.version_compare)
        for version in versions:
            d[package][version].sort(utils.arch_compare_sw)
            summary += "%10s | %10s | %s\n" % (package, version, ", ".join(
                d[package][version]))

    for package in summary.split("\n"):
        for row in package.split("\n"):
            element = row.split("|")
            if len(element) == 3:
                if element[2].find("source") > 0:
                    sources.append(
                        "%s_%s" %
                        tuple(elem.strip(" ") for elem in element[:2]))
                    element[2] = sub("source\s?,?", "", element[2]).strip(" ")
                if element[2]:
                    binaries.append("%s_%s [%s]" %
                                    tuple(elem.strip(" ") for elem in element))

    dsc_type_id = get_override_type('dsc', session).overridetype_id
    deb_type_id = get_override_type('deb', session).overridetype_id

    for suite in suites:
        s = get_suite(suite, session=session)
        if s is not None:
            suite_ids_list.append(s.suite_id)
            whitelists.append(s.mail_whitelist)

    #######################################################################################################
    log_filename = cnf["Rm::LogFile"]
    log822_filename = cnf["Rm::LogFile822"]
    with utils.open_file(log_filename, "a") as logfile, utils.open_file(
            log822_filename, "a") as logfile822:
        fcntl.lockf(logfile, fcntl.LOCK_EX)
        fcntl.lockf(logfile822, fcntl.LOCK_EX)

        logfile.write(
            "=========================================================================\n"
        )
        logfile.write("[Date: %s] [ftpmaster: %s]\n" % (date, whoami))
        logfile.write("Removed the following packages from %s:\n\n%s" %
                      (suites_list, summary))
        if done_bugs:
            logfile.write("Closed bugs: %s\n" % (", ".join(done_bugs)))
        logfile.write(
            "\n------------------- Reason -------------------\n%s\n" % reason)
        logfile.write("----------------------------------------------\n")

        logfile822.write("Date: %s\n" % date)
        logfile822.write("Ftpmaster: %s\n" % whoami)
        logfile822.write("Suite: %s\n" % suites_list)

        if sources:
            logfile822.write("Sources:\n")
            for source in sources:
                logfile822.write(" %s\n" % source)

        if binaries:
            logfile822.write("Binaries:\n")
            for binary in binaries:
                logfile822.write(" %s\n" % binary)

        logfile822.write("Reason: %s\n" % reason.replace('\n', '\n '))
        if done_bugs:
            logfile822.write("Bug: %s\n" % (", ".join(done_bugs)))

        for i in removals:
            package = i[0]
            architecture = i[2]
            package_id = i[3]
            for suite_id in suite_ids_list:
                if architecture == "source":
                    session.execute(
                        "DELETE FROM src_associations WHERE source = :packageid AND suite = :suiteid",
                        {
                            'packageid': package_id,
                            'suiteid': suite_id
                        })
                else:
                    session.execute(
                        "DELETE FROM bin_associations WHERE bin = :packageid AND suite = :suiteid",
                        {
                            'packageid': package_id,
                            'suiteid': suite_id
                        })
                # Delete from the override file
                if not partial:
                    if architecture == "source":
                        type_id = dsc_type_id
                    else:
                        type_id = deb_type_id
                    # TODO: Fix this properly to remove the remaining non-bind argument
                    session.execute(
                        "DELETE FROM override WHERE package = :package AND type = :typeid AND suite = :suiteid %s"
                        % (con_components), {
                            'package': package,
                            'typeid': type_id,
                            'suiteid': suite_id
                        })

        session.commit()
        # ### REMOVAL COMPLETE - send mail time ### #

        # If we don't have a Bug server configured, we're done
        if "Dinstall::BugServer" not in cnf:
            if done_bugs or close_related_bugs:
                utils.warn(
                    "Cannot send mail to BugServer as Dinstall::BugServer is not configured"
                )

            logfile.write(
                "=========================================================================\n"
            )
            logfile822.write("\n")
            return

        # read common subst variables for all bug closure mails
        Subst_common = {}
        Subst_common["__RM_ADDRESS__"] = cnf["Dinstall::MyEmailAddress"]
        Subst_common["__BUG_SERVER__"] = cnf["Dinstall::BugServer"]
        Subst_common["__CC__"] = "X-DAK: dak rm"
        if carbon_copy:
            Subst_common["__CC__"] += "\nCc: " + ", ".join(carbon_copy)
        Subst_common["__SUITE_LIST__"] = suites_list
        Subst_common["__SUBJECT__"] = "Removed package(s) from %s" % (
            suites_list)
        Subst_common["__ADMIN_ADDRESS__"] = cnf["Dinstall::MyAdminAddress"]
        Subst_common["__DISTRO__"] = cnf["Dinstall::MyDistribution"]
        Subst_common["__WHOAMI__"] = whoami

        # Send the bug closing messages
        if done_bugs:
            Subst_close_rm = Subst_common
            bcc = []
            if cnf.find("Dinstall::Bcc") != "":
                bcc.append(cnf["Dinstall::Bcc"])
            if cnf.find("Rm::Bcc") != "":
                bcc.append(cnf["Rm::Bcc"])
            if bcc:
                Subst_close_rm["__BCC__"] = "Bcc: " + ", ".join(bcc)
            else:
                Subst_close_rm["__BCC__"] = "X-Filler: 42"
            summarymail = "%s\n------------------- Reason -------------------\n%s\n" % (
                summary, reason)
            summarymail += "----------------------------------------------\n"
            Subst_close_rm["__SUMMARY__"] = summarymail

            for bug in done_bugs:
                Subst_close_rm["__BUG_NUMBER__"] = bug
                if close_related_bugs:
                    mail_message = utils.TemplateSubst(
                        Subst_close_rm,
                        cnf["Dir::Templates"] + "/rm.bug-close-with-related")
                else:
                    mail_message = utils.TemplateSubst(
                        Subst_close_rm,
                        cnf["Dir::Templates"] + "/rm.bug-close")
                utils.send_mail(mail_message, whitelists=whitelists)

        # close associated bug reports
        if close_related_bugs:
            Subst_close_other = Subst_common
            bcc = []
            wnpp = utils.parse_wnpp_bug_file()
            versions = list(set([re_bin_only_nmu.sub('', v)
                                 for v in versions]))
            if len(versions) == 1:
                Subst_close_other["__VERSION__"] = versions[0]
            else:
                logfile.write(
                    "=========================================================================\n"
                )
                logfile822.write("\n")
                raise ValueError(
                    "Closing bugs with multiple package versions is not supported.  Do it yourself."
                )
            if bcc:
                Subst_close_other["__BCC__"] = "Bcc: " + ", ".join(bcc)
            else:
                Subst_close_other["__BCC__"] = "X-Filler: 42"
            # at this point, I just assume, that the first closed bug gives
            # some useful information on why the package got removed
            Subst_close_other["__BUG_NUMBER__"] = done_bugs[0]
            if len(sources) == 1:
                source_pkg = source.split("_", 1)[0]
            else:
                logfile.write(
                    "=========================================================================\n"
                )
                logfile822.write("\n")
                raise ValueError(
                    "Closing bugs for multiple source packages is not supported.  Please do it yourself."
                )
            Subst_close_other["__BUG_NUMBER_ALSO__"] = ""
            Subst_close_other["__SOURCE__"] = source_pkg
            merged_bugs = set()
            other_bugs = bts.get_bugs('src', source_pkg, 'status', 'open',
                                      'status', 'forwarded')
            if other_bugs:
                for bugno in other_bugs:
                    if bugno not in merged_bugs:
                        for bug in bts.get_status(bugno):
                            for merged in bug.mergedwith:
                                other_bugs.remove(merged)
                                merged_bugs.add(merged)
                logfile.write("Also closing bug(s):")
                logfile822.write("Also-Bugs:")
                for bug in other_bugs:
                    Subst_close_other["__BUG_NUMBER_ALSO__"] += str(
                        bug) + "-done@" + cnf["Dinstall::BugServer"] + ","
                    logfile.write(" " + str(bug))
                    logfile822.write(" " + str(bug))
                logfile.write("\n")
                logfile822.write("\n")
            if source_pkg in wnpp:
                logfile.write("Also closing WNPP bug(s):")
                logfile822.write("Also-WNPP:")
                for bug in wnpp[source_pkg]:
                    # the wnpp-rm file we parse also contains our removal
                    # bugs, filtering that out
                    if bug != Subst_close_other["__BUG_NUMBER__"]:
                        Subst_close_other["__BUG_NUMBER_ALSO__"] += str(
                            bug) + "-done@" + cnf["Dinstall::BugServer"] + ","
                        logfile.write(" " + str(bug))
                        logfile822.write(" " + str(bug))
                logfile.write("\n")
                logfile822.write("\n")

            mail_message = utils.TemplateSubst(
                Subst_close_other,
                cnf["Dir::Templates"] + "/rm.bug-close-related")
            if Subst_close_other["__BUG_NUMBER_ALSO__"]:
                utils.send_mail(mail_message)

        logfile.write(
            "=========================================================================\n"
        )
        logfile822.write("\n")