#10 WIP: a tool introducing batch into multicall of clone-tag
Closed 5 years ago by julian8628. Opened 6 years ago by julian8628.
julian8628/koji-tools koji-batch-clone-tag  into  master

@@ -0,0 +1,456 @@ 

+ #!/usr/bin/env python

+ 

+ import sys

+ import os

+ import time

+ 

+ import six

+ 

+ import koji

+ import koji.tasks

+ from koji_cli.lib import OptionParser, _, activate_session

+ 

+ 

+ def handle_batch_clone_tag():

+     """[admin] Clone tag with a batch size"""

+     usage = _("usage: %prog batch-clone-tag [options] <src-tag> <dst-tag>")

+     usage += _(

+         "\n(Specify the --help global option for a list of other help options)")

+     parser = OptionParser(usage=usage)

+     parser.add_option('-p', '--profile', default='koji',

+                       help='pick a profile')

+     parser.add_option('--config', action='store_true',

+                       help=_("Copy config from the source to the dest tag"))

+     parser.add_option('--groups', action='store_true',

+                       help=_("Copy group information"))

+     parser.add_option('--pkgs', action='store_true',

+                       help=_(

+                           "Copy package list from the source to the dest tag"))

+     parser.add_option('--builds', action='store_true',

+                       help=_("Tag builds into the dest tag"))

+     parser.add_option('--all', action='store_true',

+                       help=_("The same as --config --groups --pkgs --builds"))

+     parser.add_option('--latest-only', action='store_true',

+                       help=_("Tag only the latest build of each package"))

+     parser.add_option('--inherit-builds', action='store_true',

+                       help=_(

+                           "Include all builds inherited into the source tag into "

+                           "the dest tag"))

+     parser.add_option('--ts', type='int',

+                       help=_('Clone tag at a specific timestamp'))

+     parser.add_option('--event', type='int',

+                       help=_('Clone tag at a specific event'))

+     parser.add_option('--repo', type='int',

+                       help=_('Clone tag at a specific repo event'))

+     parser.add_option("-v", "--verbose", action="store_true",

+                       help=_("show changes"))

+     parser.add_option("-f", "--force", action="store_true",

+                       help=_("override tag locks if necessary"))

+     parser.add_option("-n", "--test", action="store_true", help=_("test mode"))

+     parser.add_option("--batch", type='int', default=1000,

+                       help=_("muiltCall batch size(Default: 1000)"))

+     (options, args) = parser.parse_args()

+ 

+     _koji = koji.get_profile_module(options.profile)

+     dir_opts = ('topdir', 'cert', 'serverca')

+     for name in dir_opts:

+         # expand paths here, so we don't have to worry about it later

+         value = os.path.expanduser(getattr(_koji.config, name))

+         setattr(_koji.config, name, value)

+     session_opts = koji.grab_session_options(_koji.config)

+     session = koji.ClientSession(_koji.config.server, session_opts)

+ 

+     if len(args) != 2:

+         parser.error(

+             _("This command takes two arguments: <src-tag> <dst-tag>"))

+         assert False  # pragma: no cover

+     activate_session(session, _koji.config)

+ 

+     if not session.hasPerm('admin') and not options.test:

+         print(_("This action requires admin privileges"))

+         return

+ 

+     if args[0] == args[1]:

+         sys.stdout.write('Source and destination tags must be different.\n')

+         return

+ 

+     if options.all:

+         options.config = options.groups = options.pkgs = options.builds = True

+ 

+     if options.batch <= 0:

+         parser.error(_("batch size must be bigger than zero"))

+ 

+     event = koji.util.eventFromOpts(session, options) or {}

+     if event:

+         event['timestr'] = time.asctime(time.localtime(event['ts']))

+         print(_("Cloning at event %(id)i (%(timestr)s)") % event)

+ 

+     # store tags.

+     srctag = session.getTag(args[0])

+     dsttag = session.getTag(args[1])

+     if not srctag:

+         sys.stdout.write("Unknown src-tag: %s\n" % args[0])

