#3308 server-side clonetag
Merged a month ago by tkopecek. Opened 4 months ago by tkopecek.
tkopecek/koji issue3307  into  master

file modified
+39 -468
@@ -15,7 +15,6 @@ 

  import traceback

  from datetime import datetime

  from dateutil.tz import tzutc

- from collections import OrderedDict, defaultdict

  from optparse import SUPPRESS_HELP, OptionParser

  

  import six
@@ -3674,17 +3673,6 @@ 

          error()

  

  

- def _multicall_with_check(session, batch_size):

-     """Helper for running multicall inside handle_clone_tag"""

-     err = False

-     for r in session.multiCall(batch=batch_size):

-         if isinstance(r, dict):

-             warn(r['faultString'])

-             err = True

-     if err:

-         error('Errors during the last call. Target tag could be inconsistent.')

- 

- 

  def handle_clone_tag(goptions, session, args):

      "[admin] Duplicate the contents of one tag onto another tag"

      usage = "usage: %prog clone-tag [options] <src-tag> <dst-tag>"
@@ -3692,10 +3680,12 @@ 

      parser = OptionParser(usage=get_usage_str(usage))

      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('--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('--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',
@@ -3704,16 +3694,20 @@ 

                        help="Include all builds inherited into the source tag into the dest tag")

      parser.add_option('--ts', type='int', metavar="TIMESTAMP",

                        help='Clone tag at last event before specific timestamp')

