control_suite.py 17.5 KB
Newer Older
J
James Troup 已提交
1 2
#!/usr/bin/env python

J
Joerg Jaspert 已提交
3
""" Manipulate suite tags """
4
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
J
James Troup 已提交
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

# 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

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

# 8to6Guy: "Wow, Bob, You look rough!"
# BTAF: "Mbblpmn..."
# BTAF <.oO>: "You moron! This is what you get for staying up all night drinking vodka and salad dressing!"
# BTAF <.oO>: "This coffee I.V. drip is barely even keeping me awake! I need something with more kick! But what?"
# BTAF: "OMIGOD! I OVERDOSED ON HEROIN"
# CoWorker#n: "Give him air!!"
# CoWorker#n+1: "We need a syringe full of adrenaline!"
# CoWorker#n+2: "Stab him in the heart!"
# BTAF: "*YES!*"
# CoWorker#n+3: "Bob's been overdosing quite a bit lately..."
# CoWorker#n+4: "Third time this week."

# -- http://www.angryflower.com/8to6.gif

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

# Adds or removes packages from a suite.  Takes the list of files
# either from stdin or as a command line argument.  Special action
J
James Troup 已提交
40
# "set", will reset the suite (!) and add all packages from scratch.
J
James Troup 已提交
41 42 43

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

44
import sys
45
import apt_pkg
J
Joerg Jaspert 已提交
46
import os
47

48
from daklib.archive import ArchiveTransaction
49 50
from daklib.config import Config
from daklib.dbconn import *
51
from daklib import daklog
52
from daklib import utils
53
from daklib.queue import get_suite_version_by_package, get_suite_version_by_source
J
James Troup 已提交
54 55 56

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

57
Logger = None
J
James Troup 已提交
58

J
James Troup 已提交
59 60 61
################################################################################

def usage (exit_code=0):
62
    print """Usage: dak control-suite [OPTIONS] [FILE]
J
James Troup 已提交
63 64 65
Display or alter the contents of a suite using FILE(s), or stdin.

  -a, --add=SUITE            add to SUITE
J
James Troup 已提交
66
  -h, --help                 show this help and exit
J
James Troup 已提交
67 68
  -l, --list=SUITE           list the contents of SUITE
  -r, --remove=SUITE         remove from SUITE
69 70
  -s, --set=SUITE            set SUITE
  -b, --britney              generate changelog entry for britney runs"""
J
James Troup 已提交
71 72 73

    sys.exit(exit_code)

J
James Troup 已提交
74 75
#######################################################################################

76 77
def get_pkg(package, version, architecture, session):
    if architecture == 'source':
78 79
        q = session.query(DBSource).filter_by(source=package, version=version) \
            .join(DBSource.poolfile)
J
James Troup 已提交
80
    else:
81
        q = session.query(DBBinary).filter_by(package=package, version=version) \
82 83
            .join(DBBinary.architecture).filter(Architecture.arch_string.in_([architecture, 'all'])) \
            .join(DBBinary.poolfile)
84

85 86 87 88
    pkg = q.first()
    if pkg is None:
        utils.warn("Could not find {0}_{1}_{2}.".format(package, version, architecture))
    return pkg
J
James Troup 已提交
89 90

#######################################################################################
J
James Troup 已提交
91

92 93 94 95
def britney_changelog(packages, suite, session):

    old = {}
    current = {}
L
Luca Falavigna 已提交
96
    Cnf = utils.get_conf()
97

98
    try:
L
Luca Falavigna 已提交
99 100
        q = session.execute("SELECT changelog FROM suite WHERE id = :suiteid", \
                            {'suiteid': suite.suite_id})
101 102
        brit_file = q.fetchone()[0]
    except:
103 104
        brit_file = None

L
Luca Falavigna 已提交
105 106 107
    if brit_file:
        brit_file = os.path.join(Cnf['Dir::Root'], brit_file)
    else:
108 109
        return

110 111 112 113 114 115 116 117 118 119 120 121 122 123
    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():
124
            if apt_pkg.version_compare(current[p], old[p]) > 0:
125 126 127 128 129 130 131 132
                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])
133 134
        query += " AND architecture LIKE '%source%' AND distribution in \
                  ('unstable', 'experimental', 'testing-proposed-updates') OR"
135 136 137 138
    query += " False ORDER BY source, version DESC"
    q = session.execute(query)

    pu = None
139
    brit = utils.open_file(brit_file, 'w')
140 141 142 143 144 145

    for u in q:
        if pu and pu != u[0]:
            brit.write("\n")
        brit.write("%s\n" % u[1])
        pu = u[0]
J
Joerg Jaspert 已提交
146
    if q.rowcount: brit.write("\n\n\n")
147 148 149 150 151 152 153 154 155

    for p in list(set(old.keys()).difference(current.keys())):
        brit.write("REMOVED: %s %s\n" % (p, old[p]))

    brit.flush()
    brit.close()

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