+         return

+     if (srctag['locked'] and not options.force) or (

+                     dsttag and dsttag['locked'] and not options.force):

+         print(_(

+             "Error: You are attempting to clone from or to a tag which is locked."))

+         print(_("Please use --force if this is what you really want to do."))

+         return

+ 

+     # init debug lists.

+     chgpkglist = []

+     chgbldlist = []

+     chggrplist = []

+     # case of brand new dst-tag.

+     if not dsttag:

+         if not options.config:

+             print(_('Cannot create tag without specifying --config'))

+             return

+         # create a new tag, copy srctag header.

+         if not options.test:

+             session.createTag(args[1], parent=None, arches=srctag['arches'],

+                               perm=srctag['perm_id'],

+                               locked=srctag['locked'],

+                               maven_support=srctag['maven_support'],

+                               maven_include_all=srctag['maven_include_all'])

+             newtag = session.getTag(args[1],

+                                     strict=True)  # store the new tag, need its asigned id.

+         # get pkglist of src-tag, including inherited packages.

+         if options.pkgs:

+             srcpkgs = session.listPackages(tagID=srctag['id'], inherited=True,

+                                            event=event.get('id'))

+             srcpkgs.sort(key=lambda x: x['package_name'])

+             if not options.test:

+                 session.multicall = True

+             for pkgs in srcpkgs:

+                 # for each package add one entry in the new tag.

+                 chgpkglist.append(('[new]', pkgs['package_name'],

+                                    pkgs['blocked'], pkgs['owner_name'],

+                                    pkgs['tag_name']))

+                 if not options.test:

+                     # add packages.

+                     session.packageListAdd(newtag['name'],

+                                            pkgs['package_name'],

+                                            owner=pkgs['owner_name'],

+                                            block=pkgs['blocked'],

+                                            extra_arches=pkgs['extra_arches'])

+             if not options.test:

+                 _batch_multicall(session, options.batch)

+         if options.builds:

+             # get --all latest builds from src tag

+             builds = session.listTagged(srctag['id'], event=event.get('id'),

+                                         inherit=options.inherit_builds,

+                                         latest=options.latest_only)

+             if not options.test:

+                 session.multicall = True

+             for build in builds:

+                 build['name'] = build[

+                     'package_name']  # add missing 'name' field.

+                 chgbldlist.append(('[new]', build['package_name'],

+                                    build['nvr'],

+                                    koji.BUILD_STATES[build['state']],

+                                    build['owner_name'], build['tag_name']))

+                 # copy latest builds into new tag

+                 if not options.test:

+                     session.tagBuildBypass(newtag['name'], build,

+                                            force=options.force)

+             if not options.test:

+                 _batch_multicall(session, options.batch)

+         if options.groups:

+             # Copy the group data

+             srcgroups = session.getTagGroups(srctag['name'],

+                                              event=event.get('id'))

+             if not options.test:

+                 session.multicall = True

+             for group in srcgroups:

+                 if not options.test:

+                     session.groupListAdd(newtag['name'], group['name'])

+                 for pkg in group['packagelist']:

+                     if not options.test:

+                         session.groupPackageListAdd(newtag['name'],

+                                                     group['name'],

+                                                     pkg['package'],

+                                                     block=pkg['blocked'])

+                     chggrplist.append(('[new]', pkg['package'], group['name']))

+             if not options.test:

+                 _batch_multicall(session, options.batch)

+     # case of existing dst-tag.

+     if dsttag:

+         # get fresh list of packages & builds into maps.

+         srcpkgs = {}

+         dstpkgs = {}

+         srclblds = {}

+         dstlblds = {}

+         srcgroups = {}

+         dstgroups = {}

+         if options.pkgs:

+             for pkg in session.listPackages(tagID=srctag['id'], inherited=True,

+                                             event=event.get('id')):

+                 srcpkgs[pkg['package_name']] = pkg

+             for pkg in session.listPackages(tagID=dsttag['id'],

+                                             inherited=True):