-     parser.add_option('--no-delete', action='store_false', dest="delete", default=True,

+     parser.add_option('--no-delete', action='store_false', dest="delete",

+                       default=True,

                        help="Don't delete any existing content in dest tag.")

-     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('--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=SUPPRESS_HELP)

      parser.add_option("--notify", action="store_true", default=False,

                        help='Send tagging/untagging notifications')

      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("-n", "--test", action="store_true", help=SUPPRESS_HELP)

      parser.add_option("--batch", type='int', default=100, metavar='SIZE',

                        help="batch size of multicalls [0 to disable, default: %default]")

      (options, args) = parser.parse_args(args)
@@ -3739,6 +3733,9 @@ 

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

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

  

+     if options.builds and not options.pkgs:

+         parser.error("--builds can't be used without also specifying --pkgs")

+ 

      # store tags.

      try:

          srctag = session.getBuildConfig(args[0], event=event.get('id'))
@@ -3752,456 +3749,30 @@ 

          parser.error("Error: You are attempting to clone from or to a tag which is locked.\n"

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

  

-     # init debug lists.

-     chgpkglist = []

-     chgbldlist = []

-     chggrplist = []

-     # case of brand new dst-tag.

-     if not dsttag:

-         # create a new tag, copy srctag header.

-         if not options.test:

-             if options.config:

-                 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'],

-                                   extra=srctag['extra'])

-             else:

-                 session.createTag(args[1], parent=None)

-             # store the new tag, need its assigned id.

-             newtag = session.getTag(args[1], strict=True)

-         # 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:

-                 _multicall_with_check(session, options.batch)

-         if options.builds:

-             # get --all latest builds from src tag

-             builds = reversed(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:

-                 # add missing 'name' field.

-                 build['name'] = build['package_name']

-                 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,

-                                            notify=options.notify)

-             if not options.test:

-                 _multicall_with_check(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:

-                 _multicall_with_check(session, options.batch)

-     # case of existing dst-tag.

+     if options.test:

+         parser.error("server-side operation, test output is no longer available")

+ 

      if dsttag:

-         if options.config and not options.test:

-             if dsttag['extra']:

-                 session.editTag2(dsttag['id'], remove_extra=list(dsttag['extra'].keys()))

-             session.editTag2(dsttag['id'], parent=None, arches=srctag['arches'],

-                              perm=srctag['perm_id'],

-                              locked=srctag['locked'],

-                              maven_support=srctag['maven_support'],

-                              maven_include_all=srctag['maven_include_all'],

-                              extra=srctag['extra'])

-             dsttag = session.getTag(dsttag['id'], strict=True)

- 

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

-         srcpkgs = {}

-         dstpkgs = {}

-         srcbldsbypkg = defaultdict(OrderedDict)

-         dstbldsbypkg = defaultdict(OrderedDict)

-         srcgroups = OrderedDict()

-         dstgroups = OrderedDict()

-         # we use OrderedDict so that these indexes preserve the order given to us

-         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:

-             # listTagged orders builds latest-first

-             # so reversing that gives us oldest-first

-             for build in reversed(session.listTagged(srctag['id'],

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

-                                                      inherit=options.inherit_builds,

-                                                      latest=options.latest_only)):

-                 srcbldsbypkg[build['package_name']][build['nvr']] = build

-             # get builds in dsttag without inheritance.

-             # latest=False to get all builds even when latest_only = True,

-             # so that only the *latest* build per tag will live in.

-             for build in reversed(session.listTagged(dsttag['id'],

-                                                      inherit=False,

-                                                      latest=False)):

-                 dstbldsbypkg[build['package_name']][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

-         bdellist = []  # list containing new builds to be removed from dst tag

-         if options.delete:

-             # remove builds for packages that are absent from src tag

-             for (pkg, dstblds) in six.iteritems(dstbldsbypkg):

-                 if pkg not in srcbldsbypkg:

-                     bdellist.extend(dstblds.values())

-         # add and/or remove builds from dst to match src contents and order

-         for (pkg, srcblds) in six.iteritems(srcbldsbypkg):

-             dstblds = dstbldsbypkg[pkg]

-             ablds = []

-             dblds = []

-             # firstly, deal with extra builds in dst

-             removed_nvrs = set(dstblds.keys()) - set(srcblds.keys())

-             bld_order = srcblds.copy()

-             if options.delete:

-                 # mark the extra builds for deletion

-                 dnvrs = []

-                 for (dstnvr, dstbld) in six.iteritems(dstblds):

-                     if dstnvr in removed_nvrs:

-                         dnvrs.append(dstnvr)

-                         dblds.append(dstbld)

-                 # we also remove them from dstblds now so that they do not

-                 # interfere with the order comparison below

-                 for dnvr in dnvrs:

-                     del dstblds[dnvr]

-             else:

-                 # in the no-delete case, the extra builds should be forced

-                 # to last in the tag

-                 bld_order = OrderedDict()

-                 for (dstnvr, dstbld) in six.iteritems(dstblds):

-                     if dstnvr in removed_nvrs:

-                         bld_order[dstnvr] = dstbld

-                 for (nvr, srcbld) in six.iteritems(srcblds):

-                     bld_order[nvr] = srcbld

-             # secondly, add builds from src tag and adjust the order

-             for (nvr, srcbld) in six.iteritems(bld_order):

-                 found = False

-                 out_of_order = []

-                 # note that dstblds is trimmed as we go, so we are only

-                 # considering the tail corresponding to where we are at

-                 # in the srcblds loop

-                 for (dstnvr, dstbld) in six.iteritems(dstblds):

-                     if nvr == dstnvr:

-                         found = True

-                         break

-                     else:

-                         out_of_order.append(dstnvr)

-                         dblds.append(dstbld)

-                 for dnvr in out_of_order:

-                     del dstblds[dnvr]

-                     # these will be re-added in the proper order later

-                 if found:

-                     # remove it for next pass so we stay aligned with outer

-                     # loop

-                     del dstblds[nvr]

-                 else:

-                     # missing from dst, so we need to add it

-                     ablds.append(srcbld)

-             baddlist.extend(ablds)

-             bdellist.extend(dblds)

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

-         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 = OrderedDict()  # dict of changes to make in shared groups

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

-             if grpname in dstgroups:

-                 dstgroup = dstgroups[grpname]

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

-                 # Store whether group is inherited or not

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

-                 if dstgroup['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 pkg not in dstgrppkglist:

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

-                 for pkg in dstgrppkglist:

-                     if pkg not 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:

-             _multicall_with_check(session, options.batch)

-         # DEL builds. To keep the order we should untag builds at first

-         if not options.test:

-             session.multicall = True

-         for build in bdellist:

-             # don't delete an inherited build.

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

-                 # add missing 'name' field

-                 build['name'] = build['package_name']

-                 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,

-                                              notify=options.notify)

-         if not options.test:

-             _multicall_with_check(session, options.batch)

-         # ADD builds.

-         if not options.test:

-             session.multicall = True

-         for build in baddlist:

-             # add missing 'name' field.

-             build['name'] = build['package_name']

-             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,

-                                        notify=options.notify)

-         if not options.test:

-             _multicall_with_check(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:

-             _multicall_with_check(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:

-             _multicall_with_check(session, options.batch)

-         if options.delete:

-             # 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 = session.multiCall(batch=options.batch)

-             if not options.test:

-                 session.multicall = True

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

-                 if isinstance(builds, dict):

-                     error(builds['faultString'])

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

-                 for build in builds:

-                     # add missing 'name' field.

-                     build['name'] = build['package_name']

-                     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,

-                                                  notify=options.notify)

-                 # now safe to remove package itself 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:

-                 _multicall_with_check(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:

-                 _multicall_with_check(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:

-                 _multicall_with_check(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', '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)

+         session.snapshotTagModify(srctag['id'], args[1],

+                                   config=options.config,

+                                   pkgs=options.pkgs,

+                                   builds=options.builds,

+                                   groups=options.groups,

+                                   latest_only=options.latest_only,

+                                   inherit_builds=options.inherit_builds,

+                                   remove=options.delete,

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

+                                   force=options.force)

+     else:

+         session.snapshotTag(srctag['id'], args[1],

+                             config=options.config,

+                             pkgs=options.pkgs,

+                             builds=options.builds,

+                             groups=options.groups,

+                             latest_only=options.latest_only,

+                             inherit_builds=options.inherit_builds,

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

+                             force=options.force)

  

  

  def handle_add_target(goptions, session, args):

file modified
+363 -1
@@ -48,6 +48,7 @@ 

  from urllib.parse import parse_qs

  import xmlrpc.client

  import zipfile

+ from collections import defaultdict, OrderedDict

  

  import rpm

  from psycopg2._psycopg import IntegrityError
@@ -2051,7 +2052,7 @@ 

  

  

  def _grp_pkg_remove(taginfo, grpinfo, pkg_name):

-     """grp_pkg_remove without permssion checks"""

+     """grp_pkg_remove without permission checks"""

      tag_id = get_tag_id(taginfo, strict=True)

      grp_id = get_group_id(grpinfo, strict=True)

      update = UpdateProcessor('group_package_listing', values=locals(),
@@ -10559,6 +10560,14 @@ 

      koji.plugin.run_callbacks('postImport', type='image', image=imgdata,

                                build=build_info, fullpath=fullpath)

  

+ 

+ def _delete_event_id():

+     """Helper function to bump event"""

+     try:

+         del context.event_id

+     except AttributeError:

+         pass

+ 

  #

  # XMLRPC Methods

  #
@@ -11544,6 +11553,359 @@ 

          if notify:

              tag_notification(True, None, tag, build, context.session.user_id)

  

+     def massTag(self, tag, builds):

+         """

+         Substitute for tagBuildBypass - this call ignores every check, so special

+         'tag' permission is needed. It bypass all tag access checks and policies.

+         On error it will raise concrete exception

+ 

+         :param builds: list of build NVRs

+         :type builds: [str]

+ 

+         :returns: None

+         """

+ 

+         context.session.assertPerm('tag')

+         tag = get_tag(tag, strict=True)

+         user = get_user(context.session.user_id, strict=True)

+ 

+         logger.debug("Tagging %d builds to %s on behalf of %s",

+                      len(builds), tag['name'], user['name'])

+         start = time.time()

+         for build in builds:

+             binfo = get_build(build, strict=True)

+             _direct_tag_build(tag, binfo, user, force=True)

+             # ensure tagging order by updating event id

+             _delete_event_id()

+         length = time.time() - start

+         logger.debug("Tagged %d builds to %s in %.2f seconds", len(builds), tag['name'], length)

+ 

+     def snapshotTag(self, src, dst, config=True, pkgs=True, builds=True, groups=True,

+                     latest_only=True, inherit_builds=True, event=None, force=False):

+         """

+         Copy the tag and its current (or event) contents to new one. It doesn't copy inheritance.

+         Suitable for creating snapshots of tags. External repos are not linked.

+         Destination tag must not exist. For updating existing tags use snapshotTagModify

+ 

+         Calling user needs to have 'admin' or 'tag' permission.

+ 

+         :param [inst|str] src: source tag

+         :param [int|str] dst: destination tag

+         :param [bool] config: copy basic config (arches, permission, lock, maven_support,

+                               maven_include_all, extra)

+         :param [bool] pkgs: copy package lists

+         :param [bool] builds: copy tagged builds

+         :param [bool] latest_only: copy only latest builds instead of all

+         :param [bool] inherit_builds: use inherited builds, not only explicitly tagged

+         :param [int] event: copy state of tag in given event id

+         :param [bool] force: use force for all underlying operations

+         :returns: None

+         """

+         context.session.assertPerm('tag')

+         if builds and not pkgs:

+             raise koji.ParameterError("builds can't be used without pkgs in snapshotTag")

+ 

+         if get_tag(dst):

+             raise koji.GenericError("Target tag already exists")

+ 

+         src = get_tag(src, event=event, strict=True)

+ 

+         if src['locked'] and not force:

+             raise koji.GenericError("Source tag is locked, use force to copy")

+ 

+         if config:

+             dsttag = _create_tag(

+                 dst,

+                 parent=None,  # should clone parent?

+                 arches=src['arches'],

+                 perm=src['perm_id'],

+                 locked=src['locked'],

+                 maven_support=src['maven_support'],

+                 maven_include_all=src['maven_include_all'],

+                 extra=src['extra'])

+         else:

+             dsttag = _create_tag(dst, parent=None)

+         # all update operations will reset event_id, so we've clear order of operations

+         _delete_event_id()

+         dst = get_tag(dsttag, strict=True)

+ 

+         logger.debug("Cloning %s to %s", src['name'], dst['name'])

+ 

+         # package lists

+         if pkgs:

+             logger.debug("Cloning package list to %s", dst['name'])

+             start = time.time()

+             for pkg in self.listPackages(tagID=src['id'], event=event, inherited=True):

+                 _direct_pkglist_add(

+                     taginfo=dst['id'],

+                     pkginfo=pkg['package_name'],

+                     owner=pkg['owner_name'],

+                     block=pkg['blocked'],

+                     extra_arches=pkg['extra_arches'],

+                     force=True,

+                     update=False)

+                 _delete_event_id()

+             length = time.time() - start

+             logger.debug("Cloned packages to %s in %.2f seconds", dst['name'], length)

+ 

+         # builds

+         if builds:

+             builds = readTaggedBuilds(tag=src['id'], inherit=inherit_builds,

+                                       event=event, latest=latest_only)

+             self.massTag(dst['id'], list(reversed(builds)))

+ 

+         # groups

+         if groups:

+             logger.debug("Cloning groups to %s", dst['name'])

+             start = time.time()

+             for group in readTagGroups(tag=src['id'], event=event):

+                 _grplist_add(dst['id'], group['name'], block=group['blocked'], force=True)

+                 _delete_event_id()

+                 for pkg in group['packagelist']:

+                     _grp_pkg_add(dst['id'], group['name'], pkg['package'],

+                                  block=pkg['blocked'], force=True)

+                     _delete_event_id()

+                 for group_req in group['grouplist']:

+                     _grp_req_add(dst['id'], group['name'], group_req['name'],

+                                  block=group_req['blocked'], force=True)

+                     _delete_event_id()

+             length = time.time() - start

+             logger.debug("Cloned groups to %s in %.2f seconds", dst['name'], length)

+             _delete_event_id()

+ 

+     def snapshotTagModify(self, src, dst, config=True, pkgs=True, builds=True, groups=True,

+                           latest_only=True, inherit_builds=True, event=None, force=False,

+                           remove=False):

+         """

+         Copy the tag and its current (or event) contents to existing one. It doesn't copy

+         inheritance. Suitable for creating snapshots of tags. External repos are not linked.

+         Destination tag must not exist. For creating new snapshots use snapshotTag

+ 

+         Calling user needs to have 'admin' or 'tag' permission.

+ 

+         :param [int|str] src: source tag

+         :param [int|str] dst: destination tag

+         :param bool config: copy basic config (arches, permission, lock, maven_support,

+                             maven_include_all, extra)

+         :param bool pkgs: copy package lists

+         :param bool builds: copy tagged builds

+         :param bool latest_only: copy only latest builds instead of all

+         :param bool inherit_builds: use inherited builds, not only explicitly tagged

+         :param int event: copy state of tag in given event id

+         :param bool force: use force for all underlying operations

+         :param remove: remove builds/groups/

+         :returns: None

+         """

+ 

+         context.session.assertPerm('tag')

+ 

+         if builds and not pkgs:

+             # It is necessarily not true (current pkgs can already cover all new builds),

+             # but it seems to be more consistent to require it anyway.

+             raise koji.ParameterError("builds can't be used without pkgs in snapshotTag")

+ 

+         src = get_tag(src, event=event, strict=True)

+         dst = get_tag(dst, strict=True)

+ 

+         if (src['locked'] or dst['locked']) and not force:

+             raise koji.GenericError("Source or destination tag is locked, use force to copy")

+ 

+         user = get_user(context.session.user_id, strict=True)

+ 

+         if config:

+             if dst['extra']:

+                 remove_extra = list(set(dst['extra'].keys()) - set(src['extra'].keys()))

+             else:

+                 remove_extra = []

+             edit_tag(dst['id'], parent=None, arches=src['arches'],

+                      perm=src['perm_id'], locked=src['locked'],

+                      maven_support=src['maven_support'],

+                      maven_include_all=src['maven_include_all'],

+                      extra=src['extra'],

+                      remove_extra=remove_extra)

+             _delete_event_id()

+             dst = get_tag(dst['id'], strict=True)

+ 

+         if pkgs:

+             srcpkgs = {}

+             dstpkgs = {}

+             for pkg in self.listPackages(tagID=src['id'], event=event, inherited=True):

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

+             for pkg in self.listPackages(tagID=dst['id'], inherited=True):

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

+ 

+             for pkg_name in set(dstpkgs.keys()) - set(srcpkgs.keys()):

+                 pkg = dstpkgs[pkg_name]

+                 _direct_pkglist_add(dst,

+                                     pkg_name,

+                                     owner=pkg['owner_name'],

+                                     block=True,

+                                     force=True,

+                                     update=True,

+                                     extra_arches=pkg['extra_arches'])

+                 _delete_event_id()

+ 

+             for pkg_name in set(srcpkgs.keys()) - set(dstpkgs.keys()):

+                 pkg = srcpkgs[pkg_name]

+                 _direct_pkglist_add(dst,

+                                     pkg_name,

+                                     owner=pkg['owner_name'],

+                                     block=pkg['blocked'],

+                                     update=False,

+                                     force=True,

+                                     extra_arches=pkg['extra_arches'])

+                 _delete_event_id()

+ 

+         if builds:

+             srcbldsbypkg = defaultdict(OrderedDict)

+             dstbldsbypkg = defaultdict(OrderedDict)

+             # listTagged orders builds latest-first

+             # so reversing that gives us oldest-first

+             for build in reversed(readTaggedBuilds(src['id'], event=event, inherit=inherit_builds,

+                                                    latest=latest_only)):

+                 srcbldsbypkg[build['package_name']][build['nvr']] = build

+             # get builds in dst without inheritance.

+             # latest=False to get all builds even when latest_only = True,

+             # so that only the *latest* build per tag will live in.

+             for build in reversed(readTaggedBuilds(dst['id'], inherit=False, latest=False)):

+                 dstbldsbypkg[build['package_name']][build['nvr']] = build

+ 

+             if remove:

+                 for (pkg, dstblds) in dstbldsbypkg.items():

+                     if pkg not in srcbldsbypkg:

+                         # untag all builds for packages which are not in dst

+                         for build in dstblds:

+                             # don't untag inherited builds

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

+                                 _direct_untag_build(dst, build, user, force=force)

+                                 _delete_event_id()

+ 

+             # add and/or remove builds from dst to match src contents and order

+             for (pkg, srcblds) in srcbldsbypkg.items():

+                 dstblds = dstbldsbypkg[pkg]

+                 # firstly, deal with extra builds in dst

+                 removed_nvrs = set(dstblds.keys()) - set(srcblds.keys())

+                 bld_order = srcblds.copy()

+                 if remove:

+                     # mark the extra builds for deletion

+                     dnvrs = []

+                     for (dstnvr, dstbld) in dstblds.items():

+                         if dstnvr in removed_nvrs:

+                             dnvrs.append(dstnvr)

+                             if dstbld['tag_name'] == dst['name']:

+                                 _untag_build(dst['name'], dstbld, force=force)

+                                 _delete_event_id()

+                     # we also remove them from dstblds now so that they do not

+                     # interfere with the order comparison below

+                     for dnvr in dnvrs:

+                         del dstblds[dnvr]

+                 else:

+                     # in the no-removal case, the extra builds should be forced

+                     # to last in the tag

+                     bld_order = OrderedDict()

+                     for (dstnvr, dstbld) in dstblds.items():

+                         if dstnvr in removed_nvrs:

+                             bld_order[dstnvr] = dstbld

+                     for (nvr, srcbld) in srcblds.items():

+                         bld_order[nvr] = srcbld

+                 # secondly, add builds from src tag and adjust the order

+                 for (nvr, srcbld) in bld_order.items():

+                     found = False

+                     out_of_order = []

+                     # note that dstblds is trimmed as we go, so we are only

+                     # considering the tail corresponding to where we are at

+                     # in the srcblds loop

+                     for (dstnvr, dstbld) in dstblds.items():

+                         if nvr == dstnvr:

+                             found = True

+                             break

+                         else:

+                             out_of_order.append(dstnvr)

+                             if dstbld['tag_name'] == dst['name']:

+                                 _untag_build(dst['name'], dstbld, force=force)

+                                 _delete_event_id()

+                     for dnvr in out_of_order:

+                         del dstblds[dnvr]

+                         # these will be re-added in the proper order later

+                     if found:

+                         # remove it for next pass so we stay aligned with outer

+                         # loop

+                         del dstblds[nvr]

+                     else:

+                         # missing from dst, so we need to add it

+                         _direct_tag_build(dst, srcbld, user, force=force)

+                         _delete_event_id()

+ 

+         if groups:

+             srcgroups = OrderedDict()

+             dstgroups = OrderedDict()

+             for group in readTagGroups(src['name'], event=event, incl_blocked=True):

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

+             for group in readTagGroups(dst['name'], incl_blocked=True):

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

+ 

+             for (grpname, group) in srcgroups.items():

+                 if grpname not in dstgroups or group['blocked'] != dstgroups[grpname]['blocked']:

+                     _grplist_add(dst['id'], group['name'], block=group['blocked'], force=force)

+                     _delete_event_id()

+ 

+             if remove:

+                 for (grpname, group) in dstgroups.items():

+                     if grpname not in srcgroups:

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

+                             # not inherited

+                             _grplist_remove(dst['id'], group['id'], force=force)

+                         else:

+                             # block inherited groups

+                             _grplist_add(dst['id'], group['name'], block=True, force=force)

+                         _delete_event_id()

+ 

+             for (grpname, group) in srcgroups.items():

+                 if grpname in dstgroups:

+                     srcgrppkglist = {}

+                     dstgrppkglist = {}

+                     for pkg in group['packagelist']:

+                         srcgrppkglist[pkg['package']] = pkg

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

+                         dstgrppkglist[pkg['package']] = pkg

+                     for pkg in srcgrppkglist.values():

+                         if pkg['package'] not in dstgrppkglist:

+                             _grp_pkg_add(dst['name'], grpname, pkg['package'],

+                                          force=force, block=False)

+                             _delete_event_id()

+                     srcgrpreqlist = {}

+                     dstgrpreqlist = {}

+                     for grp in group['grouplist']:

+                         srcgrpreqlist[grp['name']] = grp

+                     for grp in dstgroups[grpname]['grouplist']:

+                         dstgrpreqlist[grp['name']] = grp

+                     for grp in srcgrpreqlist.values():

+                         if grp['name'] not in dstgrpreqlist:

+                             _grp_req_add(dst['name'], grpname, grp['name'],

+                                          force=force, block=grp['blocked'])

+                             _delete_event_id()

+                     if remove:

+                         for pkgname, pkg in dstgrppkglist.items():

+                             if pkg['blocked']:

+                                 continue

+                             if srcgrppkglist.get(pkgname, {}).get('blocked'):

+                                 _grp_pkg_add(dst['id'], grpname, pkg['package'],

+                                              block=True, force=force)

+                                 _delete_event_id()

+                             elif pkgname not in srcgrppkglist and pkg['tag_id'] == dst['id']:

+                                 _grp_pkg_remove(dst['name'], grpname, pkg['package'], force=force)

+                                 _delete_event_id()

+                         for grp in dstgrpreqlist.values():

+                             if grp['blocked']:

+                                 continue

+                             if grp['name'] not in srcgrpreqlist:

+                                 if grp['group_id'] == dst['id']:

+                                     _grp_req_remove(dst['name'], grpname, grp['name'], force=force)

+                                 else:

+                                     _grp_req_add(dst['name'], grpname, grp['name'],

+                                                  block=True, force=force)

+                                 _delete_event_id()

+ 

      def moveBuild(self, tag1, tag2, build, force=False):

          """Move a build from tag1 to tag2

  

file modified
+13 -227
@@ -200,81 +200,12 @@ 

          self.session.assert_has_calls([call.hasPerm('admin'),

                                         call.getBuildConfig('src-tag', event=None),

                                         call.getTag('dst-tag'),

-                                        call.createTag('dst-tag', arches='arch1 arch2',

-                                                       locked=False, maven_include_all=True,

-                                                       maven_support=False, parent=None, perm=1,

-                                                       extra={}),

-                                        call.getTag('dst-tag', strict=True),

-                                        call.listPackages(event=None, inherited=True, tagID=1),

-                                        call.packageListAdd('dst-tag', 'apkg', block=False,

-                                                            extra_arches='arch4', owner='userA'),

-                                        call.packageListAdd('dst-tag', 'pkg1', block=False,

-                                                            extra_arches=None, owner='userA'),

-                                        call.packageListAdd('dst-tag', 'pkg2', block=True,

-                                                            extra_arches='arch3 arch4',

-                                                            owner='userB'),

-                                        call.multiCall(batch=100),

-                                        call.listTagged(1, event=None, inherit=None, latest=None),

-                                        call.tagBuildBypass('dst-tag', {

-                                            'owner_name': 'b_owner',

-                                            'nvr': 'pkg2-1.0-1',

-                                            'package_name': 'pkg2', 'state': 2,

-                                            'tag_name': 'src-tag-p',

-                                            'name': 'pkg2'}, force=None, notify=False),

-                                        call.tagBuildBypass('dst-tag', {

-                                            'owner_name': 'b_owner',

-                                            'nvr': 'pkg1-1.0-1',

-                                            'package_name': 'pkg1', 'state': 1,

-                                            'tag_name': 'src-tag',

-                                            'name': 'pkg1'}, force=None, notify=False),

-                                        call.tagBuildBypass('dst-tag', {

-                                            'owner_name': 'b_owner',

-                                            'nvr': 'pkg1-1.0-2',

-                                            'package_name': 'pkg1', 'state': 1,

-                                            'tag_name': 'src-tag',

-                                            'name': 'pkg1'}, force=None, notify=False),

-                                        call.tagBuildBypass('dst-tag', {

-                                            'owner_name': 'b_owner',

-                                            'nvr': 'pkg1-1.1-2',

-                                            'package_name': 'pkg1', 'state': 1,

-                                            'tag_name': 'src-tag',

-                                            'name': 'pkg1'}, force=None, notify=False),

-                                        call.multiCall(batch=100),

-                                        call.getTagGroups('src-tag', event=None),

-                                        call.groupListAdd('dst-tag', 'group1'),

-                                        call.groupPackageListAdd('dst-tag', 'group1', 'pkg1',

-                                                                 block=False),

-                                        call.groupPackageListAdd('dst-tag', 'group1', 'pkg2',

-                                                                 block=False),

-                                        call.groupListAdd('dst-tag', 'group2'),

-                                        call.groupPackageListAdd('dst-tag', 'group2', 'apkg',

-                                                                 block=False),

-                                        call.groupPackageListAdd('dst-tag', 'group2', 'bpkg',

-                                                                 block=False),

-                                        call.multiCall(batch=100)])

-         self.assert_console_message(stdout, """

- List of changes:

- 

-     Action  Package                      Blocked    Owner      From Tag  

-     ------- ---------------------------- ---------- ---------- ----------

-     [new]   apkg                         False      userA      src-tag-p 

-     [new]   pkg1                         False      userA      src-tag   

-     [new]   pkg2                         True       userB      src-tag-p 

- 

-     Action  From/To Package              Build(s)                                 State      Owner      From Tag  

-     ------- ---------------------------- ---------------------------------------- ---------- ---------- ----------

-     [new]   pkg2                         pkg2-1.0-1                               DELETED    b_owner    src-tag-p 

-     [new]   pkg1                         pkg1-1.0-1                               COMPLETE   b_owner    src-tag   

-     [new]   pkg1                         pkg1-1.0-2                               COMPLETE   b_owner    src-tag   

-     [new]   pkg1                         pkg1-1.1-2                               COMPLETE   b_owner    src-tag   

- 

-     Action  Package                      Group                       

-     ------- ---------------------------- ----------------------------

-     [new]   pkg1                         group1                      

-     [new]   pkg2                         group1                      

-     [new]   apkg                         group2                      

-     [new]   bpkg                         group2                      

- """)

+                                        call.snapshotTag(1, 'dst-tag',

+                                                         builds=True, config=True, event=None,

+                                                         force=None, groups=True,

+                                                         inherit_builds=None, latest_only=None,

+                                                         pkgs=True),

+                                        ])

  

      @mock.patch('sys.stdout', new_callable=six.StringIO)

      def test_handle_clone_tag_existing_dsttag(self, stdout):
@@ -460,114 +391,13 @@ 

          self.session.assert_has_calls([call.hasPerm('admin'),

                                         call.getBuildConfig('src-tag', event=None),

                                         call.getTag('dst-tag'),

-                                        call.editTag2(2, arches='arch1 arch2',

-                                                      extra={}, locked=False,

-                                                      maven_include_all=True,

-                                                      maven_support=False,

-                                                      parent=None, perm=1),

-                                        call.getTag(2, strict=True),

-                                        call.listPackages(event=None, inherited=True, tagID=1),

-                                        call.listPackages(inherited=True, tagID=2),

-                                        call.listTagged(1, event=None, inherit=None, latest=None),

-                                        call.listTagged(2, inherit=False, latest=False),

-                                        call.getTagGroups('src-tag', event=None),

-                                        call.getTagGroups('dst-tag'),

-                                        call.packageListAdd('dst-tag', 'pkg2', block=True,

-                                                            extra_arches='arch3 arch4',

-                                                            owner='userB'),

-                                        call.multiCall(batch=100),

-                                        call.untagBuildBypass('dst-tag', {

-                                            'owner_name': 'b_owner',

-                                            'nvr': 'pkg1-2.1-2',

-                                            'package_name': 'pkg1', 'state': 1,

-                                            'tag_name': 'dst-tag',

-                                            'name': 'pkg1'}, force=None, notify=False),

-                                        call.untagBuildBypass('dst-tag', {

-                                            'owner_name': 'b_owner',

-                                            'nvr': 'pkg1-0.1-1',

-                                            'package_name': 'pkg1', 'state': 1,

-                                            'tag_name': 'dst-tag',

-                                            'name': 'pkg1'}, force=None, notify=False),

-                                        call.untagBuildBypass('dst-tag', {

-                                            'owner_name': 'b_owner',

-                                            'nvr': 'pkg3-1.0-1',

-                                            'package_name': 'pkg3', 'state': 1,

-                                            'tag_name': 'dst-tag',

-                                            'name': 'pkg3'}, force=None, notify=False),

-                                        call.multiCall(batch=100),

-                                        call.tagBuildBypass('dst-tag', {

-                                            'owner_name': 'b_owner',

-                                            'nvr': 'pkg1-0.1-1',

-                                            'package_name': 'pkg1', 'state': 1,

-                                            'tag_name': 'src-tag',

-                                            'name': 'pkg1'}, force=None, notify=False),

-                                        call.tagBuildBypass('dst-tag', {

-                                            'owner_name': 'b_owner',

-                                            'nvr': 'pkg1-1.0-2',

-                                            'package_name': 'pkg1', 'state': 1,

-                                            'tag_name': 'src-tag',

-                                            'name': 'pkg1'}, force=None, notify=False),

-                                        call.tagBuildBypass('dst-tag', {

-                                            'owner_name': 'b_owner',

-                                            'nvr': 'pkg1-1.1-2',

-                                            'package_name': 'pkg1', 'state': 1,

-                                            'tag_name': 'src-tag',

-                                            'name': 'pkg1'}, force=None, notify=False),

-                                        call.multiCall(batch=100),

-                                        call.multiCall(batch=100),

-                                        call.groupPackageListAdd('dst-tag', 'group1', 'pkg2',

-                                                                 force=None),

-                                        call.groupPackageListAdd('dst-tag', 'group1', 'pkg3',

-                                                                 force=None),

-                                        call.groupPackageListAdd('dst-tag', 'group1', 'pkg4',

-                                                                 force=None),

-                                        call.groupPackageListAdd('dst-tag', 'group2', 'bpkg',

-                                                                 force=None),

-                                        call.multiCall(batch=100),

-                                        call.multiCall(batch=100),

-                                        call.packageListBlock('dst-tag', 'bpkg'),

-                                        call.packageListBlock('dst-tag', 'cpkg'),

-                                        call.packageListBlock('dst-tag', 'dpkg'),

-                                        call.multiCall(batch=100),

-                                        call.groupListRemove('dst-tag', 'group3', force=None),

-                                        call.groupListBlock('dst-tag', 'group4'),

-                                        call.multiCall(batch=100),

-                                        call.groupPackageListRemove('dst-tag', 'group1', 'pkg5',

-                                                                    force=None),

-                                        call.groupPackageListBlock('dst-tag', 'group2', 'cpkg'),

-                                        call.multiCall(batch=100)])

-         self.assert_console_message(stdout, """

- List of changes:

- 

-     Action  Package                      Blocked    Owner      From Tag  

-     ------- ---------------------------- ---------- ---------- ----------

-     [add]   pkg2                         True       userB      src-tag-p 

-     [blk]   bpkg                         False      userC      src-tag   

-     [blk]   cpkg                         True       userC      src-tag-p 

-     [blk]   dpkg                         True       userC      src-tag   

- 

-     Action  From/To Package              Build(s)                                 State      Owner      From Tag  

-     ------- ---------------------------- ---------------------------------------- ---------- ---------- ----------

-     [del]   pkg1                         pkg1-2.1-2                               COMPLETE   b_owner    dst-tag   

-     [del]   pkg1                         pkg1-0.1-1                               COMPLETE   b_owner    dst-tag   

-     [del]   pkg3                         pkg3-1.0-1                               COMPLETE   b_owner    dst-tag   

-     [add]   pkg1                         pkg1-0.1-1                               COMPLETE   b_owner    src-tag   

-     [add]   pkg1                         pkg1-1.0-2                               COMPLETE   b_owner    src-tag   

-     [add]   pkg1                         pkg1-1.1-2                               COMPLETE   b_owner    src-tag   

- 

-     Action  Package                      Group                       

-     ------- ---------------------------- ----------------------------

-     [new]   pkg2                         group1                      

-     [new]   pkg3                         group1                      

-     [new]   pkg4                         group1                      

-     [new]   bpkg                         group2                      

-     [del]   cpkg                         group3                      

-     [del]   dpkg                         group3                      

-     [blk]   epkg                         group4                      

-     [blk]   fpkg                         group4                      

-     [del]   pkg5                         group1                      

-     [blk]   cpkg                         group2                      

- """)

+                                        call.snapshotTagModify(1, 'dst-tag',

+                                                               builds=True, config=True,

+                                                               event=None, force=None,

+                                                               groups=True, inherit_builds=None,

+                                                               latest_only=None, pkgs=True,

+                                                               remove=True)

+                                        ])

  

      @mock.patch('sys.stdout', new_callable=six.StringIO)

      def test_handle_clone_tag_existing_dsttag_nodelete(self, stdout):
@@ -622,21 +452,6 @@ 

                                              'extra': {}}]

          handle_clone_tag(self.options, self.session, args)

          self.activate_session.assert_called_once()

-         self.assert_console_message(stdout, """

- List of changes:

- 

-     Action  Package                      Blocked    Owner      From Tag  

-     ------- ---------------------------- ---------- ---------- ----------

- 

-     Action  From/To Package              Build(s)                                 State      Owner      From Tag  

-     ------- ---------------------------- ---------------------------------------- ---------- ---------- ----------

-     [add]   pkg                          pkg-0.1-1                                COMPLETE   b_owner    src-tag   

-     [add]   pkg                          pkg-1.0-21                               COMPLETE   b_owner    src-tag   

-     [add]   pkg                          pkg-1.0-23                               COMPLETE   b_owner    src-tag   

- 

-     Action  Package                      Group                       

-     ------- ---------------------------- ----------------------------

- """)

  

      @mock.patch('sys.stdout', new_callable=six.StringIO)

      def test_handle_clone_tag_existing_dsttag_nodelete_1(self, stdout):
@@ -710,20 +525,6 @@ 

              'extra': {}}

          handle_clone_tag(self.options, self.session, args)

          self.activate_session.assert_called_once()

-         self.assert_console_message(stdout, """

- List of changes:

- 

-     Action  Package                      Blocked    Owner      From Tag  

-     ------- ---------------------------- ---------- ---------- ----------

- 

-     Action  From/To Package              Build(s)                                 State      Owner      From Tag  

-     ------- ---------------------------- ---------------------------------------- ---------- ---------- ----------

-     [add]   pkg                          pkg-1.0-21                               COMPLETE   b_owner    src-tag   

-     [add]   pkg                          pkg-1.0-23                               COMPLETE   b_owner    src-tag   

- 

-     Action  Package                      Group                       

-     ------- ---------------------------- ----------------------------

- """)

  

      @mock.patch('sys.stdout', new_callable=six.StringIO)

      def test_handle_clone_tag_existing_dsttag_nodelete_2(self, stdout):
@@ -791,19 +592,6 @@ 

                                                     ]

          handle_clone_tag(self.options, self.session, args)

          self.activate_session.assert_called_once()

-         self.assert_console_message(stdout, """

- List of changes:

- 

-     Action  Package                      Blocked    Owner      From Tag  

-     ------- ---------------------------- ---------- ---------- ----------

- 

-     Action  From/To Package              Build(s)                                 State      Owner      From Tag  

-     ------- ---------------------------- ---------------------------------------- ---------- ---------- ----------

-     [add]   pkg                          pkg-1.0-23                               COMPLETE   b_owner    src-tag   

- 

-     Action  Package                      Group                       

-     ------- ---------------------------- ----------------------------

- """)

  

      def test_handle_clone_tag_help(self):

          self.assert_help(
@@ -826,10 +614,8 @@ 

    --no-delete       Don't delete any existing content in dest tag.

    --event=EVENT     Clone tag at a specific event

    --repo=REPO       Clone tag at a specific repo event

-   -v, --verbose     show changes

    --notify          Send tagging/untagging notifications

    -f, --force       override tag locks if necessary

-   -n, --test        test mode

    --batch=SIZE      batch size of multicalls [0 to disable, default: 100]

  """ % self.progname)

  

@@ -0,0 +1,53 @@ 

+ import mock

+ import unittest

+ import koji

+ import kojihub

+ 

+ 

+ class TestDeleteEventId(unittest.TestCase):

+     @mock.patch('kojihub.context')

+     def test_delete_event_id(self, context):

+         kojihub.context.event_id = 123

+         kojihub._delete_event_id()

+         self.assertFalse(hasattr(context, 'event_id'))

+ 

+     @mock.patch('kojihub.context')

+     def test_delete_event_id_none(self, context):

+         kojihub._delete_event_id()

+         self.assertFalse(hasattr(context, 'event_id'))

+ 

+ 

+ class TestMassTag(unittest.TestCase):

+     def setUp(self):

+         self.get_tag = mock.patch('kojihub.get_tag').start()

+         self.get_build = mock.patch('kojihub.get_build').start()

+         self.get_user = mock.patch('kojihub.get_user').start()

+         self._direct_tag_build = mock.patch('kojihub._direct_tag_build').start()

+         self._delete_event_id = mock.patch('kojihub._delete_event_id').start()

+         self.context = mock.patch('kojihub.context').start()

+         self.context.session.assertPerm = mock.MagicMock()

+         self.hub = kojihub.RootExports()

+ 

+     def tearDown(self):

+         mock.patch.stopall()

+ 

+     def test_no_permission(self):

+         self.context.session.assertPerm.side_effect = koji.ActionNotAllowed

+         with self.assertRaises(koji.ActionNotAllowed):

+             self.hub.massTag('tag', ['n-v-r1'])

+         self.context.session.assertPerm.assert_called_once_with('tag')

+ 

+     def test_non_existent_tag(self):

+         self.hub.massTag('non-existent-tag', ['n-v-r-1', 'n-v-r-2'])

+ 

+     def test_non_existent_build(self):

+         self.hub.massTag('tag', ['non-existent-nvr'])

+ 

+     def test_correct_tagging_mixed_build_id_nvr(self):

+         self.hub.massTag('tag', ['n-v-r1', 123])

+ 

+     def test_correct_tagging_tag_id(self):

+         self.hub.massTag(1234, ['n-v-r1', 123])

+ 

+     def test_correct_tagging_tag_dict(self):

+         self.hub.massTag({'id': 1234, 'name': 'tag'}, ['n-v-r1', 123])

@@ -0,0 +1,120 @@ 

+ import mock

+ import unittest

+ import koji

+ import kojihub

+ 

+ 

+ class TestSnapshotTag(unittest.TestCase):

+     def setUp(self):

+         self._create_tag = mock.patch('kojihub._create_tag').start()

+         self.get_tag = mock.patch('kojihub.get_tag').start()

+         self.get_build = mock.patch('kojihub.get_build').start()

+         self.get_user = mock.patch('kojihub.get_user').start()

+         self._direct_tag_build = mock.patch('kojihub._direct_tag_build').start()

+         self._direct_pkglist_add = mock.patch('kojihub._direct_pkglist_add').start()

+         self._delete_event_id = mock.patch('kojihub._delete_event_id').start()

+         self._grplist_add = mock.patch('kojihub._grplist_add').start()

+         self._grp_pkg_add = mock.patch('kojihub._grp_pkg_add').start()

+         self._grp_req_add = mock.patch('kojihub._grp_req_add').start()

+         self.readTagGroups = mock.patch('kojihub.readTagGroups').start()

+         self.readTaggedBuilds = mock.patch('kojihub.readTaggedBuilds').start()

+         self.context = mock.patch('kojihub.context').start()

+         self.context.session.assertPerm = mock.MagicMock()

+         self.hub = kojihub.RootExports()

+         self.hub.listPackages = mock.MagicMock()

+         self.hub.massTag = mock.MagicMock()

+ 

+     def tearDown(self):

+         mock.patch.stopall()

+ 

+     def test_no_permission(self):

+         self.context.session.assertPerm.side_effect = koji.ActionNotAllowed

+         with self.assertRaises(koji.ActionNotAllowed):

+             self.hub.snapshotTag('src', 'dst')

+         self.context.session.assertPerm.assert_called_once_with('tag')

+ 

+     def test_builds_without_pkgs(self):

+         with self.assertRaises(koji.ParameterError):

+             self.hub.snapshotTag('src', 'dst', builds=True, pkgs=False)

+ 

+     def test_existing_dst(self):

+         self.get_tag.side_effect = [{'id': 1}, {'id': 2}]

+         with self.assertRaises(koji.GenericError) as cm:

+             self.hub.snapshotTag('src', 'dst')

+         self.assertEqual("Target tag already exists", str(cm.exception))

+ 

+     def test_locked_without_force(self):

+         self.get_tag.side_effect = [None, {'id': 1, 'locked': True}]

+         with self.assertRaises(koji.GenericError) as cm:

+             self.hub.snapshotTag('src', 'dst')

+         self.assertEqual("Source tag is locked, use force to copy", str(cm.exception))

+ 

+     def test_correct_all(self):

+         src = {

+             'id': 1,

+             'name': 'src',

+             'parent': 2,

+             'locked': True,

+             'arches': 'x86_64 s390x',

+             'perm_id': 3,

+             'maven_support': True,

+             'maven_include_all': False,

+             'extra': {'extra_field': 'text'},

+         }

+         dst = src.copy()

+         dst['id'] = 11

+         dst['name'] = 'dst'

+         pkg = {

+             'package_name': 'pkg1',

+             'owner_name': 'owner',

+             'blocked': False,

+             'extra_arches': None,

+         }

+         build = {

+             'id': 21,

+             'nvr': 'n-v-r',

+         }

+ 

+         self.get_tag.side_effect = [

+             None,  # non-existing dst

+             src,  # retrieve src

+             dst,  # retrieve created dst

+         ]

+         self._create_tag.return_value = dst['id']

+         self.hub.listPackages.return_value = [pkg]

+         self.readTaggedBuilds.return_value = [build]

+         self.readTagGroups.return_value = [

+             {

+                 'id': 1,

+                 'name': 'group',

+                 'blocked': False,

+                 'packagelist': [{'package': 'pkg', 'blocked': False}],

+                 'grouplist': [{'name': 'group2', 'blocked': False}],

+             }

+         ]

+ 

+         # call

+         self.hub.snapshotTag('src', 'dst', force=True)

+ 

+         self._create_tag.assert_called_once_with('dst', parent=None, arches=src['arches'],

+                                                  perm=src['perm_id'], locked=src['locked'],

+                                                  maven_support=src['maven_support'],

+                                                  maven_include_all=src['maven_include_all'],

+                                                  extra=src['extra'])

+         self.get_tag.assert_has_calls([

+             mock.call('dst'),

+             mock.call('src', event=None, strict=True),

+             mock.call(dst['id'], strict=True),

+         ])

+         self.hub.listPackages.assert_called_once_with(tagID=src['id'], event=None, inherited=True)

+         self._direct_pkglist_add.assert_called_once_with(

+             taginfo=dst['id'],

+             pkginfo=pkg['package_name'],

+             owner=pkg['owner_name'],

+             block=pkg['blocked'],

+             extra_arches=pkg['extra_arches'],

+             force=True,

+             update=False,

+         )

+         self.readTaggedBuilds.assert_called_once_with(tag=src['id'], inherit=True, event=None, latest=True)

+         self.hub.massTag.assert_called_once_with(dst['id'], [build])

@@ -0,0 +1,218 @@ 

+ import mock

+ import unittest

+ import koji

+ import kojihub

+ 

+ 

+ class TestSnapshotTagModify(unittest.TestCase):

+     def setUp(self):

+         self._create_tag = mock.patch('kojihub._create_tag').start()

+         self.get_tag = mock.patch('kojihub.get_tag').start()

+         self.get_build = mock.patch('kojihub.get_build').start()

+         self.get_user = mock.patch('kojihub.get_user').start()

+         self._direct_tag_build = mock.patch('kojihub._direct_tag_build').start()

+         self._direct_untag_build = mock.patch('kojihub._direct_untag_build').start()

+         self._tag_build = mock.patch('kojihub._tag_build').start()

+         self._untag_build = mock.patch('kojihub._untag_build').start()

+         self._direct_pkglist_add = mock.patch('kojihub._direct_pkglist_add').start()

+         self._delete_event_id = mock.patch('kojihub._delete_event_id').start()

+         self._grplist_add = mock.patch('kojihub._grplist_add').start()

+         self._grplist_remove = mock.patch('kojihub._grplist_remove').start()

+         self._grp_pkg_add = mock.patch('kojihub._grp_pkg_add').start()

+         self._grp_pkg_remove = mock.patch('kojihub._grp_pkg_remove').start()

+         self._grp_req_add = mock.patch('kojihub._grp_req_add').start()

+         self._grp_req_remove = mock.patch('kojihub._grp_req_remove').start()

+         self.readTagGroups = mock.patch('kojihub.readTagGroups').start()

+         self.readTaggedBuilds = mock.patch('kojihub.readTaggedBuilds').start()

+         self.context = mock.patch('kojihub.context').start()

+         self.context.session.assertPerm = mock.MagicMock()

+         self.edit_tag = mock.patch('kojihub.edit_tag').start()

+         self.hub = kojihub.RootExports()

+         self.hub.listPackages = mock.MagicMock()

+         self.hub.massTag = mock.MagicMock()

+ 

+     def tearDown(self):

+         mock.patch.stopall()

+ 

+     def test_no_permission(self):

+         self.context.session.assertPerm.side_effect = koji.ActionNotAllowed

+         with self.assertRaises(koji.ActionNotAllowed):

+             self.hub.snapshotTagModify('src', 'dst')

+         self.context.session.assertPerm.assert_called_once_with('tag')

+ 

+     def test_builds_without_pkgs(self):

+         with self.assertRaises(koji.ParameterError):

+             self.hub.snapshotTagModify('src', 'dst', builds=True, pkgs=False)

+ 

+     def test_nonexisting_dst(self):

+         self.get_tag.side_effect = [{'id': 1, 'locked': False}, koji.GenericError('xx')]

+         with self.assertRaises(koji.GenericError) as cm:

+             self.hub.snapshotTagModify('src', 'dst')

+         self.assertEqual("xx", str(cm.exception))

+ 

+     def test_locked_without_force_both(self):

+         self.get_tag.side_effect = [{'id': 1, 'locked': True}, {'id': 2, 'locked': True}]

+         with self.assertRaises(koji.GenericError) as cm:

+             self.hub.snapshotTagModify('src', 'dst')

+         self.assertEqual("Source or destination tag is locked, use force to copy", str(cm.exception))

+ 

+     def test_locked_without_force_src(self):

+         self.get_tag.side_effect = [{'id': 1, 'locked': True}, {'id': 2, 'locked': False}]

+         with self.assertRaises(koji.GenericError) as cm:

+             self.hub.snapshotTagModify('src', 'dst')

+         self.assertEqual("Source or destination tag is locked, use force to copy", str(cm.exception))

+ 

+     def test_locked_without_force_dst(self):

+         self.get_tag.side_effect = [{'id': 1, 'locked': False}, {'id': 2, 'locked': True}]

+         with self.assertRaises(koji.GenericError) as cm:

+             self.hub.snapshotTagModify('src', 'dst')

+         self.assertEqual("Source or destination tag is locked, use force to copy", str(cm.exception))

+ 

+     def test_correct_all(self):

+         src = {

+             'id': 1,

+             'name': 'src',

+             'parent': 2,

+             'locked': True,

+             'arches': 'x86_64 s390x',

+             'perm_id': 3,

+             'maven_support': True,

+             'maven_include_all': False,

+             'extra': {'extra_field': 'text'},

+         }

+         dst = src.copy()

+         dst['id'] = 11

+         dst['name'] = 'dst'

+         pkg1 = {

+             'tag_id': src['id'],

+             'package_name': 'pkg1',

+             'owner_name': 'owner',

+             'blocked': False,

+             'extra_arches': None,

+         }

+         pkg2 = {

+             'tag_id': dst['id'],

+             'package_name': 'pkg2',

+             'owner_name': 'owner',

+             'blocked': False,

+             'extra_arches': None,

+         }

+         build = {

+             'id': 21,

+             'nvr': 'n-v-r',

+             'package_name': pkg1['package_name'],

+             'tag_name': 'src',

+         }

+         build2 = {

+             'id': 22,

+             'nvr': 'n-v-r2',

+             'package_name': pkg1['package_name'],

+             'tag_name': 'dst',

+         }

+         user = {

+             'id': 321,

+             'name': 'username',

+         }

+         src_group1 = {

+             'id': 1,

+             'name': 'group1',