From eec43146f9c7e9ca143f1bae30afd6fb00e674d8 Mon Sep 17 00:00:00 2001
From: CoprDistGit <infra@openeuler.org>
Date: Sun, 23 Mar 2025 04:09:17 +0000
Subject: automatic import of copr-backend

---
 copr-backend.spec                |   2 +-
 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())
- 
-- 
cgit v1.2.3