Source code for dak.dominate

#! /usr/bin/env python3

"""
Remove obsolete source and binary associations from suites.

@contact: Debian FTP Master <ftpmaster@debian.org>
@copyright: 2009  Torsten Werner <twerner@debian.org>
@license: GNU General Public License version 2 or later
"""

# 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

from daklib.dbconn import *
from daklib.config import Config
from daklib import daklog, utils
import apt_pkg
import sys

from sqlalchemy.sql import exists, text
from tabulate import tabulate


Options = None
Logger = None


[docs]def retrieve_associations(suites, session): return session.execute(text(''' WITH -- Provide (source, suite) tuple of all source packages to remain remain_source AS ( SELECT * FROM ( SELECT source.id AS source_id, src_associations.suite AS suite_id, -- generate rank over versions of a source package in one suite -- "1" being the newest dense_rank() OVER ( PARTITION BY source.source, src_associations.suite ORDER BY source.version DESC ) AS version_rank FROM source INNER JOIN src_associations ON src_associations.source = source.id AND src_associations.suite = ANY(:suite_ids) ) AS source_ranked WHERE version_rank = 1 ), -- Provide (source, arch, suite) tuple of all binary packages to remain remain_binaries AS ( SELECT * FROM ( SELECT binaries.id, binaries.architecture AS arch_id, bin_associations.suite AS suite_id, source.id AS source_id, architecture.arch_string AS arch, -- arch of newest version first_value(architecture.arch_string) OVER ( PARTITION BY binaries.package, bin_associations.suite ORDER BY binaries.version DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) as arch_first, -- generate rank over versions of a source package in one suite -- "1" being the newest -- if newest package is arch-any, we use the rank only over current arch dense_rank() OVER ( PARTITION BY binaries.package, binaries.architecture, bin_associations.suite ORDER BY binaries.version DESC ) AS version_rank_any, -- if newest package is arch-all, we use the rank over all arches -- this makes it possible to replace all by any and any by all dense_rank() OVER ( PARTITION BY binaries.package, bin_associations.suite ORDER BY binaries.version DESC ) AS version_rank_all FROM binaries INNER JOIN source ON source.id = binaries.source INNER JOIN bin_associations ON bin_associations.bin = binaries.id AND bin_associations.suite = ANY(:suite_ids) INNER JOIN architecture ON architecture.id = binaries.architecture ) AS source_rank WHERE -- we only want to retain the newest of each CASE WHEN arch != 'all' AND arch_first != 'all' THEN version_rank_any = 1 ELSE version_rank_all = 1 END ), -- Figure out which source we should remove -- A binary forces the corresponding source to remain dominate_source AS ( SELECT source.source AS source_package, source.version AS source_version, source.source AS package, source.version, 'source'::text AS arch, suite.suite_name AS suite, src_associations.id AS assoc_id FROM source INNER JOIN src_associations ON src_associations.source = source.id AND src_associations.suite = ANY(:suite_ids) INNER join suite ON suite.id = src_associations.suite LEFT JOIN remain_binaries ON remain_binaries.source_id = source.id AND remain_binaries.suite_id = src_associations.suite LEFT JOIN remain_source ON remain_source.source_id = source.id AND remain_source.suite_id = src_associations.suite WHERE remain_binaries.source_id IS NULL AND remain_source.source_id IS NULL ), -- Figure out which arch-any binaries we should remove dominate_binaries AS ( SELECT source.source AS source_package, source.version AS source_version, binaries.package AS package, binaries.version, architecture.arch_string AS arch, suite.suite_name AS suite, bin_associations.id AS assoc_id FROM binaries INNER JOIN source ON source.id = binaries.source INNER JOIN bin_associations ON bin_associations.bin = binaries.id AND bin_associations.suite = ANY(:suite_ids) INNER JOIN architecture ON architecture.id = binaries.architecture INNER join suite ON suite.id = bin_associations.suite LEFT JOIN remain_binaries ON remain_binaries.id = binaries.id AND remain_binaries.arch_id = binaries.architecture AND remain_binaries.suite_id = bin_associations.suite WHERE remain_binaries.source_id IS NULL AND binaries.architecture != (SELECT id from architecture WHERE arch_string = 'all') ), -- Figure out which arch-all binaries we should remove -- A arch-any binary forces the related arch-all binaries to remain dominate_binaries_all AS ( SELECT source.source AS source_package, source.version AS source_version, binaries.package AS package, binaries.version, architecture.arch_string AS arch, suite.suite_name AS suite, bin_associations.id AS assoc_id FROM binaries INNER JOIN source ON source.id = binaries.source INNER JOIN bin_associations ON bin_associations.bin = binaries.id AND bin_associations.suite = ANY(:suite_ids) INNER JOIN architecture ON architecture.id = binaries.architecture INNER join suite ON suite.id = bin_associations.suite LEFT JOIN remain_binaries ON remain_binaries.id = binaries.id AND remain_binaries.arch_id = binaries.architecture AND remain_binaries.suite_id = bin_associations.suite LEFT JOIN remain_binaries AS remain_binaries_any ON remain_binaries_any.source_id = source.id AND remain_binaries_any.suite_id = bin_associations.suite AND remain_binaries_any.arch_id != (SELECT id from architecture WHERE arch_string = 'all') WHERE remain_binaries.source_id IS NULL AND remain_binaries_any.source_id IS NULL AND binaries.architecture = (SELECT id from architecture WHERE arch_string = 'all') ) SELECT * FROM dominate_source UNION SELECT * FROM dominate_binaries UNION SELECT * FROM dominate_binaries_all ORDER BY source_package, source_version, package, version, arch, suite ''').params( suite_ids=[s.suite_id for s in suites], ))
[docs]def delete_associations_table(table, ids, session): result = session.execute(text(''' DELETE FROM {} WHERE id = ANY(:assoc_ids) '''.format(table)).params( assoc_ids=list(ids), )) assert result.rowcount == len(ids), 'Rows deleted are not equal to deletion requests'
[docs]def delete_associations(assocs, session): ids_bin = set() ids_src = set() for e in assocs: Logger.log(['newer', e.package, e.version, e.suite, e.arch, e.assoc_id]) if e.arch == 'source': ids_src.add(e.assoc_id) else: ids_bin.add(e.assoc_id) delete_associations_table('bin_associations', ids_bin, session) delete_associations_table('src_associations', ids_src, session)
[docs]def usage(): print("""Usage: dak dominate [OPTIONS] Remove obsolete source and binary associations from suites. -s, --suite=SUITE act on this suite -h, --help show this help and exit -n, --no-action don't commit changes -f, --force also clean up untouchable suites SUITE can be comma (or space) separated list, e.g. --suite=testing,unstable""") sys.exit()
[docs]def main(): global Options, Logger cnf = Config() Arguments = [('h', "help", "Obsolete::Options::Help"), ('s', "suite", "Obsolete::Options::Suite", "HasArg"), ('n', "no-action", "Obsolete::Options::No-Action"), ('f', "force", "Obsolete::Options::Force")] cnf['Obsolete::Options::Help'] = '' cnf['Obsolete::Options::No-Action'] = '' cnf['Obsolete::Options::Force'] = '' apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) Options = cnf.subtree("Obsolete::Options") if Options['Help']: usage() if not Options['No-Action']: Logger = daklog.Logger("dominate") session = DBConn().session() suites_query = (session .query(Suite) .order_by(Suite.suite_name) .filter(~exists().where(Suite.suite_id == PolicyQueue.suite_id))) if 'Suite' in Options: suites_query = suites_query.filter(Suite.suite_name.in_(utils.split_args(Options['Suite']))) if not Options['Force']: suites_query = suites_query.filter_by(untouchable=False) suites = suites_query.all() assocs = list(retrieve_associations(suites, session)) if Options['No-Action']: headers = ('source package', 'source version', 'package', 'version', 'arch', 'suite', 'id') print((tabulate(assocs, headers, tablefmt="orgtbl"))) session.rollback() else: delete_associations(assocs, session) session.commit() if Logger: Logger.close()
if __name__ == '__main__': main()