diff options
| -rw-r--r-- | copr-backend.spec | 2 | ||||
| -rw-r--r-- | support_signatrust_backend.patch | 749 | 
2 files changed, 277 insertions, 474 deletions
diff --git a/copr-backend.spec b/copr-backend.spec index ae9f5f4..93c763f 100644 --- a/copr-backend.spec +++ b/copr-backend.spec @@ -40,7 +40,7 @@ BuildRequires: python3-setuptools  BuildRequires: python3-copr  BuildRequires: python3-kafka-python -BuildRequires: python3-copr-common = %copr_common_version +BuildRequires: python3-copr-common >= %copr_common_version  BuildRequires: python3-daemon  BuildRequires: python3-dateutil  BuildRequires: python3-distro diff --git a/support_signatrust_backend.patch b/support_signatrust_backend.patch index d803d26..adcbf1e 100644 --- a/support_signatrust_backend.patch +++ b/support_signatrust_backend.patch @@ -1,116 +1,3 @@ -diff --git a/copr_backend/actions.py b/backend/copr_backend/actions.py -index 39722b843..6da2dfb65 100644 ---- a/copr_backend/actions.py -+++ b/copr_backend/actions.py -@@ -21,13 +21,13 @@ from copr_common.worker_manager import WorkerManager -  - from copr_backend.worker_manager import BackendQueueTask -  --from .sign import create_user_keys, CoprKeygenRequestError -+from .sign import CoprKeygenRequestError - from .exceptions import CreateRepoError, CoprSignError, FrontendClientException - from .helpers import (get_redis_logger, silent_remove, ensure_dir_exists, -                       get_chroot_arch, format_filename, -                       uses_devel_repo, call_copr_repo, build_chroot_log_name, -                       copy2_but_hardlink_rpms) --from .sign import sign_rpms_in_dir, unsign_rpms_in_dir, get_pubkey -+from .sign import new_signer -  -  - class Action(object): -@@ -92,6 +92,8 @@ class Action(object): -  -         self.log = log if log else get_redis_logger(self.opts, "backend.actions", "actions") -  -+        self.signer = new_signer(opts) -+ -     def __str__(self): -         return "<{}(Action): {}>".format(self.__class__.__name__, self.data) -  -@@ -147,7 +149,7 @@ class GPGMixin(object): -             # skip key creation, most probably sign component is unused -             return True -         try: --            create_user_keys(ownername, projectname, self.opts) -+            self.signer.create_user_keys(ownername, projectname, self.opts) -             return True -         except CoprKeygenRequestError as e: -             self.log.exception(e) -@@ -176,7 +178,7 @@ class Fork(Action, GPGMixin): -                 # Generate brand new gpg key. -                 self.generate_gpg_key(data["user"], data["copr"]) -                 # Put the new public key into forked build directory. --                get_pubkey(data["user"], data["copr"], self.log, self.opts.sign_domain, pubkey_path) -+                self.signer.get_pubkey(data["user"], data["copr"], self.log, self.opts.sign_domain, pubkey_path) -  -             chroot_paths = set() -             for chroot, src_dst_dir in builds_map.items(): -@@ -206,9 +208,9 @@ class Fork(Action, GPGMixin): -                         continue -  -                     # Drop old signatures coming from original repo and re-sign. --                    unsign_rpms_in_dir(dst_path, opts=self.opts, log=self.log) -+                    self.signer.unsign_rpms_in_dir(dst_path, opts=self.opts, log=self.log) -                     if sign: --                        sign_rpms_in_dir(data["user"], data["copr"], dst_path, -+                        self.signer.sign_rpms_in_dir(data["user"], data["copr"], dst_path, -                                          chroot, opts=self.opts, log=self.log) -  -                     self.log.info("Forked build %s as %s", src_path, dst_path) -diff --git a/copr_backend/background_worker.py b/backend/copr_backend/background_worker.py -index 4b5f13135..bc05edbd7 100644 ---- a/copr_backend/background_worker.py -+++ b/copr_backend/background_worker.py -@@ -9,7 +9,7 @@ import logging - from copr_common.background_worker import BackgroundWorker - from copr_backend.frontend import FrontendClient - from copr_backend.helpers import (BackendConfigReader, get_redis_logger) -- -+from copr_backend.sign import new_signer -  - class BackendBackgroundWorker(BackgroundWorker): -     """ -@@ -28,6 +28,7 @@ class BackendBackgroundWorker(BackgroundWorker): -  -         self.frontend_client = FrontendClient(self.opts, self.log, -                                               try_indefinitely=True) -+        self.signer = new_signer(self.opts) -  -     def _switch_logger_to_redis(self): -         logger_name = '{}.{}.pid-{}'.format( -diff --git a/copr_backend/background_worker_build.py b/backend/copr_backend/background_worker_build.py -index 0eee39ab8..c12d4921d 100644 ---- a/copr_backend/background_worker_build.py -+++ b/copr_backend/background_worker_build.py -@@ -27,8 +27,8 @@ from copr_backend.helpers import ( -     call_copr_repo, run_cmd, register_build_result, format_evr, - ) - from copr_backend.job import BuildJob --from copr_backend.msgbus import MessageSender --from copr_backend.sign import sign_rpms_in_dir, get_pubkey -+from copr_backend.euler_msgbus import MessageSender -+from copr_backend.sign import new_signer - from copr_backend.sshcmd import SSHConnection, SSHConnectionError - from copr_backend.vm_alloc import ResallocHostFactory -  -@@ -622,7 +622,7 @@ class BuildBackgroundWorker(BackendBackgroundWorker): -         self.log.info("Going to sign pkgs from source: %s in chroot: %s", -                       self.job.task_id, self.job.chroot_dir) -  --        sign_rpms_in_dir( -+        self.signer.sign_rpms_in_dir( -             self.job.project_owner, -             self.job.project_name, -             os.path.join(self.job.chroot_dir, self.job.target_dir_name), -@@ -736,7 +736,7 @@ class BuildBackgroundWorker(BackendBackgroundWorker): -         # TODO: uncomment this when key revoke/change will be implemented -         # if os.path.exists(pubkey_path): -         #    return --        get_pubkey(user, project, self.log, self.opts.sign_domain, pubkey_path) -+        self.signer.get_pubkey(user, project, self.log, self.opts.sign_domain, pubkey_path) -         self.log.info("Added pubkey for user %s project %s into: %s", -                       user, project, pubkey_path) -   diff --git a/copr_backend/constants.py b/backend/copr_backend/constants.py  index a529be28a..83bcb8fbe 100644  --- a/copr_backend/constants.py @@ -171,15 +58,35 @@ index 75fa5e62d..291228912 100644           for group_id in range(opts.build_groups_count):               archs = _get_conf(cp, "backend",  diff --git a/copr_backend/sign.py b/backend/copr_backend/sign.py -index e21653e78..95f674255 100644 +index e21653e78..599d209ee 100644  --- a/copr_backend/sign.py  +++ b/copr_backend/sign.py -@@ -25,78 +25,6 @@ def create_gpg_email(username, projectname, domain): +@@ -7,17 +7,16 @@ Wrapper for /bin/sign from obs-sign package + from subprocess import Popen, PIPE, SubprocessError + import os + import time ++import functools +  + from packaging import version +  + from copr_common.request import SafeRequest +-from copr_backend.helpers import get_redis_logger ++from copr_backend.helpers import get_redis_logger, get_backend_opts + from .exceptions import CoprSignError, CoprSignNoKeyError, \ +     CoprKeygenRequestError +  +  +-SIGN_BINARY = "/bin/sign" +- + def create_gpg_email(username, projectname, domain): +     """ +     Creates canonical name_email to identify gpg key +@@ -25,77 +24,17 @@ def create_gpg_email(username, projectname, domain):       return "{}#{}@copr.{}".format(username, projectname, domain)  - --def call_sign_bin(cmd, log): + def call_sign_bin(cmd, log):  -    """  -    Call /bin/sign and return (rc, stdout, stderr).  Re-try the call  -    automatically upon certain failures (if that makes sense). @@ -204,8 +111,10 @@ index e21653e78..95f674255 100644  -        break  -    return handle.returncode, stdout, stderr  - -- --def get_pubkey(username, projectname, log, sign_domain, outfile=None): ++    signer = new_signer() ++    return signer.call_sign_bin(cmd, log) +  + def get_pubkey(username, projectname, log, sign_domain, outfile=None):  -    """  -    Retrieves public key for user/project from signer host.  - @@ -238,8 +147,10 @@ index e21653e78..95f674255 100644  -  -    return stdout  - -- --def _sign_one(path, email, hashtype, log): ++    signer = new_signer() ++    return signer.get_pubkey(username, projectname, log, sign_domain, outfile) +  + def _sign_one(path, email, hashtype, log):  -    cmd = [SIGN_BINARY, "-4", "-h", hashtype, "-u", email, "-r", path]  -    returncode, stdout, stderr = call_sign_bin(cmd, log)  -    if returncode != 0: @@ -249,67 +160,88 @@ index e21653e78..95f674255 100644  -            cmd=cmd, stdout=stdout, stderr=stderr)  -    return stdout, stderr  - -- ++    signer = new_signer() ++    return signer._sign_one(path, email, hashtype, log) +    def gpg_hashtype_for_chroot(chroot, opts):       """ -     Given the chroot name (in "mock format", like "fedora-rawhide-x86_64") -@@ -136,133 +64,436 @@ def gpg_hashtype_for_chroot(chroot, opts): +@@ -135,134 +74,455 @@ def gpg_hashtype_for_chroot(chroot, opts): +     # fallback to sha256       return "sha256" -  --def sign_rpms_in_dir(username, projectname, path, chroot, opts, log): +- + def sign_rpms_in_dir(username, projectname, path, chroot, opts, log):  -    """  -    Signs rpms using obs-signd. ++    signer = new_signer() ++    return signer.sign_rpms_in_dir(username, projectname, path, chroot, opts, log) +  +-    If some some pkgs failed to sign, entire build marked as failed, +-    but we continue to try sign other pkgs. +- +-    :param username: copr username +-    :param projectname: copr projectname +-    :param path: directory with rpms to be signed +-    :param chroot: chroot name where we sign packages, affects the hash type +-    :param Munch opts: backend config ++def create_user_keys(username, projectname, opts, try_indefinitely=False): ++    signer = new_signer() ++    return signer.create_user_keys(username, projectname, opts, try_indefinitely) +  +-    :type log: logging.Logger ++def _unsign_one(path): ++    signer = new_signer() ++    return signer._unsign_one(path) +  +-    :raises: :py:class:`backend.exceptions.CoprSignError` failed to sign at least one package +-    """ ++def unsign_rpms_in_dir(path, opts, log): ++    signer = new_signer() ++    return signer.unsign_rpms_in_dir(path, opts, log) ++ ++  +# a sign interface  +class Signer(object):  +    @classmethod  +    def get_pubkey(cls, username, projectname, log, sign_domain, outfile=None):  +        """get public key"""  +        raise NotImplementedError -  --    If some some pkgs failed to sign, entire build marked as failed, --    but we continue to try sign other pkgs. ++  +    @classmethod  +    def sign_rpms_in_dir(cls, username, projectname, path, chroot, opts, log):  +        """batch sign rpms"""  +        raise NotImplementedError -  --    :param username: copr username --    :param projectname: copr projectname --    :param path: directory with rpms to be signed --    :param chroot: chroot name where we sign packages, affects the hash type --    :param Munch opts: backend config ++  +    @classmethod  +    def create_user_keys(username, projectname, opts, try_indefinitely=False):  +        """create user key pair"""  +        raise NotImplementedError -  --    :type log: logging.Logger ++  +    @classmethod  +    def _sign_one(cls, path, email, hashtype, log):  +        """sign one rpm"""  +        raise NotImplementedError -  --    :raises: :py:class:`backend.exceptions.CoprSignError` failed to sign at least one package --    """ ++  +    @classmethod  +    def _unsign_one(cls, path):  +        # Requires rpm-sign package  +        cmd = ["/usr/bin/rpm", "--delsign", path]  +        handle = Popen(cmd, stdout=PIPE, stderr=PIPE, encoding="utf-8")  +        stdout, stderr = handle.communicate() -+ -+        if handle.returncode != 0: -+            err = CoprSignError( -+                msg="Failed to unsign {}".format(path), -+                return_code=handle.returncode, -+                cmd=cmd, stdout=stdout, stderr=stderr)  -    rpm_list = [  -        os.path.join(path, filename)  -        for filename in os.listdir(path)  -        if filename.endswith(".rpm")  -    ] ++        if handle.returncode != 0: ++            err = CoprSignError( ++                msg="Failed to unsign {}".format(path), ++                return_code=handle.returncode, ++                cmd=cmd, stdout=stdout, stderr=stderr) +  +-    if not rpm_list: +-        return  +            raise err  +  +        return stdout, stderr @@ -364,19 +296,35 @@ index e21653e78..95f674255 100644  +            if filename.endswith(".rpm")  +        ] --    if not rpm_list: --        return +-    hashtype = gpg_hashtype_for_chroot(chroot, opts)  +        if not rpm_list:  +            return --    hashtype = gpg_hashtype_for_chroot(chroot, opts) -+        hashtype = gpg_hashtype_for_chroot(chroot, opts) -   -    try:  -        get_pubkey(username, projectname, log, opts.sign_domain)  -    except CoprSignNoKeyError:  -        create_user_keys(username, projectname, opts, try_indefinitely=True) -+        try: ++        hashtype = gpg_hashtype_for_chroot(chroot, opts) +  +-    errors = []  # tuples (rpm_filepath, exception) +-    for rpm in rpm_list: +         try: +-            _sign_one(rpm, create_gpg_email(username, projectname, opts.sign_domain), +-                      hashtype, log) +-            log.info("signed rpm: %s", rpm) +- +-        except CoprSignError as e: +-            log.exception("failed to sign rpm: %s", rpm) +-            errors.append((rpm, e)) +- +-    if errors: +-        raise CoprSignError("Rpm sign failed, affected rpms: {}" +-                            .format([err[0] for err in errors])) +- +- +-def create_user_keys(username, projectname, opts, try_indefinitely=False): +-    """ +-    Generate a new key-pair at sign host  +            cls.get_pubkey(username, projectname, log, opts.sign_domain)  +        except CoprSignNoKeyError:  +            cls.create_user_keys(username, projectname, opts, try_indefinitely=True) @@ -427,8 +375,10 @@ index e21653e78..95f674255 100644  +            raise CoprSignError("Rpm unsign failed, affected rpms: {}"  +                                .format([err[0] for err in errors]))  + -+def new_signer(opts): -+    if opts.sign_backend == "signatrust": ++@functools.lru_cache(maxsize=1) ++def new_signer(): ++    opts = get_backend_opts() ++    if hasattr(opts, "sign_backend") and opts.sign_backend == "signatrust":  +        Signatrust.signatrust_token = opts.signatrust_token  +        Signatrust.signatrust_host = opts.signatrust_host  +        return Signatrust @@ -466,14 +416,39 @@ index e21653e78..95f674255 100644  +                return_code=returncode,  +                cmd=cmd, stdout=stdout, stderr=stderr) --    errors = []  # tuples (rpm_filepath, exception) --    for rpm in rpm_list: +-    :param username: +-    :param projectname: +-    :param opts: backend config  +        if outfile:  +            with open(outfile, "w") as handle:  +                handle.write(stdout) -+ +  +-    :return: None +-    """ +-    data = { +-        "name_real": "{}_{}".format(username, projectname), +-        "name_email": create_gpg_email(username, projectname, opts.sign_domain) +-    } +- +-    log = get_redis_logger(opts, "sign", "actions") +-    keygen_url = "http://{}/gen_key".format(opts.keygen_host) +-    query = dict(url=keygen_url, data=data, method="post") +-    try: +-        request = SafeRequest(log=log, try_indefinitely=try_indefinitely) +-        response = request.send(**query) +-    except Exception as e: +-        raise CoprKeygenRequestError( +-            msg="Failed to create key-pair for user: {}," +-                " project:{} with error: {}" +-            .format(username, projectname, e), request=query) +- +-    if response.status_code >= 400: +-        raise CoprKeygenRequestError( +-            msg="Failed to create key-pair for user: {}, project:{}, status_code: {}, response: {}" +-            .format(username, projectname, response.status_code, response.text), +-            request=query, response=response)  +        return stdout -+ +   +    @classmethod  +    def _sign_one(cls, path, email, hashtype, log):  +        cmd = [cls.sign_cmd, "-4", "-h", hashtype, "-u", email, "-r", path] @@ -574,18 +549,17 @@ index e21653e78..95f674255 100644  +  +        key_name = cls.get_key_name(username, projectname)  +        url = "{}/api/v1/keys/{}/public_key".format(cls.signatrust_host, key_name) -         try: --            _sign_one(rpm, create_gpg_email(username, projectname, opts.sign_domain), --                      hashtype, log) --            log.info("signed rpm: %s", rpm) ++        try:  +            r = requests.get(url, headers=headers)  +        except Exception as e:  +            raise CoprKeygenRequestError(  +                msg="Failed to get public_key", request="/api/v1/keys/{}/public_key".format(key_name), response=r) --        except CoprSignError as e: --            log.exception("failed to sign rpm: %s", rpm) --            errors.append((rpm, e)) +-def _unsign_one(path): +-    # Requires rpm-sign package +-    cmd = ["/usr/bin/rpm", "--delsign", path] +-    handle = Popen(cmd, stdout=PIPE, stderr=PIPE, encoding="utf-8") +-    stdout, stderr = handle.communicate()  +        if r.status_code == 404:  +            raise CoprSignNoKeyError(  +                    "There are no gpg keys for user {} in keyring".format(username), @@ -624,9 +598,11 @@ index e21653e78..95f674255 100644  +                return_code=returncode,  +                cmd=cmd, stdout=stdout, stderr=stderr) --    if errors: --        raise CoprSignError("Rpm sign failed, affected rpms: {}" --                            .format([err[0] for err in errors])) +-    if handle.returncode != 0: +-        err = CoprSignError( +-            msg="Failed to unsign {}".format(path), +-            return_code=handle.returncode, +-            cmd=cmd, stdout=stdout, stderr=stderr)  +        if stderr:  +            # signatrust client will print error message for one rpm per line  +            failed_list = re.findall(r"failed to sign file (.*.rpm) due to error", stderr) @@ -645,67 +621,20 @@ index e21653e78..95f674255 100644  +        """  +        return +-        raise err  +    @classmethod  +    def _key_existed(cls, username, projectname, opts):  +        """  +            check keyname existence --def create_user_keys(username, projectname, opts, try_indefinitely=False): --    """ --    Generate a new key-pair at sign host +-    return stdout, stderr  +            HEAD /api/v1/keys/  +        """  +        if not cls.prefix:  +            cls.get_prefix() --    :param username: --    :param projectname: --    :param opts: backend config  +        key_name = cls.get_key_name(username, projectname, prefix=False) --    :return: None --    """ --    data = { --        "name_real": "{}_{}".format(username, projectname), --        "name_email": create_gpg_email(username, projectname, opts.sign_domain) --    } -- --    log = get_redis_logger(opts, "sign", "actions") --    keygen_url = "http://{}/gen_key".format(opts.keygen_host) --    query = dict(url=keygen_url, data=data, method="post") --    try: --        request = SafeRequest(log=log, try_indefinitely=try_indefinitely) --        response = request.send(**query) --    except Exception as e: --        raise CoprKeygenRequestError( --            msg="Failed to create key-pair for user: {}," --                " project:{} with error: {}" --            .format(username, projectname, e), request=query) -- --    if response.status_code >= 400: --        raise CoprKeygenRequestError( --            msg="Failed to create key-pair for user: {}, project:{}, status_code: {}, response: {}" --            .format(username, projectname, response.status_code, response.text), --            request=query, response=response) -- -- --def _unsign_one(path): --    # Requires rpm-sign package --    cmd = ["/usr/bin/rpm", "--delsign", path] --    handle = Popen(cmd, stdout=PIPE, stderr=PIPE, encoding="utf-8") --    stdout, stderr = handle.communicate() -- --    if handle.returncode != 0: --        err = CoprSignError( --            msg="Failed to unsign {}".format(path), --            return_code=handle.returncode, --            cmd=cmd, stdout=stdout, stderr=stderr) -- --        raise err -- --    return stdout, stderr -- --  -def unsign_rpms_in_dir(path, opts, log):  -    """  -    :param path: directory with rpms to be signed @@ -800,238 +729,152 @@ index e21653e78..95f674255 100644  +        if res.status_code >= 400:  +            raise CoprKeygenRequestError(  +                msg="Failed to create user payload: {}".format(data), request="/api/v1/keys/", response=res) -diff --git a/tests/daemons/test_log.py b/backend/tests/daemons/test_log.py -index 73b1a777a..c68b7d918 100644 ---- a/tests/daemons/test_log.py -+++ b/tests/daemons/test_log.py -@@ -45,7 +45,8 @@ class TestLog(object): -         self.log_file = os.path.join(self.log_dir, "copr.log") -         self.opts = Munch( -             verbose=False, --            log_dir=self.log_dir -+            log_dir=self.log_dir, -+            sign_backend = "obs-signd" -         ) -         print("\n log dir: {}".format(self.log_dir)) -         self.queue = MagicMock() -diff --git a/tests/daemons/unused_test_job_grab.py b/backend/tests/daemons/unused_test_job_grab.py -index be3e64c56..79f6472b7 100644 ---- a/tests/daemons/unused_test_job_grab.py -+++ b/tests/daemons/unused_test_job_grab.py -@@ -87,6 +87,7 @@ class TestJobGrab(object): -             redis_host="127.0.0.1", -             redis_port=6379, -             redis_db=0, -+            sign_backend = "obs-signd" -         ) -  -         self.queue = MagicMock() -diff --git a/tests/run/test_copr_prune_results.py b/backend/tests/run/test_copr_prune_results.py -index 6620f01b1..8c9ef2369 100644 ---- a/tests/run/test_copr_prune_results.py -+++ b/tests/run/test_copr_prune_results.py -@@ -59,7 +59,8 @@ class TestPruneResults(object): -         self.opts = Munch( -             prune_days=14, -             frontend_base_url = '<frontend_url>', --            destdir=self.testresults_dir -+            destdir=self.testresults_dir, -+            sign_backend = "obs-signd" -         ) -  -     def teardown_method(self, method): -diff --git a/tests/test_action.py b/backend/tests/test_action.py -index 7da3ab09c..4c935eb53 100644 ---- a/tests/test_action.py -+++ b/tests/test_action.py -@@ -57,6 +57,7 @@ class TestAction(object): -             results_baseurl=RESULTS_ROOT_URL, -  -             do_sign=False, -+            sign_backend = "obs-signd", -  -             keygen_host="example.com" -         ) -@@ -136,7 +137,7 @@ class TestAction(object): -     @mock.patch("copr_backend.actions.os.makedirs") -     @mock.patch("copr_backend.actions.copy_tree") -     @mock.patch("copr_backend.actions.os.path.exists") --    @mock.patch("copr_backend.actions.unsign_rpms_in_dir") -+    @mock.patch("copr_backend.sign.ObsSign.unsign_rpms_in_dir") -     @mock.patch("copr_backend.helpers.subprocess.Popen") -     def test_action_handle_forks(self, mc_popen, mc_unsign_rpms_in_dir, -                                  mc_exists, mc_copy_tree, _mc_os_makedirs,  diff --git a/tests/test_background_worker_build.py b/backend/tests/test_background_worker_build.py -index 1dfe19563..258564067 100644 +index 1dfe19563..ba1e5aeab 100644  --- a/tests/test_background_worker_build.py  +++ b/tests/test_background_worker_build.py -@@ -77,6 +77,7 @@ def _reset_build_worker(): -     # Don't waste time with mocking.  We don't want to log anywhere, and we want -     # to let BuildBackgroundWorker adjust the handlers. -     worker.log.handlers = [] -+    worker.opts.sign_backend = "obs-signd" -     return worker -  - def _fake_host(): -diff --git a/tests/test_frontend.py b/backend/tests/test_frontend.py -index eeb8dc8f4..06a6e605b 100644 ---- a/tests/test_frontend.py -+++ b/tests/test_frontend.py -@@ -48,6 +48,7 @@ class TestFrontendClient(object): -         self.opts = Munch( -             frontend_base_url="http://example.com/", -             frontend_auth="12345678", -+            sign_backend = "obs-signd" -         ) -         self.fc = FrontendClient(self.opts) -  -diff --git a/tests/test_helpers.py b/backend/tests/test_helpers.py -index 8da70269d..e23565054 100644 ---- a/tests/test_helpers.py -+++ b/tests/test_helpers.py -@@ -31,6 +31,7 @@ class TestHelpers(object): -         self.opts = Munch( -             redis_db=9, -             redis_port=7777, -+            sign_backend = "obs-signd" -         ) -  -         self.rc = get_redis_connection(self.opts) +@@ -88,6 +88,12 @@ def _fake_host(): +     host.release = mock.MagicMock() +     return host +  ++@pytest.fixture(autouse=True) ++def get_opts(): ++    with pytest.MonkeyPatch.context() as mp: ++        mp.setattr("copr_backend.sign.get_backend_opts", lambda: None) ++        yield mp ++ + @pytest.fixture + def f_build_something(): +     """ +@@ -324,8 +330,8 @@ def test_full_srpm_build(f_build_srpm): +         "00855954/hello-2.8-1.src.rpm") +  +  +-@mock.patch("copr_backend.sign.SIGN_BINARY", "tests/fake-bin-sign") +-@mock.patch("copr_backend.sign._sign_one") ++@mock.patch("copr_backend.sign.ObsSign.sign_cmd", "tests/fake-bin-sign") ++@mock.patch("copr_backend.sign.ObsSign._sign_one") + def test_build_and_sign(mc_sign_one, f_build_rpm_sign_on, caplog): +     config = f_build_rpm_sign_on +     worker = config.bw +@@ -351,7 +357,7 @@ def test_build_and_sign(mc_sign_one, f_build_rpm_sign_on, caplog): +         _, level, _ = record +         assert level <= logging.INFO +  +-@mock.patch("copr_backend.sign.SIGN_BINARY", "tests/fake-bin-sign") ++@mock.patch("copr_backend.sign.ObsSign.sign_cmd", "tests/fake-bin-sign") + @mock.patch("copr_backend.sign._sign_one") + @_patch_bwbuild_object("sign_rpms_in_dir") + def test_sign_built_packages_exception(mc_sign_rpms, mc_sign_one, +@@ -452,7 +458,7 @@ def test_invalid_job_info(f_build_rpm_case, caplog): +  + @mock.patch("copr_backend.vm_alloc.time.sleep", mock.MagicMock()) + @_patch_bwbuild_object("CANCEL_CHECK_PERIOD", 0.5) +-@mock.patch("copr_backend.sign.SIGN_BINARY", "tests/fake-bin-sign") ++@mock.patch("copr_backend.sign.ObsSign.sign_cmd", "tests/fake-bin-sign") + def test_cancel_build_on_vm_allocation(f_build_rpm_sign_on, caplog): +     config = f_build_rpm_sign_on +     worker = config.bw +@@ -513,7 +519,7 @@ class _CancelFunction(): +             time.sleep(0.25) +  + @_patch_bwbuild_object("CANCEL_CHECK_PERIOD", 0.5) +-@mock.patch("copr_backend.sign.SIGN_BINARY", "tests/fake-bin-sign") ++@mock.patch("copr_backend.sign.ObsSign.sign_cmd", "tests/fake-bin-sign") + def test_cancel_build_on_tail_log_no_ssh(f_build_rpm_sign_on, caplog): +     config = f_build_rpm_sign_on +     worker = config.bw +@@ -542,7 +548,7 @@ def test_cancel_build_on_tail_log_no_ssh(f_build_rpm_sign_on, caplog): +     assert "canceled stdout" in log +  + @_patch_bwbuild_object("CANCEL_CHECK_PERIOD", 0.5) +-@mock.patch("copr_backend.sign.SIGN_BINARY", "tests/fake-bin-sign") ++@mock.patch("copr_backend.sign.ObsSign.sign_cmd", "tests/fake-bin-sign") + def test_cancel_before_vm(f_build_rpm_sign_on, caplog): +     config = f_build_rpm_sign_on +     worker = config.bw +@@ -558,7 +564,7 @@ def test_cancel_before_vm(f_build_rpm_sign_on, caplog): +     assert_logs_dont_exist(["Releasing VM back to pool"], caplog) +  + @_patch_bwbuild_object("CANCEL_CHECK_PERIOD", 0.5) +-@mock.patch("copr_backend.sign.SIGN_BINARY", "tests/fake-bin-sign") ++@mock.patch("copr_backend.sign.ObsSign.sign_cmd", "tests/fake-bin-sign") + def test_cancel_before_start(f_build_rpm_sign_on, caplog): +     config = f_build_rpm_sign_on +     worker = config.bw +@@ -578,7 +584,7 @@ def test_cancel_before_start(f_build_rpm_sign_on, caplog): +     ], caplog) +  + @_patch_bwbuild_object("CANCEL_CHECK_PERIOD", 0.5) +-@mock.patch("copr_backend.sign.SIGN_BINARY", "tests/fake-bin-sign") ++@mock.patch("copr_backend.sign.ObsSign.sign_cmd", "tests/fake-bin-sign") + def test_build_retry(f_build_rpm_sign_on): +     config = f_build_rpm_sign_on +     worker = config.bw +@@ -650,7 +656,7 @@ def test_fe_failed_start(f_build_rpm_sign_on, caplog): +     assert worker.redis_get_worker_flag("status") == "done" +  + @_patch_bwbuild_object("CANCEL_CHECK_PERIOD", 0.5) +-@mock.patch("copr_backend.sign.SIGN_BINARY", "tests/fake-bin-sign") ++@mock.patch("copr_backend.sign.ObsSign.sign_cmd", "tests/fake-bin-sign") + def test_cancel_script_failure(f_build_rpm_sign_on, caplog): +     config = f_build_rpm_sign_on +     worker = config.bw +@@ -672,7 +678,7 @@ def test_cancel_script_failure(f_build_rpm_sign_on, caplog): +     ], caplog) +  + @_patch_bwbuild_object("CANCEL_CHECK_PERIOD", 0.5) +-@mock.patch("copr_backend.sign.SIGN_BINARY", "tests/fake-bin-sign") ++@mock.patch("copr_backend.sign.ObsSign.sign_cmd", "tests/fake-bin-sign") + def test_cancel_build_during_log_download(f_build_rpm_sign_on, caplog): +     config = f_build_rpm_sign_on +     worker = config.bw  diff --git a/tests/test_sign.py b/backend/tests/test_sign.py -index bf2dd1b8c..ebb8f2be3 100644 +index bf2dd1b8c..13a7a2ebc 100644  --- a/tests/test_sign.py  +++ b/tests/test_sign.py -@@ -10,10 +10,10 @@ import pytest -  - from copr_backend.exceptions import CoprSignError, CoprSignNoKeyError, CoprKeygenRequestError +@@ -12,13 +12,16 @@ from copr_backend.exceptions import CoprSignError, CoprSignNoKeyError, CoprKeyge   from copr_backend.sign import ( --    get_pubkey, _sign_one, sign_rpms_in_dir, create_user_keys, -+    new_signer, +     get_pubkey, _sign_one, sign_rpms_in_dir, create_user_keys,       gpg_hashtype_for_chroot,  -    call_sign_bin, ++    call_sign_bin   ) -+from copr_backend.constants import DEF_SIGN_BACKEND ++from copr_backend import helpers   STDOUT = "stdout"   STDERR = "stderr" -@@ -33,6 +33,8 @@ class TestSign(object): -         self.opts = Munch(keygen_host="example.com") -         self.opts.gently_gpg_sha256 = False -         self.opts.sign_domain = "fedorahosted.org" -+        self.opts.sign_backend = DEF_SIGN_BACKEND -+        self.signer = new_signer(self.opts) -  -     def teardown_method(self, method): -         if self.tmp_dir_path: -@@ -60,7 +62,7 @@ class TestSign(object): -         mc_handle.returncode = 0 -         mc_popen.return_value = mc_handle -  --        result = get_pubkey(self.username, self.projectname, MagicMock(), self.opts.sign_domain) -+        result = self.signer.get_pubkey(self.username, self.projectname, MagicMock(), self.opts.sign_domain) -         assert result == STDOUT -         assert mc_popen.call_args[0][0] == ['/bin/sign', '-u', self.usermail, '-p'] -  -@@ -70,7 +72,7 @@ class TestSign(object): -         mc_popen.side_effect = IOError(STDERR) -  -         with pytest.raises(CoprSignError): --            get_pubkey(self.username, self.projectname, MagicMock(), self.opts.sign_domain) -+            self.signer.get_pubkey(self.username, self.projectname, MagicMock(), self.opts.sign_domain) -  -  -     @mock.patch("copr_backend.sign.time.sleep") -@@ -82,7 +84,7 @@ class TestSign(object): -         mc_popen.return_value = mc_handle -  -         with pytest.raises(CoprSignNoKeyError) as err: --            get_pubkey(self.username, self.projectname, MagicMock(), self.opts.sign_domain) -+            self.signer.get_pubkey(self.username, self.projectname, MagicMock(), self.opts.sign_domain) -  -         assert "There are no gpg keys for user foo in keyring" in str(err) -  -@@ -95,7 +97,7 @@ class TestSign(object): -         mc_popen.return_value = mc_handle -  -         with pytest.raises(CoprSignError) as err: --            get_pubkey(self.username, self.projectname, MagicMock(), self.opts.sign_domain) -+            self.signer.get_pubkey(self.username, self.projectname, MagicMock(), self.opts.sign_domain) -  -         assert "Failed to get user pubkey" in str(err) -  -@@ -108,7 +110,7 @@ class TestSign(object): -         outfile_path = os.path.join(self.tmp_dir_path, "out.pub") -         assert not os.path.exists(outfile_path) --        result = get_pubkey(self.username, self.projectname, MagicMock(), -+        result = self.signer.get_pubkey(self.username, self.projectname, MagicMock(), -                             self.opts.sign_domain, outfile_path) -         assert result == STDOUT -         assert os.path.exists(outfile_path) -@@ -124,7 +126,7 @@ class TestSign(object): -         mc_popen.return_value = mc_handle -         fake_path = "/tmp/pkg.rpm" --        result = _sign_one(fake_path, self.usermail, "sha1", MagicMock()) -+        result = self.signer._sign_one(fake_path, self.usermail, "sha1", MagicMock()) -         assert STDOUT, STDERR == result -  -         expected_cmd = ['/bin/sign', "-4", "-h", "sha1", "-u", self.usermail, -@@ -137,7 +139,7 @@ class TestSign(object): -  -         fake_path = "/tmp/pkg.rpm" -         with pytest.raises(CoprSignError): --            _sign_one(fake_path, self.usermail, "sha256", MagicMock()) -+            self.signer._sign_one(fake_path, self.usermail, "sha256", MagicMock()) ++ ++ + class TestSign(object): +     # pylint: disable=too-many-public-methods -     @mock.patch("copr_backend.sign.time.sleep") -     @mock.patch("copr_backend.sign.Popen") -@@ -149,12 +151,11 @@ class TestSign(object): +@@ -38,6 +41,12 @@ class TestSign(object): +         if self.tmp_dir_path: +             shutil.rmtree(self.tmp_dir_path) -         fake_path = "/tmp/pkg.rpm" -         with pytest.raises(CoprSignError): --            _sign_one(fake_path, self.usermail, "sha256", MagicMock()) -+            self.signer._sign_one(fake_path, self.usermail, "sha256", MagicMock()) ++    @pytest.fixture(autouse=True) ++    def get_opts(self): ++        with pytest.MonkeyPatch.context() as mp: ++            mp.setattr("copr_backend.sign.get_backend_opts", lambda: None) ++            yield mp ++ +     @pytest.fixture +     def tmp_dir(self): +         subdir = "test_createrepo_{}".format(time.time()) +@@ -54,7 +63,7 @@ class TestSign(object): +                 handle.write("1") --    @staticmethod -     @mock.patch("copr_backend.sign.time.sleep")       @mock.patch("copr_backend.sign.Popen") --    def test_call_sign_bin_repeatedly(mc_popen, _sleep): -+    def test_call_sign_bin_repeatedly(self, mc_popen, _sleep): -         """ -         Test that we attempt to run /bin/sign multiple times if it returns -         non-zero exit status -@@ -163,13 +164,13 @@ class TestSign(object): +-    def test_get_pubkey(self, mc_popen): ++    def test_get_pubkey(self, mc_popen, get_opts): +         mc_handle = MagicMock()           mc_handle.communicate.return_value = (STDOUT, STDERR) -         mc_handle.returncode = 1 -         mc_popen.return_value = mc_handle --        call_sign_bin(cmd=[], log=MagicMock()) -+        self.signer.call_sign_bin(cmd=[], log=MagicMock()) -         assert mc_popen.call_count == 3 -  -     @mock.patch("copr_backend.sign.SafeRequest.send") -     def test_create_user_keys(self, mc_request): -         mc_request.return_value.status_code = 200 --        create_user_keys(self.username, self.projectname, self.opts) -+        self.signer.create_user_keys(self.username, self.projectname, self.opts) -  -         assert mc_request.called -         expected_call = mock.call( -@@ -183,7 +184,7 @@ class TestSign(object): -     def test_create_user_keys_error_1(self, mc_request): -         mc_request.side_effect = IOError() -         with pytest.raises(CoprKeygenRequestError) as err: --            create_user_keys(self.username, self.projectname, self.opts) -+            self.signer.create_user_keys(self.username, self.projectname, self.opts) -  -         assert "Failed to create key-pair" in str(err) -  -@@ -195,16 +196,16 @@ class TestSign(object): -             mc_request.return_value.content = "error: {}".format(code) -  -             with pytest.raises(CoprKeygenRequestError) as err: --                create_user_keys(self.username, self.projectname, self.opts) -+                self.signer.create_user_keys(self.username, self.projectname, self.opts) +         mc_handle.returncode = 0 +@@ -198,9 +207,9 @@ class TestSign(object): +                 create_user_keys(self.username, self.projectname, self.opts)               assert "Failed to create key-pair for user: foo, project:bar" in str(err)  -    @mock.patch("copr_backend.sign._sign_one") @@ -1043,12 +886,7 @@ index bf2dd1b8c..ebb8f2be3 100644       def test_sign_rpms_id_dir_nothing(self, mc_gp, mc_cuk, mc_so,                                         tmp_dir):           # empty target dir doesn't produce error --        sign_rpms_in_dir(self.username, self.projectname, -+        self.signer.sign_rpms_in_dir(self.username, self.projectname, -                          self.tmp_dir_path, "epel-8-x86_64", self.opts, -                          log=MagicMock()) -  -@@ -212,13 +213,13 @@ class TestSign(object): +@@ -212,9 +221,9 @@ class TestSign(object):           assert not mc_cuk.called           assert not mc_so.called @@ -1061,12 +899,7 @@ index bf2dd1b8c..ebb8f2be3 100644       def test_sign_rpms_id_dir_ok(self, mc_gp, mc_cuk, mc_so,                                         tmp_dir, tmp_files): --        sign_rpms_in_dir(self.username, self.projectname, -+        self.signer.sign_rpms_in_dir(self.username, self.projectname, -                          self.tmp_dir_path, "fedora-rawhide-x86_64", -                          self.opts, log=MagicMock()) -  -@@ -234,15 +235,15 @@ class TestSign(object): +@@ -234,9 +243,9 @@ class TestSign(object):                   assert os.path.join(self.tmp_dir_path, name) in pathes           assert len(pathes) == count @@ -1079,14 +912,7 @@ index bf2dd1b8c..ebb8f2be3 100644       def test_sign_rpms_id_dir_error_on_pubkey(               self, mc_gp, mc_cuk, mc_so, tmp_dir, tmp_files): -         mc_gp.side_effect = CoprSignError("foobar") -         with pytest.raises(CoprSignError): --            sign_rpms_in_dir(self.username, self.projectname, -+            self.signer.sign_rpms_in_dir(self.username, self.projectname, -                              self.tmp_dir_path, "epel-7-x86_64", self.opts, -                              log=MagicMock()) -  -@@ -250,15 +251,15 @@ class TestSign(object): +@@ -250,9 +259,9 @@ class TestSign(object):           assert not mc_cuk.called           assert not mc_so.called @@ -1099,14 +925,7 @@ index bf2dd1b8c..ebb8f2be3 100644       def test_sign_rpms_id_dir_no_pub_key(               self, mc_gp, mc_cuk, mc_so, tmp_dir, tmp_files): -         mc_gp.side_effect = CoprSignNoKeyError("foobar") -  --        sign_rpms_in_dir(self.username, self.projectname, -+        self.signer.sign_rpms_in_dir(self.username, self.projectname, -                          self.tmp_dir_path, "rhel-7-x86_64", self.opts, -                          log=MagicMock()) -  -@@ -266,9 +267,9 @@ class TestSign(object): +@@ -266,9 +275,9 @@ class TestSign(object):           assert mc_cuk.called           assert mc_so.called @@ -1119,16 +938,7 @@ index bf2dd1b8c..ebb8f2be3 100644       def test_sign_rpms_id_dir_sign_error_one(               self, mc_gp, mc_cuk, mc_so, tmp_dir, tmp_files): -@@ -276,7 +277,7 @@ class TestSign(object): -             None, CoprSignError("foobar"), None -         ] -         with pytest.raises(CoprSignError): --            sign_rpms_in_dir(self.username, self.projectname, -+            self.signer.sign_rpms_in_dir(self.username, self.projectname, -                              self.tmp_dir_path, "fedora-36-x86_64", self.opts, -                              log=MagicMock()) -  -@@ -285,15 +286,15 @@ class TestSign(object): +@@ -285,9 +294,9 @@ class TestSign(object):           assert mc_so.called @@ -1141,10 +951,3 @@ index bf2dd1b8c..ebb8f2be3 100644       def test_sign_rpms_id_dir_sign_error_all(               self, mc_gp, mc_cuk, mc_so, tmp_dir, tmp_files): -         mc_so.side_effect = CoprSignError("foobar") -         with pytest.raises(CoprSignError): --            sign_rpms_in_dir(self.username, self.projectname, -+            self.signer.sign_rpms_in_dir(self.username, self.projectname, -                              self.tmp_dir_path, "fedora-36-i386", self.opts, -                              log=MagicMock()) -   | 