+                 dstpkgs[pkg['package_name']] = pkg

+         if options.builds:

+             src_builds = session.listTagged(srctag['id'],

+                                             event=event.get('id'),

+                                             inherit=options.inherit_builds,

+                                             latest=options.latest_only)

+             for build in src_builds:

+                 srclblds[build['nvr']] = build

+             for build in session.getLatestBuilds(dsttag['name']):

+                 dstlblds[build['nvr']] = build

+         if options.groups:

+             for group in session.getTagGroups(srctag['name'],

+                                               event=event.get('id')):

+                 srcgroups[group['name']] = group

+             for group in session.getTagGroups(dsttag['name']):

+                 dstgroups[group['name']] = group

+         # construct to-do lists.

+         paddlist = []  # list containing new packages to be added from src tag

+         for (package_name, pkg) in six.iteritems(srcpkgs):

+             if package_name not in dstpkgs:

+                 paddlist.append(pkg)

+         paddlist.sort(key=lambda x: x['package_name'])

+         pdellist = []  # list containing packages no more present in dst tag

+         for (package_name, pkg) in six.iteritems(dstpkgs):

+             if package_name not in srcpkgs:

+                 pdellist.append(pkg)

+         pdellist.sort(key=lambda x: x['package_name'])

+         baddlist = []  # list containing new builds to be added from src tag

+         for (nvr, lbld) in six.iteritems(srclblds):

+             if nvr not in dstlblds:

+                 baddlist.append(lbld)

+         baddlist.sort(key=lambda x: x['package_name'])

+         bdellist = []  # list containing new builds to be removed from src tag

+         for (nvr, lbld) in six.iteritems(dstlblds):

+             if nvr not in srclblds:

+                 bdellist.append(lbld)

+         bdellist.sort(key=lambda x: x['package_name'])

+         gaddlist = []  # list containing new groups to be added from src tag

+         for (grpname, group) in six.iteritems(srcgroups):

+             if grpname not in dstgroups:

+                 gaddlist.append(group)

+         gdellist = []  # list containing groups to be removed from src tag

+         for (grpname, group) in six.iteritems(dstgroups):

+             if grpname not in srcgroups:

+                 gdellist.append(group)

+         grpchanges = {}  # dict of changes to make in shared groups

+         for (grpname, group) in six.iteritems(srcgroups):

+             if grpname in dstgroups:

+                 grpchanges[grpname] = {'adds': [], 'dels': []}

+                 # Store whether group is inherited or not

+                 grpchanges[grpname]['inherited'] = False

+                 if group['tag_id'] != dsttag['id']:

+                     grpchanges[grpname]['inherited'] = True

+                 srcgrppkglist = []

+                 dstgrppkglist = []

+                 for pkg in group['packagelist']:

+                     srcgrppkglist.append(pkg['package'])

+                 for pkg in dstgroups[grpname]['packagelist']:

+                     dstgrppkglist.append(pkg['package'])

+                 for pkg in srcgrppkglist:

+                     if not pkg in dstgrppkglist:

+                         grpchanges[grpname]['adds'].append(pkg)

+                 for pkg in dstgrppkglist:

+                     if not pkg in srcgrppkglist:

+                         grpchanges[grpname]['dels'].append(pkg)

+         # ADD new packages.

+         if not options.test:

+             session.multicall = True

+         for pkg in paddlist:

+             chgpkglist.append(('[add]', pkg['package_name'],

+                                pkg['blocked'], pkg['owner_name'],

+                                pkg['tag_name']))

+             if not options.test:

+                 session.packageListAdd(dsttag['name'], pkg['package_name'],

+                                        owner=pkg['owner_name'],

+                                        block=pkg['blocked'],

+                                        extra_arches=pkg['extra_arches'])

+         if not options.test:

+             _batch_multicall(session, options.batch)

+         # ADD builds.

+         if not options.test:

+             session.multicall = True

+         for build in baddlist:

+             build['name'] = build['package_name']  # add missing 'name' field.

