X-Git-Url: https://git.decadent.org.uk/gitweb/?a=blobdiff_plain;f=dak%2Fcontrol_suite.py;h=5392259c6002ac20e15597c8123ef4eeb8ce63c3;hb=98d086ddda3772fb58c1bfa97478e8044596b569;hp=2454b23478c54c81c8556acf16d1a825aba2e065;hpb=59fd5aa2a8be3b76dbc968429c457f096adfa472;p=dak.git diff --git a/dak/control_suite.py b/dak/control_suite.py index 2454b234..5392259c 100755 --- a/dak/control_suite.py +++ b/dak/control_suite.py @@ -1,8 +1,7 @@ #!/usr/bin/env python -# Manipulate suite tags -# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005 James Troup -# $Id: heidi,v 1.19 2005-11-15 09:50:32 ajt Exp $ +""" Manipulate suite tags """ +# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006 James Troup # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -42,251 +41,423 @@ ####################################################################################### -import pg, sys; -import apt_pkg; -import utils, db_access, logging; +import sys +import apt_pkg +import os + +from daklib.archive import ArchiveTransaction +from daklib.config import Config +from daklib.dbconn import * +from daklib import daklog +from daklib import utils +from daklib.queue import get_suite_version_by_package, get_suite_version_by_source ####################################################################################### -Cnf = None; -projectB = None; -Logger = None; +Logger = None ################################################################################ def usage (exit_code=0): - print """Usage: heidi [OPTIONS] [FILE] + print """Usage: dak control-suite [OPTIONS] [FILE] Display or alter the contents of a suite using FILE(s), or stdin. -a, --add=SUITE add to SUITE -h, --help show this help and exit -l, --list=SUITE list the contents of SUITE -r, --remove=SUITE remove from SUITE - -s, --set=SUITE set SUITE""" + -s, --set=SUITE set SUITE + -b, --britney generate changelog entry for britney runs""" sys.exit(exit_code) ####################################################################################### -def get_id (package, version, architecture): +def get_pkg(package, version, architecture, session): + if architecture == 'source': + q = session.query(DBSource).filter_by(source=package, version=version) \ + .join(DBSource.poolfile) + else: + q = session.query(DBBinary).filter_by(package=package, version=version) \ + .join(DBBinary.architecture).filter(Architecture.arch_string.in_([architecture, 'all'])) \ + .join(DBBinary.poolfile) + + pkg = q.first() + if pkg is None: + utils.warn("Could not find {0}_{1}_{2}.".format(package, version, architecture)) + return pkg + +####################################################################################### + +def britney_changelog(packages, suite, session): + + old = {} + current = {} + Cnf = utils.get_conf() + + try: + q = session.execute("SELECT changelog FROM suite WHERE id = :suiteid", \ + {'suiteid': suite.suite_id}) + brit_file = q.fetchone()[0] + except: + brit_file = None + + if brit_file: + brit_file = os.path.join(Cnf['Dir::Root'], brit_file) + else: + return + + q = session.execute("""SELECT s.source, s.version, sa.id + FROM source s, src_associations sa + WHERE sa.suite = :suiteid + AND sa.source = s.id""", {'suiteid': suite.suite_id}) + + for p in q.fetchall(): + current[p[0]] = p[1] + for p in packages.keys(): + if p[2] == "source": + old[p[0]] = p[1] + + new = {} + for p in current.keys(): + if p in old.keys(): + if apt_pkg.version_compare(current[p], old[p]) > 0: + new[p] = [current[p], old[p]] + else: + new[p] = [current[p], 0] + + query = "SELECT source, changelog FROM changelogs WHERE" + for p in new.keys(): + query += " source = '%s' AND version > '%s' AND version <= '%s'" \ + % (p, new[p][1], new[p][0]) + query += " AND architecture LIKE '%source%' AND distribution in \ + ('unstable', 'experimental', 'testing-proposed-updates') OR" + query += " False ORDER BY source, version DESC" + q = session.execute(query) + + pu = None + brit = utils.open_file(brit_file, 'w') + + for u in q: + if pu and pu != u[0]: + brit.write("\n") + brit.write("%s\n" % u[1]) + pu = u[0] + if q.rowcount: brit.write("\n\n\n") + + for p in list(set(old.keys()).difference(current.keys())): + brit.write("REMOVED: %s %s\n" % (p, old[p])) + + brit.flush() + brit.close() + +####################################################################################### + +def version_checks(package, architecture, target_suite, new_version, session, force = False): if architecture == "source": - q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version)) + suite_version_list = get_suite_version_by_source(package, session) else: - q = projectB.query("SELECT b.id FROM binaries b, architecture a WHERE b.package = '%s' AND b.version = '%s' AND (a.arch_string = '%s' OR a.arch_string = 'all') AND b.architecture = a.id" % (package, version, architecture)) - - ql = q.getresult(); - if not ql: - utils.warn("Couldn't find '%s~%s~%s'." % (package, version, architecture)); - return None; - if len(ql) > 1: - utils.warn("Found more than one match for '%s~%s~%s'." % (package, version, architecture)); - return None; - id = ql[0][0]; - return id; + suite_version_list = get_suite_version_by_package(package, architecture, session) + + must_be_newer_than = [ vc.reference.suite_name for vc in get_version_checks(target_suite, "MustBeNewerThan") ] + must_be_older_than = [ vc.reference.suite_name for vc in get_version_checks(target_suite, "MustBeOlderThan") ] + + # Must be newer than an existing version in target_suite + if target_suite not in must_be_newer_than: + must_be_newer_than.append(target_suite) + + violations = False + + for suite, version in suite_version_list: + cmp = apt_pkg.version_compare(new_version, version) + if suite in must_be_newer_than and cmp < 1: + utils.warn("%s (%s): version check violated: %s targeted at %s is *not* newer than %s in %s" % (package, architecture, new_version, target_suite, version, suite)) + violations = True + if suite in must_be_older_than and cmp > 1: + utils.warn("%s (%s): version check violated: %s targeted at %s is *not* older than %s in %s" % (package, architecture, new_version, target_suite, version, suite)) + violations = True + + if violations: + if force: + utils.warn("Continuing anyway (forced)...") + else: + utils.fubar("Aborting. Version checks violated and not forced.") + +####################################################################################### + +def cmp_package_version(a, b): + """ + comparison function for tuples of the form (package-name, version, arch, ...) + """ + res = 0 + if a[2] == 'source' and b[2] != 'source': + res = -1 + elif a[2] != 'source' and b[2] == 'source': + res = 1 + if res == 0: + res = cmp(a[0], b[0]) + if res == 0: + res = apt_pkg.version_compare(a[1], b[1]) + return res ####################################################################################### -def set_suite (file, suite_id): - lines = file.readlines(); +def set_suite(file, suite, transaction, britney=False, force=False): + session = transaction.session + suite_id = suite.suite_id + lines = file.readlines() - projectB.query("BEGIN WORK"); + # Our session is already in a transaction # Build up a dictionary of what is currently in the suite - current = {}; - q = projectB.query("SELECT b.package, b.version, a.arch_string, ba.id FROM binaries b, bin_associations ba, architecture a WHERE ba.suite = %s AND ba.bin = b.id AND b.architecture = a.id" % (suite_id)); - ql = q.getresult(); - for i in ql: - key = " ".join(i[:3]); - current[key] = i[3]; - q = projectB.query("SELECT s.source, s.version, sa.id FROM source s, src_associations sa WHERE sa.suite = %s AND sa.source = s.id" % (suite_id)); - ql = q.getresult(); - for i in ql: - key = " ".join(i[:2]) + " source"; - current[key] = i[2]; + current = {} + q = session.execute("""SELECT b.package, b.version, a.arch_string, ba.id + FROM binaries b, bin_associations ba, architecture a + WHERE ba.suite = :suiteid + AND ba.bin = b.id AND b.architecture = a.id""", {'suiteid': suite_id}) + for i in q: + key = i[:3] + current[key] = i[3] + + q = session.execute("""SELECT s.source, s.version, 'source', sa.id + FROM source s, src_associations sa + WHERE sa.suite = :suiteid + AND sa.source = s.id""", {'suiteid': suite_id}) + for i in q: + key = i[:3] + current[key] = i[3] # Build up a dictionary of what should be in the suite - desired = {}; + desired = set() for line in lines: - split_line = line.strip().split(); + split_line = line.strip().split() if len(split_line) != 3: - utils.warn("'%s' does not break into 'package version architecture'." % (line[:-1])); - continue; - key = " ".join(split_line); - desired[key] = ""; + utils.warn("'%s' does not break into 'package version architecture'." % (line[:-1])) + continue + desired.add(tuple(split_line)) - # Check to see which packages need removed and remove them - for key in current.keys(): - if not desired.has_key(key): - (package, version, architecture) = key.split(); - id = current[key]; + # Check to see which packages need added and add them + for key in sorted(desired, cmp=cmp_package_version): + if key not in current: + (package, version, architecture) = key + version_checks(package, architecture, suite.suite_name, version, session, force) + pkg = get_pkg(package, version, architecture, session) + if pkg is None: + continue + + component = pkg.poolfile.component if architecture == "source": - q = projectB.query("DELETE FROM src_associations WHERE id = %s" % (id)); + transaction.copy_source(pkg, suite, component) else: - q = projectB.query("DELETE FROM bin_associations WHERE id = %s" % (id)); - Logger.log(["removed",key,id]); + transaction.copy_binary(pkg, suite, component) - # Check to see which packages need added and add them - for key in desired.keys(): - if not current.has_key(key): - (package, version, architecture) = key.split(); - id = get_id (package, version, architecture); - if not id: - continue; + Logger.log(["added", " ".join(key)]) + + # Check to see which packages need removed and remove them + for key, pkid in current.iteritems(): + if key not in desired: + (package, version, architecture) = key if architecture == "source": - q = projectB.query("INSERT INTO src_associations (suite, source) VALUES (%s, %s)" % (suite_id, id)); + session.execute("""DELETE FROM src_associations WHERE id = :pkid""", {'pkid': pkid}) else: - q = projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%s, %s)" % (suite_id, id)); - Logger.log(["added",key,id]); + session.execute("""DELETE FROM bin_associations WHERE id = :pkid""", {'pkid': pkid}) + Logger.log(["removed", " ".join(key), pkid]) - projectB.query("COMMIT WORK"); + session.commit() -####################################################################################### + if britney: + britney_changelog(current, suite, session) -def process_file (file, suite, action): +####################################################################################### - suite_id = db_access.get_suite_id(suite); +def process_file(file, suite, action, transaction, britney=False, force=False): + session = transaction.session if action == "set": - set_suite (file, suite_id); - return; + set_suite(file, suite, transaction, britney, force) + return - lines = file.readlines(); + suite_id = suite.suite_id - projectB.query("BEGIN WORK"); + request = [] - for line in lines: - split_line = line.strip().split(); + # Our session is already in a transaction + for line in file: + split_line = line.strip().split() if len(split_line) != 3: - utils.warn("'%s' does not break into 'package version architecture'." % (line[:-1])); - continue; + utils.warn("'%s' does not break into 'package version architecture'." % (line[:-1])) + continue + request.append(split_line) + + request.sort(cmp=cmp_package_version) + + for package, version, architecture in request: + pkg = get_pkg(package, version, architecture, session) + if pkg is None: + continue + if architecture == 'source': + pkid = pkg.source_id + else: + pkid = pkg.binary_id - (package, version, architecture) = split_line; + component = pkg.poolfile.component - id = get_id(package, version, architecture); - if not id: - continue; + # Do version checks when adding packages + if action == "add": + version_checks(package, architecture, suite.suite_name, version, session, force) if architecture == "source": - # Find the existing assoications ID, if any - q = projectB.query("SELECT id FROM src_associations WHERE suite = %s and source = %s" % (suite_id, id)); - ql = q.getresult(); - if not ql: - assoication_id = None; + # Find the existing association ID, if any + q = session.execute("""SELECT id FROM src_associations + WHERE suite = :suiteid and source = :pkid""", + {'suiteid': suite_id, 'pkid': pkid}) + ql = q.fetchall() + if len(ql) < 1: + association_id = None else: - assoication_id = ql[0][0]; + association_id = ql[0][0] + # Take action if action == "add": - if assoication_id: - utils.warn("'%s~%s~%s' already exists in suite %s." % (package, version, architecture, suite)); - continue; + if association_id: + utils.warn("'%s_%s_%s' already exists in suite %s." % (package, version, architecture, suite)) + continue else: - q = projectB.query("INSERT INTO src_associations (suite, source) VALUES (%s, %s)" % (suite_id, id)); + transaction.copy_source(pkg, suite, component) + Logger.log(["added", package, version, architecture, suite.suite_name, pkid]) + elif action == "remove": - if assoication_id == None: - utils.warn("'%s~%s~%s' doesn't exist in suite %s." % (package, version, architecture, suite)); - continue; + if association_id == None: + utils.warn("'%s_%s_%s' doesn't exist in suite %s." % (package, version, architecture, suite)) + continue else: - q = projectB.query("DELETE FROM src_associations WHERE id = %s" % (assoication_id)); + session.execute("""DELETE FROM src_associations WHERE id = :pkid""", {'pkid': association_id}) + Logger.log(["removed", package, version, architecture, suite.suite_name, pkid]) else: - # Find the existing assoications ID, if any - q = projectB.query("SELECT id FROM bin_associations WHERE suite = %s and bin = %s" % (suite_id, id)); - ql = q.getresult(); - if not ql: - assoication_id = None; + # Find the existing associations ID, if any + q = session.execute("""SELECT id FROM bin_associations + WHERE suite = :suiteid and bin = :pkid""", + {'suiteid': suite_id, 'pkid': pkid}) + ql = q.fetchall() + if len(ql) < 1: + association_id = None else: - assoication_id = ql[0][0]; + association_id = ql[0][0] + # Take action if action == "add": - if assoication_id: - utils.warn("'%s~%s~%s' already exists in suite %s." % (package, version, architecture, suite)); - continue; + if association_id: + utils.warn("'%s_%s_%s' already exists in suite %s." % (package, version, architecture, suite)) + continue else: - q = projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%s, %s)" % (suite_id, id)); + transaction.copy_binary(pkg, suite, component) + Logger.log(["added", package, version, architecture, suite.suite_name, pkid]) elif action == "remove": - if assoication_id == None: - utils.warn("'%s~%s~%s' doesn't exist in suite %s." % (package, version, architecture, suite)); - continue; + if association_id == None: + utils.warn("'%s_%s_%s' doesn't exist in suite %s." % (package, version, architecture, suite)) + continue else: - q = projectB.query("DELETE FROM bin_associations WHERE id = %s" % (assoication_id)); + session.execute("""DELETE FROM bin_associations WHERE id = :pkid""", {'pkid': association_id}) + Logger.log(["removed", package, version, architecture, suite.suite_name, pkid]) - projectB.query("COMMIT WORK"); + session.commit() ####################################################################################### -def get_list (suite): - suite_id = db_access.get_suite_id(suite); +def get_list(suite, session): + suite_id = suite.suite_id # List binaries - q = projectB.query("SELECT b.package, b.version, a.arch_string FROM binaries b, bin_associations ba, architecture a WHERE ba.suite = %s AND ba.bin = b.id AND b.architecture = a.id" % (suite_id)); - ql = q.getresult(); - for i in ql: - print " ".join(i); + q = session.execute("""SELECT b.package, b.version, a.arch_string + FROM binaries b, bin_associations ba, architecture a + WHERE ba.suite = :suiteid + AND ba.bin = b.id AND b.architecture = a.id""", {'suiteid': suite_id}) + for i in q.fetchall(): + print " ".join(i) # List source - q = projectB.query("SELECT s.source, s.version FROM source s, src_associations sa WHERE sa.suite = %s AND sa.source = s.id" % (suite_id)); - ql = q.getresult(); - for i in ql: - print " ".join(i) + " source"; + q = session.execute("""SELECT s.source, s.version + FROM source s, src_associations sa + WHERE sa.suite = :suiteid + AND sa.source = s.id""", {'suiteid': suite_id}) + for i in q.fetchall(): + print " ".join(i) + " source" ####################################################################################### def main (): - global Cnf, projectB, Logger; + global Logger - Cnf = utils.get_conf() + cnf = Config() - Arguments = [('a',"add","Heidi::Options::Add", "HasArg"), - ('h',"help","Heidi::Options::Help"), - ('l',"list","Heidi::Options::List","HasArg"), - ('r',"remove", "Heidi::Options::Remove", "HasArg"), - ('s',"set", "Heidi::Options::Set", "HasArg")]; + Arguments = [('a',"add","Control-Suite::Options::Add", "HasArg"), + ('b',"britney","Control-Suite::Options::Britney"), + ('f','force','Control-Suite::Options::Force'), + ('h',"help","Control-Suite::Options::Help"), + ('l',"list","Control-Suite::Options::List","HasArg"), + ('r',"remove", "Control-Suite::Options::Remove", "HasArg"), + ('s',"set", "Control-Suite::Options::Set", "HasArg")] - for i in ["add", "help", "list", "remove", "set", "version" ]: - if not Cnf.has_key("Heidi::Options::%s" % (i)): - Cnf["Heidi::Options::%s" % (i)] = ""; + for i in ["add", "britney", "help", "list", "remove", "set", "version" ]: + if not cnf.has_key("Control-Suite::Options::%s" % (i)): + cnf["Control-Suite::Options::%s" % (i)] = "" - file_list = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv); - Options = Cnf.SubTree("Heidi::Options") + try: + file_list = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv); + except SystemError as e: + print "%s\n" % e + usage(1) + Options = cnf.subtree("Control-Suite::Options") if Options["Help"]: - usage(); - - projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"],int(Cnf["DB::Port"])); + usage() - db_access.init(Cnf, projectB); + force = Options.has_key("Force") and Options["Force"] - action = None; + action = None for i in ("add", "list", "remove", "set"): - if Cnf["Heidi::Options::%s" % (i)] != "": - suite = Cnf["Heidi::Options::%s" % (i)]; - if db_access.get_suite_id(suite) == -1: - utils.fubar("Unknown suite '%s'." %(suite)); - else: - if action: - utils.fubar("Can only perform one action at a time."); - action = i; + if cnf["Control-Suite::Options::%s" % (i)] != "": + suite_name = cnf["Control-Suite::Options::%s" % (i)] + + if action: + utils.fubar("Can only perform one action at a time.") + + action = i # Need an action... - if action == None: - utils.fubar("No action specified."); + if action is None: + utils.fubar("No action specified.") - # Safety/Sanity check - if action == "set" and suite != "testing": - utils.fubar("Will not reset a suite other than testing."); + britney = False + if action == "set" and cnf["Control-Suite::Options::Britney"]: + britney = True if action == "list": - get_list(suite); + session = DBConn().session() + suite = session.query(Suite).filter_by(suite_name=suite_name).one() + get_list(suite, session) else: - Logger = logging.Logger(Cnf, "heidi"); - if file_list: - for file in file_list: - process_file(utils.open_file(file), suite, action); - else: - process_file(sys.stdin, suite, action); - Logger.close(); + Logger = daklog.Logger("control-suite") + + with ArchiveTransaction() as transaction: + session = transaction.session + suite = session.query(Suite).filter_by(suite_name=suite_name).one() + + if action == "set" and not suite.allowcsset: + if force: + utils.warn("Would not normally allow setting suite {0} (allowcsset is FALSE), but --force used".format(suite_name)) + else: + utils.fubar("Will not reset suite {0} due to its database configuration (allowcsset is FALSE)".format(suite_name)) + + if file_list: + for f in file_list: + process_file(utils.open_file(f), suite, action, transaction, britney, force) + else: + process_file(sys.stdin, suite, action, transaction, britney, force) + + Logger.close() ####################################################################################### if __name__ == '__main__': main() -