| |
@@ -1,12 +1,16 @@
|
| |
from __future__ import unicode_literals, absolute_import
|
| |
|
| |
+ import logging
|
| |
+ import re
|
| |
+
|
| |
import koji
|
| |
|
| |
+ import six
|
| |
+
|
| |
import robosignatory.utils as utils
|
| |
|
| |
- import logging
|
| |
- log = logging.getLogger("robosignatory.tagconsumer")
|
| |
|
| |
+ log = logging.getLogger("robosignatory.tagconsumer")
|
| |
|
| |
KNOWN_TAG_TYPES = ['plain', 'modular']
|
| |
|
| |
@@ -38,6 +42,7 @@
|
| |
raise Exception('Only SSL and kerberos authmethods supported')
|
| |
|
| |
instance_obj = {'client': client,
|
| |
+ 'sidetags': {},
|
| |
'tags': {}}
|
| |
if 'mbs_user' in instance_info:
|
| |
instance_obj['mbs_user'] = instance_info['mbs_user']
|
| |
@@ -55,6 +60,51 @@
|
| |
raise Exception('Invalid tag type detected: %s' % tag_type)
|
| |
instance_obj['tags'][tag['from']]['type'] = tag_type
|
| |
|
| |
+ sidetags = tag.get('sidetags')
|
| |
+ if not sidetags:
|
| |
+ continue
|
| |
+
|
| |
+ # Fill in from, to tags in placeholders, this will be the key
|
| |
+ # in instance_obj['sidetags'].
|
| |
+ pattern_filled_in = sidetags['pattern'].replace(
|
| |
+ '<from>', tag['from']
|
| |
+ ).replace(
|
| |
+ '<to>', tag['to']
|
| |
+ )
|
| |
+
|
| |
+ # Escape and construct regex to match the
|
| |
+ # '<sidetag>-pending-signing' tag to which we should react.
|
| |
+ from_tag_re = re.compile(
|
| |
+ sidetags['from'].replace(
|
| |
+ '<sidetag>',
|
| |
+ r'(?P<sidetag>'
|
| |
+ + re.escape(pattern_filled_in)
|
| |
+ + r')'
|
| |
+ ).replace(re.escape('<seq_id>'), r'(?P<seq_id>[1-9][0-9]*)')
|
| |
+ )
|
| |
+
|
| |
+ # We don't want to accept '<sidetag>-pending-signing' tagged
|
| |
+ # builds from just anyone.
|
| |
+
|
| |
+ # The 'trusted_taggers' option is a list which defines the user
|
| |
+ # ids from which we want to accept builds tagged into the
|
| |
+ # '<sidetag>-pending-signing' tag.
|
| |
+ trusted_taggers = sidetags['trusted_taggers']
|
| |
+ if (
|
| |
+ not isinstance(trusted_taggers, list)
|
| |
+ or not all(isinstance(x, six.text_type) for x in trusted_taggers)
|
| |
+ ):
|
| |
+ raise TypeError("`trusted_taggers` must be a list of strings.")
|
| |
+
|
| |
+ instance_obj['sidetags'][pattern_filled_in] = {
|
| |
+ 'pattern': sidetags['pattern'],
|
| |
+ 'from_tag_re': from_tag_re,
|
| |
+ 'from': sidetags['from'],
|
| |
+ 'to': sidetags['to'],
|
| |
+ 'tags_key': tag['from'],
|
| |
+ 'trusted_taggers': trusted_taggers,
|
| |
+ }
|
| |
+
|
| |
self.koji_clients[instance] = instance_obj
|
| |
|
| |
log.info('TagSigner ready for service')
|
| |
@@ -85,6 +135,54 @@
|
| |
self.dowork(build_nvr, build_id, tag, koji_instance,
|
| |
skip_tagging=False)
|
| |
|
| |
+ def match_sidetag(self, build_nvr, build_id, tag, koji_instance):
|
| |
+ instance = self.koji_clients[koji_instance]
|
| |
+
|
| |
+ for pattern_filled_in, sidetag_matched in instance['sidetags'].items():
|
| |
+ from_tag_re = sidetag_matched['from_tag_re']
|
| |
+
|
| |
+ m = from_tag_re.match(tag)
|
| |
+ if not m:
|
| |
+ continue
|
| |
+
|
| |
+ sidetag = m.group('sidetag')
|
| |
+
|
| |
+ # Verify that the build was tagged into the tag by one of the
|
| |
+ # trusted taggers.
|
| |
+ for hist_event in sorted(instance['client'].tagHistory(build_id),
|
| |
+ key=lambda ev: ev['create_ts'],
|
| |
+ reverse=True):
|
| |
+ # We want to verify the latest matching tagging event only.
|
| |
+ if (
|
| |
+ hist_event['active']
|
| |
+ and hist_event['tag_name'] == tag
|
| |
+ ):
|
| |
+ if hist_event['creator_name'] in sidetag_matched['trusted_taggers']:
|
| |
+ # This is how it should be, break out of the loop.
|
| |
+ break
|
| |
+
|
| |
+ log.error("Side tag build not tagged into %s by trusted user (%s)"
|
| |
+ " but by '%s'!",
|
| |
+ tag, ", ".join(sidetag_matched['trusted_taggers']),
|
| |
+ hist_event['creator_name'])
|
| |
+ raise Exception('Side tag build tagged by untrusted user')
|
| |
+ else:
|
| |
+ # We should find the tag as active in the tagging history
|
| |
+ # of the build. Bail out because we didn't find it above
|
| |
+ # i.e. didn't break out of the loop.
|
| |
+ log.error("Couldn't find tag %s in history of build %s (%s)!",
|
| |
+ tag, build_nvr, build_id)
|
| |
+ raise Exception("Tag not found in build history")
|
| |
+
|
| |
+ tag_info = instance['tags'][sidetag_matched['tags_key']]
|
| |
+ tag_to = sidetag_matched['to'].replace('<sidetag>', sidetag)
|
| |
+
|
| |
+ tag_matched = True
|
| |
+
|
| |
+ return tag_matched, tag_info, tag_to
|
| |
+
|
| |
+ return False, None, None
|
| |
+
|
| |
def dowork(self, build_nvr, build_id, tag, koji_instance,
|
| |
skip_tagging=False):
|
| |
instance = self.koji_clients[koji_instance]
|
| |
@@ -92,14 +190,22 @@
|
| |
if not build_id:
|
| |
build_id = instance['client'].findBuildID(build_nvr)
|
| |
|
| |
- if tag not in instance['tags']:
|
| |
+ tag_matched = False
|
| |
+
|
| |
+ if tag in instance['tags']:
|
| |
+ tag_matched = True
|
| |
+ tag_info = instance['tags'][tag]
|
| |
+ tag_to = tag_info['to']
|
| |
+ else:
|
| |
+ tag_matched, tag_info, tag_to = self.match_sidetag(build_nvr, build_id, tag,
|
| |
+ koji_instance)
|
| |
+
|
| |
+ if not tag_matched:
|
| |
log.info('Tag not autosigned, skipping')
|
| |
return
|
| |
- tag_info = instance['tags'][tag]
|
| |
|
| |
log.info('Going to sign %s with %s (%s) and move to %s',
|
| |
- build_nvr, tag_info['key'], tag_info['keyid'],
|
| |
- tag_info['to'])
|
| |
+ build_nvr, tag_info['key'], tag_info['keyid'], tag_to)
|
| |
|
| |
if tag_info['type'] == 'plain':
|
| |
self.signwrite_single_build(build_nvr, build_id, tag_info, instance, koji_instance)
|
| |
@@ -111,13 +217,11 @@
|
| |
if skip_tagging:
|
| |
log.info('Tagging skipped, done')
|
| |
else:
|
| |
- log.info('Packages correctly signed, moving to %s' %
|
| |
- tag_info['to'])
|
| |
- if tag == tag_info['to']:
|
| |
+ log.info('Packages correctly signed, moving to %s' % tag_to)
|
| |
+ if tag == tag_to:
|
| |
log.info('Non-gated, not moving')
|
| |
else:
|
| |
- instance['client'].tagBuild(tag_info['to'], build_id, False,
|
| |
- tag)
|
| |
+ instance['client'].tagBuild(tag_to, build_id, False, tag)
|
| |
|
| |
def signwrite_module(self, build_nvr, build_id, tag_info, instance, koji_instance):
|
| |
log.info('Signing module build %s', build_nvr)
|
| |
@@ -129,9 +233,11 @@
|
| |
log.info('Signing all module content')
|
| |
for build in instance['client'].listTagged(content_koji_tag):
|
| |
if build['owner_name'] != instance['mbs_user']:
|
| |
- log.error('Build %(build_id)s has owner %(owner_name)s, which is NOT mbs_user!' % build)
|
| |
+ log.error('Build %(build_id)s has owner %(owner_name)s, which is NOT mbs_user!'
|
| |
+ % build)
|
| |
raise Exception('Modular content tag contains invalid owned build')
|
| |
- self.signwrite_single_build(build['nvr'], build['build_id'], tag_info, instance, koji_instance)
|
| |
+ self.signwrite_single_build(build['nvr'], build['build_id'], tag_info, instance,
|
| |
+ koji_instance)
|
| |
|
| |
def signwrite_single_build(self, build_nvr, build_id, tag_info, instance, koji_instance):
|
| |
log.info('Signing and writing build %s', build_nvr)
|
| |
@@ -140,9 +246,8 @@
|
| |
build_id=build_id,
|
| |
sigkey=tag_info['keyid'])
|
| |
log.info('RPMs to sign and move: %s',
|
| |
- ['%s (%s, signed: %s)' %
|
| |
- (key, rpm['id'], rpm['signed'])
|
| |
- for key, rpm in rpms.items()])
|
| |
+ ['%s (%s, signed: %s)' % (key, rpm['id'], rpm['signed'])
|
| |
+ for key, rpm in rpms.items()])
|
| |
if len(rpms) < 1:
|
| |
log.info('Build contains no rpms, skipping signing and writing')
|
| |
|
| |
This is based off PR #27 which needs to be merged first, and fixes/implements issue #28.