+             chgbldlist.append(('[add]', build['package_name'], build['nvr'],

+                                koji.BUILD_STATES[build['state']],

+                                build['owner_name'], build['tag_name']))

+             # copy latest builds into new tag.

+             if not options.test:

+                 session.tagBuildBypass(dsttag['name'], build,

+                                        force=options.force)

+         if not options.test:

+             _batch_multicall(session, options.batch)

+         # ADD groups.

+         if not options.test:

+             session.multicall = True

+         for group in gaddlist:

+             if not options.test:

+                 session.groupListAdd(dsttag['name'], group['name'],

+                                      force=options.force)

+             for pkg in group['packagelist']:

+                 if not options.test:

+                     session.groupPackageListAdd(dsttag['name'], group['name'],

+                                                 pkg['package'],

+                                                 force=options.force)

+                 chggrplist.append(('[new]', pkg['package'], group['name']))

+         if not options.test:

+             _batch_multicall(session, options.batch)

+         # ADD group pkgs.

+         if not options.test:

+             session.multicall = True

+         for group in grpchanges:

+             for pkg in grpchanges[group]['adds']:

+                 chggrplist.append(('[new]', pkg, group))

+                 if not options.test:

+                     session.groupPackageListAdd(dsttag['name'], group, pkg,

+                                                 force=options.force)

+         if not options.test:

+             _batch_multicall(session, options.batch)

+         # DEL builds.

+         if not options.test:

+             session.multicall = True

+         for build in bdellist:

+             # dont delete an inherited build.

+             if build['tag_name'] == dsttag['name']:

+                 build['name'] = build[

+                     'package_name']  # add missing 'name' field.

+                 chgbldlist.append(

+                     ('[del]', build['package_name'], build['nvr'],

+                      koji.BUILD_STATES[build['state']],

+                      build['owner_name'], build['tag_name']))

+                 # go on del builds from new tag.

+                 if not options.test:

+                     session.untagBuildBypass(dsttag['name'], build,

+                                              force=options.force)

+         if not options.test:

+             _batch_multicall(session, options.batch)

+         # DEL packages.

+         ninhrtpdellist = []

+         inhrtpdellist = []

+         for pkg in pdellist:

+             if pkg['tag_name'] == dsttag['name']:

+                 ninhrtpdellist.append(pkg)

+             else:

+                 inhrtpdellist.append(pkg)

+         session.multicall = True

+         # delete only non-inherited packages.

+         for pkg in ninhrtpdellist:

+             # check if package have owned builds inside.

+             session.listTagged(dsttag['name'], package=pkg['package_name'],

+                                inherit=False)

+         bump_builds = _batch_multicall(session, options.batch)

+         if not options.test:

+             session.multicall = True

+         for pkg, [builds] in zip(ninhrtpdellist, bump_builds):

+             # remove all its builds first if there are any.

+             for build in builds:

+                 build['name'] = build[

+                     'package_name']  # add missing 'name' field.

+                 chgbldlist.append(

+                     ('[del]', build['package_name'], build['nvr'],

+                      koji.BUILD_STATES[build['state']],

+                      build['owner_name'], build['tag_name']))

+                 # so delete latest build(s) from new tag.

+                 if not options.test:

+                     session.untagBuildBypass(dsttag['name'], build,

+                                              force=options.force)

+             # now safe to remove package itselfm since we resolved its builds.

+             chgpkglist.append(('[del]', pkg['package_name'], pkg['blocked'],

+                                pkg['owner_name'], pkg['tag_name']))

+             if not options.test:

+                 session.packageListRemove(dsttag['name'], pkg['package_name'],

+                                           force=False)

+         # mark as blocked inherited packages.

+         for pkg in inhrtpdellist:

+             chgpkglist.append(('[blk]', pkg['package_name'], pkg['blocked'],

+                                pkg['owner_name'], pkg['tag_name']))

+             if not options.test:

+                 session.packageListBlock(dsttag['name'], pkg['package_name'])

+         if not options.test:

