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
#######################################################################################
-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, session):
+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 = session.execute("SELECT id FROM source WHERE source = :package AND version = :version",
- {'package': package, 'version': version})
+ suite_version_list = get_suite_version_by_source(package, session)
else:
- q = session.execute("""SELECT b.id FROM binaries b, architecture a
- WHERE b.package = :package AND b.version = :version
- AND (a.arch_string = :arch OR a.arch_string = 'all')
- AND b.architecture = a.id""",
- {'package': package, 'version': version, 'arch': architecture})
+ suite_version_list = get_suite_version_by_package(package, architecture, session)
- ql = q.fetchall()
- if len(ql) < 1:
- utils.warn("Couldn't find '%s_%s_%s'." % (package, version, architecture))
- return None
+ 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") ]
- if len(ql) > 1:
- utils.warn("Found more than one match for '%s_%s_%s'." % (package, version, architecture))
- return None
+ # 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)
- return ql[0][0]
+ 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, session):
+def set_suite(file, suite, transaction, britney=False, force=False):
+ session = transaction.session
suite_id = suite.suite_id
lines = file.readlines()
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():
- key = " ".join(i[:3])
+ for i in q:
+ key = i[:3]
current[key] = i[3]
- q = session.execute("""SELECT s.source, s.version, sa.id
+ 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.fetchall():
- key = " ".join(i[:2]) + " source"
- current[key] = i[2]
+ 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()
if len(split_line) != 3:
utils.warn("'%s' does not break into 'package version architecture'." % (line[:-1]))
continue
- key = " ".join(split_line)
- desired[key] = ""
+ 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()
- pkid = 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":
- session.execute("""DELETE FROM src_associations WHERE id = :pkid""", {'pkid': pkid})
+ transaction.copy_source(pkg, suite, component)
else:
- session.execute("""DELETE FROM bin_associations WHERE id = :pkid""", {'pkid': pkid})
- Logger.log(["removed", key, pkid])
+ 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()
- pkid = get_id (package, version, architecture, session)
- if not pkid:
- 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":
- session.execute("""INSERT INTO src_associations (suite, source)
- VALUES (:suiteid, :pkid)""", {'suiteid': suite_id, 'pkid': pkid})
+ session.execute("""DELETE FROM src_associations WHERE id = :pkid""", {'pkid': pkid})
else:
- session.execute("""INSERT INTO bin_associations (suite, bin)
- VALUES (:suiteid, :pkid)""", {'suiteid': suite_id, 'pkid': pkid})
- Logger.log(["added", key, pkid])
+ session.execute("""DELETE FROM bin_associations WHERE id = :pkid""", {'pkid': pkid})
+ Logger.log(["removed", " ".join(key), pkid])
session.commit()
+ if britney:
+ britney_changelog(current, suite, session)
+
#######################################################################################
-def process_file(file, suite, action, session):
+def process_file(file, suite, action, transaction, britney=False, force=False):
+ session = transaction.session
+
if action == "set":
- set_suite(file, suite, session)
+ set_suite(file, suite, transaction, britney, force)
return
suite_id = suite.suite_id
- lines = file.readlines()
+ request = []
# Our session is already in a transaction
- for line in lines:
+ 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
+ request.append(split_line)
- (package, version, architecture) = split_line
+ request.sort(cmp=cmp_package_version)
- pkid = get_id(package, version, architecture, session)
- if not pkid:
+ 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
+
+ component = pkg.poolfile.component
+
+ # 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 association ID, if any
utils.warn("'%s_%s_%s' already exists in suite %s." % (package, version, architecture, suite))
continue
else:
- session.execute("""INSERT INTO src_associations (suite, source)
- VALUES (:suiteid, :pkid)""",
- {'suiteid': suite_id, 'pkid': pkid})
+ transaction.copy_source(pkg, suite, component)
+ Logger.log(["added", package, version, architecture, suite.suite_name, pkid])
+
elif action == "remove":
if association_id == None:
utils.warn("'%s_%s_%s' doesn't exist in suite %s." % (package, version, architecture, suite))
continue
else:
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 associations ID, if any
q = session.execute("""SELECT id FROM bin_associations
utils.warn("'%s_%s_%s' already exists in suite %s." % (package, version, architecture, suite))
continue
else:
- session.execute("""INSERT INTO bin_associations (suite, bin)
- VALUES (:suiteid, :pkid)""",
- {'suiteid': suite_id, 'pkid': pkid})
+ transaction.copy_binary(pkg, suite, component)
+ Logger.log(["added", package, version, architecture, suite.suite_name, pkid])
elif action == "remove":
if association_id == None:
utils.warn("'%s_%s_%s' doesn't exist in suite %s." % (package, version, architecture, suite))
continue
else:
session.execute("""DELETE FROM bin_associations WHERE id = :pkid""", {'pkid': association_id})
+ Logger.log(["removed", package, version, architecture, suite.suite_name, pkid])
session.commit()
cnf = Config()
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" ]:
+ 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)] = ""
try:
- file_list = apt_pkg.ParseCommandLine(cnf.Cnf, Arguments, sys.argv);
- except SystemError, e:
+ 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")
+ Options = cnf.subtree("Control-Suite::Options")
if Options["Help"]:
usage()
- session = DBConn().session()
+ force = Options.has_key("Force") and Options["Force"]
action = None
for i in ("add", "list", "remove", "set"):
if cnf["Control-Suite::Options::%s" % (i)] != "":
suite_name = cnf["Control-Suite::Options::%s" % (i)]
- suite = get_suite(suite_name, session=session)
- if suite is None:
- utils.fubar("Unknown suite '%s'." % (suite_name))
- else:
- if action:
- utils.fubar("Can only perform one action at a time.")
- action = i
+
+ if action:
+ utils.fubar("Can only perform one action at a time.")
+
+ action = i
# Need an action...
- if action == None:
+ if action is None:
utils.fubar("No action specified.")
- # Safety/Sanity check
- # XXX: This should be stored in the database
- if action == "set" and suite_name not in ["testing"]:
- utils.fubar("Will not reset suite %s" % (suite_name))
+ britney = False
+ if action == "set" and cnf["Control-Suite::Options::Britney"]:
+ britney = True
if action == "list":
+ session = DBConn().session()
+ suite = session.query(Suite).filter_by(suite_name=suite_name).one()
get_list(suite, session)
else:
- Logger = daklog.Logger(cnf.Cnf, "control-suite")
- if file_list:
- for f in file_list:
- process_file(utils.open_file(f), suite, action, session)
- else:
- process_file(sys.stdin, suite, action, session)
+ 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} (allowsetcs is FALSE), but --force used".format(suite_name))
+ else:
+ utils.fubar("Will not reset suite {0} due to its database configuration (allowsetcs 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()
#######################################################################################