From 512671534a7ce9f62344f17cbc69f30c2571be96 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Nov 04 2016 14:11:00 +0000 Subject: [PATCH 1/2] Abstract the SigulHelper to allow other backends. Eventually, we can add other backends if we like. **Note**, this changes the config to make it easy to select and instantiate these things. --- diff --git a/fedmsg.d/example-config.py b/fedmsg.d/example-config.py index ad49ad5..dc72f17 100644 --- a/fedmsg.d/example-config.py +++ b/fedmsg.d/example-config.py @@ -4,9 +4,13 @@ hostname = socket.gethostname() config = { 'robosignatory.enabled.tagsigner': True, 'robosignatory.enabled.atomicsigner': True, - 'robosignatory.signing.user': 'robosignatory', - 'robosignatory.signing.passphrase_file': 'robosignatory.pass', - 'robosignatory.signing.config_file': '/etc/sigul/client.conf', + + 'robosignatory.signing': { + 'backend': 'sigul', + 'user': 'robosignatory', + 'passphrase_file': 'robosignatory.pass', + 'config_file': '/etc/sigul/client.conf', + }, # The keys here need to be the same in the sigul bridge 'robosignatory.koji_instances': { diff --git a/robosignatory/atomicconsumer.py b/robosignatory/atomicconsumer.py index a4c1442..2f40d8a 100644 --- a/robosignatory/atomicconsumer.py +++ b/robosignatory/atomicconsumer.py @@ -19,10 +19,8 @@ class AtomicSignerConsumer(fedmsg.consumers.FedmsgConsumer): '%s.%s.bodhi.ostree.compose.finish' % (prefix, env), ] - self.sigul_helper = utils.SigulHelper( - self.hub.config['robosignatory.signing.user'], - self.hub.config['robosignatory.signing.passphrase_file'], - self.hub.config['robosignatory.signing.config_file']) + signing_config = self.hub.config['robosignatory.signing'] + self.signer = utils.get_signing_helper(**signing_config) self.refs = {} for ref in self.hub.config['robosignatory.ostree_refs']: @@ -74,5 +72,5 @@ class AtomicSignerConsumer(fedmsg.consumers.FedmsgConsumer): val = self.refs[ref] - robosignatory.work.process_atomic(self.sigul_helper, ref, commitid, + robosignatory.work.process_atomic(self.signer, ref, commitid, **val) diff --git a/robosignatory/cli.py b/robosignatory/cli.py index f4264a9..d9e6241 100644 --- a/robosignatory/cli.py +++ b/robosignatory/cli.py @@ -27,15 +27,14 @@ def buildsigner(): koji_session = koji.ClientSession( config['robosignatory.koji_instances'][koji_instance]['url']) - sigul_helper = utils.SigulHelper( - config['robosignatory.signing.user'], - config['robosignatory.signing.passphrase_file'], - config['robosignatory.signing.config_file']) + + signing_config = self.hub.config['robosignatory.signing'] + signer = utils.get_signing_helper(**signing_config) rpms = utils.get_rpms(koji_session, build_nvr) print 'Signing %s' % ', '.join(rpms) - cmd_line = sigul_helper.build_sig_cmdline(key, rpms, koji_instance) + cmd_line = signer.build_sig_cmdline(key, rpms, koji_instance) ret, stdout, stderr = utils.run_command(cmd_line) if ret != 0: @@ -70,10 +69,8 @@ def atomicsigner(): config = fedmsg.config.load_config([], None) - sigul_helper = utils.SigulHelper( - config['robosignatory.signing.user'], - config['robosignatory.signing.passphrase_file'], - config['robosignatory.signing.config_file']) + signing_config = self.hub.config['robosignatory.signing'] + signer = utils.get_signing_helper(**signing_config) if ref not in config['robosignatory.ostree_refs']: print 'Ref %s not found' % ref @@ -81,4 +78,4 @@ def atomicsigner(): val = config['robosignatory.ostree_refs'][ref] - robosignatory.work.process_atomic(sigul_helper, ref, commitid, **val) + robosignatory.work.process_atomic(signer, ref, commitid, **val) diff --git a/robosignatory/tagconsumer.py b/robosignatory/tagconsumer.py index da14493..1e8dc4c 100644 --- a/robosignatory/tagconsumer.py +++ b/robosignatory/tagconsumer.py @@ -26,10 +26,9 @@ class TagSignerConsumer(fedmsg.consumers.FedmsgConsumer): '%s.%s.buildsys.tag' % (prefix, env) ] - self.sigul_helper = utils.SigulHelper( - self.config['robosignatory.signing.user'], - self.config['robosignatory.signing.passphrase_file'], - self.config['robosignatory.signing.config_file']) + signing_config = self.hub.config['robosignatory.signing'] + self.signer = utils.get_signing_helper(**signing_config) + self.koji_clients = {} for instance in self.config['robosignatory.koji_instances']: instance_info = self.config[ @@ -116,9 +115,9 @@ class TagSignerConsumer(fedmsg.consumers.FedmsgConsumer): else: to_sign = [key for key in rpms.keys() if not rpms[key]['signed']] log.debug('RPMs needing signing: %s' % to_sign) - cmdline = self.sigul_helper.build_sign_cmdline(tag_info['key'], - rpms.keys(), - koji_instance) + cmdline = self.signer.build_sign_cmdline(tag_info['key'], + rpms.keys(), + koji_instance) log.debug('Signing command line: %s' % cmdline) ret, stdout, stderr = utils.run_command(cmdline) diff --git a/robosignatory/utils.py b/robosignatory/utils.py index d8102c1..79fb3b2 100644 --- a/robosignatory/utils.py +++ b/robosignatory/utils.py @@ -1,5 +1,9 @@ +import abc import subprocess +import logging +log = logging.getLogger('robosignatory.utils') + class Struct: def __init__(self, **entries): @@ -30,7 +34,34 @@ def run_command(command): return ret, stdout, stderr -class SigulHelper(object): +def get_signing_helper(backend, *args, **kwargs): + """ Instantiate and return the appropriate signing backend. """ + classes = { + 'sigul': SigulHelper, + # TODO.. other backends can go here. + } + cls = classes[backend] + log.debug("Instantiating helper %r from backend key %r" % (cls, backend)) + return cls(*args, **kwargs) + + +class BaseSigningHelper(object): + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def build_cmdline(self, *args): + pass + + @abc.abstractmethod + def build_sign_cmdline(self, *args): + pass + + @abc.abstractmethod + def build_atomic_cmdline(self, *args): + pass + + +class SigulHelper(BaseSigningHelper): def __init__(self, user, passphrase_file, config_file=None): self.user = user self.passphrase_file = passphrase_file diff --git a/robosignatory/work.py b/robosignatory/work.py index d9d7690..25e8292 100644 --- a/robosignatory/work.py +++ b/robosignatory/work.py @@ -7,7 +7,7 @@ import logging log = logging.getLogger("robosignatory.work") -def process_atomic(sigul_helper, ref, commitid, key, directory): +def process_atomic(signer, ref, commitid, key, directory): commitpath = os.path.join(directory, 'objects', commitid[:2], commitid[2:] + '.commit') commitmetapath = commitpath + 'meta' @@ -20,10 +20,10 @@ def process_atomic(sigul_helper, ref, commitid, key, directory): return log.info('All checks passed, signing %s with %s' % (commitid, key)) - cmdline = sigul_helper.build_atomic_cmdline(key, - commitid, - commitpath, - commitmetapath) + cmdline = signer.build_atomic_cmdline(key, + commitid, + commitpath, + commitmetapath) log.info('Signing command line: %s' % cmdline) ret, stdout, stderr = utils.run_command(cmdline) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/__init__.py diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..6d8f061 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,20 @@ +import unittest + +from nose.tools import raises, eq_ + +import robosignatory.utils + +class TestUtils(unittest.TestCase): + + def test_get_helper(self): + helper = robosignatory.utils.get_signing_helper( + backend='sigul', + user='ralph', + passphrase_file='/tmp/wide-open.txt', + ) + eq_(type(helper), robosignatory.utils.SigulHelper) + + @raises(KeyError) + def test_no_such_helper(self): + robosignatory.utils.get_signing_helper(backend='wat', ) + From 1760b695aa3d31b822e6be13f9ede1e52e07935b Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Nov 17 2016 21:37:42 +0000 Subject: [PATCH 2/2] Use entry-points to discover plugins. Additionally, add a dummy EchoHelper that can be used for debugging and demonstration purposes. --- diff --git a/fedmsg.d/example-config.py b/fedmsg.d/example-config.py index dc72f17..8503005 100644 --- a/fedmsg.d/example-config.py +++ b/fedmsg.d/example-config.py @@ -6,7 +6,10 @@ config = { 'robosignatory.enabled.atomicsigner': True, 'robosignatory.signing': { + # This should be the name of an entrypoint plugin that provides + # SigningHelper functionality. 'backend': 'sigul', + # These are arguments to the __init__ method of that helper. 'user': 'robosignatory', 'passphrase_file': 'robosignatory.pass', 'config_file': '/etc/sigul/client.conf', diff --git a/robosignatory/utils.py b/robosignatory/utils.py index 79fb3b2..e78616b 100644 --- a/robosignatory/utils.py +++ b/robosignatory/utils.py @@ -1,4 +1,5 @@ import abc +import pkg_resources import subprocess import logging @@ -36,10 +37,9 @@ def run_command(command): def get_signing_helper(backend, *args, **kwargs): """ Instantiate and return the appropriate signing backend. """ - classes = { - 'sigul': SigulHelper, - # TODO.. other backends can go here. - } + points = pkg_resources.iter_entry_points('robosignatory.signing.helpers') + classes = dict([(point.name, point.load()) for point in points]) + log.debug("Found the following installed signing helpers %r" % classes) cls = classes[backend] log.debug("Instantiating helper %r from backend key %r" % (cls, backend)) return cls(*args, **kwargs) @@ -61,6 +61,27 @@ class BaseSigningHelper(object): pass +class EchoHelper(BaseSigningHelper): + """ A dummy "hello world" helper, used for debugging. """ + def __init__(self, *args, **kwargs): + log.info("Constructing EchoHelper(%r, %r)" % (args, kwargs)) + + def build_cmdline(self, *args, **kwargs): + result = ['echo', ' '.join(['build_cmdline:', str(args), str(kwargs)])] + log.info(result) + return result + + def build_sign_cmdline(self, *args, **kwargs): + result = ['echo', ' '.join(['build_sign_cmdline:', str(args), str(kwargs)])] + log.info(result) + return result + + def build_atomic_cmdline(self, *args, **kwargs): + result = ['echo', ' '.join(['build_atomic_cmdline:', str(args), str(kwargs)])] + log.info(result) + return result + + class SigulHelper(BaseSigningHelper): def __init__(self, user, passphrase_file, config_file=None): self.user = user diff --git a/setup.py b/setup.py index 59cc2ae..f0e36e0 100644 --- a/setup.py +++ b/setup.py @@ -18,9 +18,14 @@ setup( [moksha.consumer] tagsignerconsumer = robosignatory.tagconsumer:TagSignerConsumer atomicsignerconsumer = robosignatory.atomicconsumer:AtomicSignerConsumer + [console_scripts] robosignatory-signbuild = robosignatory.cli:buildsigner robosignatory-signtagbuild = robosignatory.cli:tagsigner robosignatory-signatomic = robosignatory.cli:atomicsigner + + [robosignatory.signing.helpers] + echo = robosignatory.utils:EchoHelper + sigul = robosignatory.utils:SigulHelper """, ) diff --git a/tests/test_utils.py b/tests/test_utils.py index 6d8f061..a262750 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -6,7 +6,11 @@ import robosignatory.utils class TestUtils(unittest.TestCase): - def test_get_helper(self): + @raises(KeyError) + def test_no_such_helper(self): + robosignatory.utils.get_signing_helper(backend='wat', ) + + def test_get_sigul_helper(self): helper = robosignatory.utils.get_signing_helper( backend='sigul', user='ralph', @@ -14,7 +18,11 @@ class TestUtils(unittest.TestCase): ) eq_(type(helper), robosignatory.utils.SigulHelper) - @raises(KeyError) - def test_no_such_helper(self): - robosignatory.utils.get_signing_helper(backend='wat', ) - + def test_simple_echo_helper(self): + helper = robosignatory.utils.get_signing_helper( + backend='echo', + user='ralph', + passphrase_file='/tmp/wide-open.txt', + ) + cmdline = helper.build_cmdline('wat') + eq_(cmdline, ["echo", "build_cmdline: ('wat',) {}"])