156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
def version_checks(package, architecture, target_suite, new_version, session, force = False):
    if architecture == "source":
        suite_version_list = get_suite_version_by_source(package, session)
    else:
        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:
172
        cmp = apt_pkg.version_compare(new_version, version)
173
        if suite in must_be_newer_than and cmp < 1:
174
            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))
175 176
            violations = True
        if suite in must_be_older_than and cmp > 1:
177
            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))
178 179 180
            violations = True

    if violations:
181
        if force:
182 183 184 185 186 187
            utils.warn("Continuing anyway (forced)...")
        else:
            utils.fubar("Aborting. Version checks violated and not forced.")

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

188 189
def cmp_package_version(a, b):
    """
190
    comparison function for tuples of the form (package-name, version, arch, ...)
191
    """
192 193 194 195 196 197 198 199 200 201
    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
202 203 204

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

205 206
def set_suite(file, suite, transaction, britney=False, force=False):
    session = transaction.session
207
    suite_id = suite.suite_id
208
    lines = file.readlines()
209

210
    # Our session is already in a transaction
211

J
James Troup 已提交
212
    # Build up a dictionary of what is currently in the suite
213
    current = {}
214 215 216 217
    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})
218 219
    for i in q:
        key = i[:3]
220
        current[key] = i[3]
221

222
    q = session.execute("""SELECT s.source, s.version, 'source', sa.id
223 224 225
                             FROM source s, src_associations sa
                            WHERE sa.suite = :suiteid
                              AND sa.source = s.id""", {'suiteid': suite_id})
226 227 228
    for i in q:
        key = i[:3]
        current[key] = i[3]
J
James Troup 已提交
229 230

    # Build up a dictionary of what should be in the suite
231
    desired = set()
232
    for line in lines:
233
        split_line = line.strip().split()
J
James Troup 已提交
234
        if len(split_line) != 3:
M
Mark Hymers 已提交
235
            utils.warn("'%s' does not break into 'package version architecture'." % (line[:-1]))
236
            continue
237
        desired.add(tuple(split_line))
J
James Troup 已提交
238 239

    # Check to see which packages need added and add them
240 241 242
    for key in sorted(desired, cmp=cmp_package_version):
        if key not in current:
            (package, version, architecture) = key
243
            version_checks(package, architecture, suite.suite_name, version, session, force)
244 245
            pkg = get_pkg(package, version, architecture, session)
            if pkg is None:
246
                continue
247 248

            component = pkg.poolfile.component
J
James Troup 已提交
249
            if architecture == "source":
250
                transaction.copy_source(pkg, suite, component)
J
James Troup 已提交
251
            else:
252 253 254
                transaction.copy_binary(pkg, suite, component)

            Logger.log(["added", " ".join(key)])
J
James Troup 已提交
255

256
    # Check to see which packages need removed and remove them
257 258 259
    for key, pkid in current.iteritems():
        if key not in desired:
            (package, version, architecture) = key
260 261 262 263
            if architecture == "source":
                session.execute("""DELETE FROM src_associations WHERE id = :pkid""", {'pkid': pkid})
            else:
                session.execute("""DELETE FROM bin_associations WHERE id = :pkid""", {'pkid': pkid})
264
            Logger.log(["removed", " ".join(key), pkid])
265

266
    session.commit()
J
James Troup 已提交
267

268 269 270
    if britney:
        britney_changelog(current, suite, session)

J
James Troup 已提交
271 272
#######################################################################################

273 274 275
def process_file(file, suite, action, transaction, britney=False, force=False):
    session = transaction.session

J
James Troup 已提交
276
    if action == "set":
277
        set_suite(file, suite, transaction, britney, force)
278
        return
J
James Troup 已提交
279

280
    suite_id = suite.suite_id
J
James Troup 已提交
281

282
    request = []
J
James Troup 已提交
283

284
    # Our session is already in a transaction
285
    for line in file:
286
        split_line = line.strip().split()
J
James Troup 已提交
287
        if len(split_line) != 3:
M
Mark Hymers 已提交
288
            utils.warn("'%s' does not break into 'package version architecture'." % (line[:-1]))
289
            continue
290
        request.append(split_line)
J
James Troup 已提交
291

292
    request.sort(cmp=cmp_package_version)
J
James Troup 已提交
293

294
    for package, version, architecture in request:
295 296
        pkg = get_pkg(package, version, architecture, session)
        if pkg is None:
297
            continue
298 299 300 301 302 303
        if architecture == 'source':
            pkid = pkg.source_id
        else:
            pkid = pkg.binary_id

        component = pkg.poolfile.component
J
James Troup 已提交
304

305 306 307 308
        # Do version checks when adding packages
        if action == "add":
            version_checks(package, architecture, suite.suite_name, version, session, force)

J
James Troup 已提交
309
        if architecture == "source":
310 311 312 313 314 315 316
            # 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
J
James Troup 已提交
317
            else:
318 319
                association_id = ql[0][0]