+             _batch_multicall(session, options.batch)

+         # DEL groups.

+         if not options.test:

+             session.multicall = True

+         for group in gdellist:

+             # Only delete a group that isn't inherited

+             if group['tag_id'] == dsttag['id']:

+                 if not options.test:

+                     session.groupListRemove(dsttag['name'], group['name'],

+                                             force=options.force)

+                 for pkg in group['packagelist']:

+                     chggrplist.append(('[del]', pkg['package'], group['name']))

+             # mark as blocked inherited groups.

+             else:

+                 if not options.test:

+                     session.groupListBlock(dsttag['name'], group['name'])

+                 for pkg in group['packagelist']:

+                     chggrplist.append(('[blk]', pkg['package'], group['name']))

+         if not options.test:

+             _batch_multicall(session, options.batch)

+         # DEL group pkgs.

+         if not options.test:

+             session.multicall = True

+         for group in grpchanges:

+             for pkg in grpchanges[group]['dels']:

+                 # Only delete a group that isn't inherited

+                 if not grpchanges[group]['inherited']:

+                     chggrplist.append(('[del]', pkg, group))

+                     if not options.test:

+                         session.groupPackageListRemove(dsttag['name'], group,

+                                                        pkg,

+                                                        force=options.force)

+                 else:

+                     chggrplist.append(('[blk]', pkg, group))

+                     if not options.test:

+                         session.groupPackageListBlock(dsttag['name'], group,

+                                                       pkg)

+         if not options.test:

+             _batch_multicall(session, options.batch)

+     # print final list of actions.

+     if options.verbose:

+         pfmt = '    %-7s %-28s %-10s %-10s %-10s\n'

+         bfmt = '    %-7s %-28s %-40s %-10s %-10s %-10s\n'

+         gfmt = '    %-7s %-28s %-28s\n'

+         sys.stdout.write('\nList of changes:\n\n')

+         sys.stdout.write(

+             pfmt % ('Action', 'Package', 'Blocked', 'Owner', 'From Tag'))

+         sys.stdout.write(

+             pfmt % ('-' * 7, '-' * 28, '-' * 10, '-' * 10, '-' * 10))

+         for changes in chgpkglist:

+             sys.stdout.write(pfmt % changes)

+         sys.stdout.write('\n')

+         sys.stdout.write(bfmt % (

+             'Action', 'From/To Package', 'Latest Build(s)', 'State', 'Owner',

+             'From Tag'))

+         sys.stdout.write(

+             bfmt % ('-' * 7, '-' * 28, '-' * 40, '-' * 10, '-' * 10, '-' * 10))

+         for changes in chgbldlist:

+             sys.stdout.write(bfmt % changes)

+         sys.stdout.write('\n')

+         sys.stdout.write(gfmt % ('Action', 'Package', 'Group'))

+         sys.stdout.write(gfmt % ('-' * 7, '-' * 28, '-' * 28))

+         for changes in chggrplist:

+             sys.stdout.write(gfmt % changes)

+ 

+ 

+ def _batch_multicall(session, size):

+     if not session.multicall:

+         raise koji.GenericError(

+             'ClientSession.multicall must be set to True before calling multiCall()')

+     session.multicall = False

+     if len(session._calls) == 0:

+         return []

+     callgrp = [session._calls[i:i + size] for i in

+                xrange(0, len(session._calls), size)]

+     session._calls = []

+     ret = []

+     for c in callgrp:

+         ret.extend(session._callMethod('multiCall', (c,), {}))

+     return ret

+ 

+ 

+ if __name__ == '__main__':

+     handle_batch_clone_tag()

add a --batch parameter to original clone-tag command to have a workaround for huge data, in order to avoid request size to reach MaxRequestLength

Why separate script? I would like to see this in koji directly.

Why separate script? I would like to see this in koji directly.

We might not be able to wait for brew new release to include this feature, so I temporarily filed this one.
Let me combine batch option into multiCall() in koji

Pull-Request has been closed by julian8628

5 years ago
Metadata