J
James Troup 已提交
320 321
            # Take action
            if action == "add":
322
                if association_id:
M
Mark Hymers 已提交
323
                    utils.warn("'%s_%s_%s' already exists in suite %s." % (package, version, architecture, suite))
324
                    continue
J
James Troup 已提交
325
                else:
326
                    transaction.copy_source(pkg, suite, component)
J
Joerg Jaspert 已提交
327
                    Logger.log(["added", package, version, architecture, suite.suite_name, pkid])
J
Joerg Jaspert 已提交
328

J
James Troup 已提交
329
            elif action == "remove":
330
                if association_id == None:
M
Mark Hymers 已提交
331
                    utils.warn("'%s_%s_%s' doesn't exist in suite %s." % (package, version, architecture, suite))
332
                    continue
J
James Troup 已提交
333
                else:
334
                    session.execute("""DELETE FROM src_associations WHERE id = :pkid""", {'pkid': association_id})
J
Joerg Jaspert 已提交
335
                    Logger.log(["removed", package, version, architecture, suite.suite_name, pkid])
J
James Troup 已提交
336
        else:
337 338 339 340 341 342 343
            # 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
J
James Troup 已提交
344
            else:
345 346
                association_id = ql[0][0]

J
James Troup 已提交
347 348
            # Take action
            if action == "add":
349
                if association_id:
M
Mark Hymers 已提交
350
                    utils.warn("'%s_%s_%s' already exists in suite %s." % (package, version, architecture, suite))
351
                    continue
J
James Troup 已提交
352
                else:
353
                    transaction.copy_binary(pkg, suite, component)
J
Joerg Jaspert 已提交
354
                    Logger.log(["added", package, version, architecture, suite.suite_name, pkid])
J
James Troup 已提交
355
            elif action == "remove":
356
                if association_id == None:
M
Mark Hymers 已提交
357
                    utils.warn("'%s_%s_%s' doesn't exist in suite %s." % (package, version, architecture, suite))
358
                    continue
J
James Troup 已提交
359
                else:
360
                    session.execute("""DELETE FROM bin_associations WHERE id = :pkid""", {'pkid': association_id})
J
Joerg Jaspert 已提交
361
                    Logger.log(["removed", package, version, architecture, suite.suite_name, pkid])
362

363
    session.commit()
364

J
James Troup 已提交
365 366
#######################################################################################

367 368
def get_list(suite, session):
    suite_id = suite.suite_id
J
James Troup 已提交
369
    # List binaries
370 371 372 373 374
    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():
375
        print " ".join(i)
J
James Troup 已提交
376 377

    # List source
378 379 380 381 382
    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():
383
        print " ".join(i) + " source"
J
James Troup 已提交
384 385 386 387

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

def main ():
388
    global Logger
J
James Troup 已提交
389

390
    cnf = Config()
J
James Troup 已提交
391

392
    Arguments = [('a',"add","Control-Suite::Options::Add", "HasArg"),
393
                 ('b',"britney","Control-Suite::Options::Britney"),
394
                 ('f','force','Control-Suite::Options::Force'),
395 396 397 398
                 ('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")]
J
James Troup 已提交
399

400
    for i in ["add", "britney", "help", "list", "remove", "set", "version" ]:
401 402
        if not cnf.has_key("Control-Suite::Options::%s" % (i)):
            cnf["Control-Suite::Options::%s" % (i)] = ""
J
James Troup 已提交
403

404
    try:
405
        file_list = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv);
406
    except SystemError as e:
407 408
        print "%s\n" % e
        usage(1)
409
    Options = cnf.subtree("Control-Suite::Options")
J
James Troup 已提交
410 411

    if Options["Help"]:
412
        usage()
J
James Troup 已提交
413

414 415
    force = Options.has_key("Force") and Options["Force"]

416
    action = None
J
James Troup 已提交
417 418

    for i in ("add", "list", "remove", "set"):
419 420
        if cnf["Control-Suite::Options::%s" % (i)] != "":
            suite_name = cnf["Control-Suite::Options::%s" % (i)]
J
James Troup 已提交
421

422 423 424 425
            if action:
                utils.fubar("Can only perform one action at a time.")

            action = i
M
Mark Hymers 已提交
426

J
James Troup 已提交
427
    # Need an action...
428
    if action is None:
M
Mark Hymers 已提交
429
        utils.fubar("No action specified.")
J
James Troup 已提交
430

431 432 433 434
    britney = False
    if action == "set" and cnf["Control-Suite::Options::Britney"]:
        britney = True

J
James Troup 已提交
435
    if action == "list":
436 437
        session = DBConn().session()
        suite = session.query(Suite).filter_by(suite_name=suite_name).one()
438
        get_list(suite, session)
J
James Troup 已提交
439
    else:
440
        Logger = daklog.Logger("control-suite")
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457

        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)

458
        Logger.close()
J
James Troup 已提交
459 460 461 462 463

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

if __name__ == '__main__':
    main()