#438 Allow per-package chroot-blacklisting by wildcard patterns
Merged 5 years ago by msuchy. Opened 5 years ago by praiskup.
Unknown source chroot-blacklist  into  master

@@ -499,6 +499,7 @@

                  self.log.exception(e)

  

  

+ # TODO: sync with ActionTypeEnum from common

  class ActionType(object):

      DELETE = 0

      RENAME = 1

@@ -7,11 +7,11 @@

  import glob

  from setproctitle import setproctitle

  

- from ..exceptions import MockRemoteError, CoprWorkerError, VmError

+ from ..exceptions import MockRemoteError, CoprWorkerError, VmError, CoprBackendSrpmError

  from ..mockremote import MockRemote

  from ..constants import BuildStatus, build_log_format

  from ..helpers import register_build_result, get_redis_logger, \

-     local_file_logger, run_cmd

+     local_file_logger, run_cmd, pkg_name_evr

  

  from ..msgbus import MsgBusStomp, MsgBusFedmsg

  from ..sshcmd import SSHConnectionError
@@ -264,17 +264,17 @@

          self.log.info("Built packages:\n%s", built_packages)

          return built_packages

  

-     def get_srpm_url(self, job):

-         self.log.info("Retrieving srpm URL for %s", job.results_dir)

-         try:

-             pattern = os.path.join(job.results_dir, '*.src.rpm')

-             srpm_name = os.path.basename(glob.glob(pattern)[0])

-             srpm_url = os.path.join(job.results_dir_url, srpm_name)

-         except IndexError:

-             srpm_url = ""

- 

+     def get_srpm_build_details(self, job):

+         build_details = {'srpm_url': ''}

+         self.log.info("Retrieving srpm URL from %s", job.results_dir)

+         pattern = os.path.join(job.results_dir, '*.src.rpm')

+         srpm_file = glob.glob(pattern)[0]

+         srpm_name = os.path.basename(srpm_file)

+         srpm_url = os.path.join(job.results_dir_url, srpm_name)

+         build_details['pkg_name'], build_details['pkg_version'] = pkg_name_evr(srpm_file)

+         build_details['srpm_url'] = srpm_url

          self.log.info("SRPM URL: %s", srpm_url)

-         return srpm_url

+         return build_details

  

      def get_build_details(self, job):

          """
@@ -283,7 +283,7 @@

          """

          try:

              if job.chroot == "srpm-builds":

-                 build_details = { "srpm_url": self.get_srpm_url(job) }

+                 build_details = self.get_srpm_build_details(job)

              else:

                  build_details = { "built_packages": self.collect_built_packages(job) }

              self.log.info("build details: %s", build_details)

@@ -88,6 +88,8 @@

  class CoprSpawnFailError(CoprBackendError):

      pass

  

+ class CoprBackendSrpmError(CoprBackendError):

+     pass

  

  class VmError(CoprBackendError):

      """

file modified
+29 -1
@@ -32,7 +32,7 @@

  from copr.client import CoprClient

  from backend.constants import DEF_BUILD_USER, DEF_BUILD_TIMEOUT, DEF_CONSECUTIVE_FAILURE_THRESHOLD, \

      CONSECUTIVE_FAILURE_REDIS_KEY, default_log_format

- from backend.exceptions import CoprBackendError

+ from backend.exceptions import CoprBackendError, CoprBackendSrpmError

  

  from . import constants

  
@@ -503,3 +503,31 @@

          # to the previous project

          for h in build_logger.handlers[:]:

              build_logger.removeHandler(h)

+ 

+ def pkg_name_evr(srpm_path):

+     """

+     Queries a package for its name and evr (epoch:version-release)

+     """

+     cmd = ['rpm', '-qp', '--nosignature', '--qf',

+            '%{NAME} %{EPOCH} %{VERSION} %{RELEASE}', srpm_path]

+ 

+     try:

+         result = run_cmd(cmd)

+     except OSError as e:

+         raise CoprBackendSrpmError(str(e))

+ 

+     if result.returncode != 0:

+         raise CoprBackendSrpmError('Error querying srpm: %s' % error)

+ 

+     try:

+         name, epoch, version, release = result.stdout.split(" ")

+     except ValueError as e:

+         raise CoprBackendSrpmError(str(e))

+ 

+     # Epoch is an integer or '(none)' if not set

+     if epoch.isdigit():

+         evr = "{}:{}-{}".format(epoch, version, release)

+     else:

+         evr = "{}-{}".format(version, release)

+ 

+     return name, evr

@@ -198,36 +198,3 @@

          raise RunCommandException(result.stderr)

  

      return result

- 

- 

- def pkg_name_evr(srpm_path):

-     """

-     Queries a package for its name and evr (epoch:version-release)

-     """

-     log.debug("Obtaining package name and version.")

-     cmd = ['rpm', '-qp', '--nosignature', '--qf',

-            '%{NAME} %{EPOCH} %{VERSION} %{RELEASE}', srpm_path]

- 

-     try:

-         proc = subprocess.Popen(

-             cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,

-             encoding='utf-8')

-         output, error = proc.communicate()

-     except OSError as e:

-         raise SrpmQueryException(str(e))

- 

-     if proc.returncode != 0:

-         raise SrpmQueryException('Error querying srpm: %s' % error)

- 

-     try:

-         name, epoch, version, release = output.split(" ")

-     except ValueError as e:

-         raise SrpmQueryException(str(e))

- 

-     # Epoch is an integer or '(none)' if not set

-     if epoch.isdigit():

-         evr = "{}:{}-{}".format(epoch, version, release)

-     else:

-         evr = "{}-{}".format(version, release)

- 

-     return name, evr

@@ -20,6 +20,7 @@

              task.project = task_dict["project"]

              task.branches = task_dict["branches"]

              task.srpm_url = task_dict["srpm_url"]

+             task.pkg_name = task_dict["pkg_name"]

          except (KeyError, ValueError) as e:

              raise PackageImportException(str(e))

  

@@ -80,7 +80,8 @@

                  self.opts,

                  task.repo_namespace,

                  task.branches,

-                 srpm_path

+                 srpm_path,

+                 task.pkg_name,

              ))

          except PackageImportException as e:

              log.exception("Exception raised during package import.")

@@ -153,7 +153,7 @@

  

  

  @helpers.single_run(import_lock)

- def import_package(opts, namespace, branches, srpm_path):

+ def import_package(opts, namespace, branches, srpm_path, pkg_name):

      """

      Import package into a DistGit repo for the given branches.

  
@@ -163,11 +163,8 @@

      :param str srpm_path: path to the srpm file

  

      :return Munch: resulting import data:

-         (branch_commits, reponame, pkg_name, pkg_evr)

+         (branch_commits, reponame, pkg_name)

      """

-     pkg_name, pkg_evr = helpers.pkg_name_evr(srpm_path)

-     log.debug("pkg_name: " + str(pkg_name))

-     log.debug("pkg_evr: " + str(pkg_evr))

  

      reponame = "{}/{}".format(namespace, pkg_name)

      setup_git_repo(reponame, branches)
@@ -212,8 +209,6 @@

      helpers.run_cmd(['git', 'config', 'user.email', opts.git_user_email])

  

      message = "automatic import of {}".format(pkg_name)

-     if pkg_evr:

-         message += " {}".format(pkg_evr)

  

      branch_commits = {}

      for branch in branches:
@@ -253,6 +248,4 @@

      return munch.Munch(

          branch_commits=branch_commits,

          reponame=reponame,

-         pkg_name=pkg_name,

-         pkg_evr=pkg_evr,

      )

file modified
+2
@@ -49,6 +49,7 @@

  

              "branches": [ self.BRANCH ],

              "srpm_url": "http://example.com/pkg.src.rpm",

+             "pkg_name": "pkg",

          }

          self.upload_task_data = {

              "build_id": 124,
@@ -57,6 +58,7 @@

  

              "branches": [ self.BRANCH ],

              "srpm_url": "http://front/tmp/tmp_2/pkg_2.src.rpm",

+             "pkg_name": "pkg_2",

          }

  

          self.url_task = import_task.ImportTask.from_dict(self.url_task_data)

@@ -188,8 +188,9 @@

  class TestMerging(object):

  

      def commit_to_branches(self, to_branches, opts, version):

-         srpm_path, packge_desc = get_srpm(version)

-         import_result = import_package(opts, 'bob/blah', to_branches, srpm_path)

+         srpm_path, package_desc = get_srpm(version)

+         name = package_desc['package_name']

+         import_result = import_package(opts, 'bob/blah', to_branches, srpm_path, name)

          return import_result['branch_commits']

  

      def setup_method(self, method):

@@ -93,26 +93,21 @@

          mc_pyrpkg_commands.return_value = mc_cmd

          mc_cmd.commithash = '1234'

  

-         mc_helpers.pkg_name_evr = MagicMock(return_value = ('pkg_name', '1.2'))

- 

          namespace = 'somenamespace'

          branches = ['f25', 'f26']

-         result = import_package(self.opts, namespace, branches, 'some_srpm_path')

+         result = import_package(self.opts, namespace, branches,

+                 'some_srpm_path', 'pkg_name')

          expected_result = Munch({

              'branch_commits': {'f26': '1234', 'f25': '1234'},

              'reponame': 'somenamespace/pkg_name',

-             'pkg_name': 'pkg_name',

-             'pkg_evr': '1.2',

          })

          assert (result == expected_result)

  

          mc_cmd.push.side_effect = rpkgError

-         result = import_package(self.opts, namespace, branches, 'some_srpm_path')

+         result = import_package(self.opts, namespace, branches, 'some_srpm_path', 'pkg_name')

          expected_result = Munch({

              'branch_commits': {},

              'reponame': 'somenamespace/pkg_name',

-             'pkg_name': 'pkg_name',

-             'pkg_evr': '1.2',

          })

          assert (result == expected_result)

  

@@ -0,0 +1,22 @@

+ """

+ package.chroot_blacklist

+ 

+ Revision ID: 51716ab39d37

+ Revises: c28451aaed50

+ Create Date: 2018-10-04 21:24:41.498242

+ """

+ 

+ # revision identifiers, used by Alembic.

+ revision = '51716ab39d37'

+ down_revision = 'c28451aaed50'

+ 

+ from alembic import op

+ import sqlalchemy as sa

+ 

+ 

+ def upgrade():

+     op.add_column('package', sa.Column('chroot_blacklist_raw', sa.Text(), nullable=True))

I know that this is an easier way, but .... can you create 1:M table chroot_blacklist(package_id, chroot) ?

I don't think it would have any benefits; at least as long as we plan to keep the fnmatch() logic. Can you elaborate?

+ 

+ 

+ def downgrade():

+     op.drop_column('package', 'chroot_blacklist_raw')

@@ -74,7 +74,7 @@

  from coprs.views.webhooks_ns import webhooks_general

  

  

- from .context_processors import include_banner, inject_fedmenu

+ from .context_processors import include_banner, inject_fedmenu, counter_processor

  

  setup_log()

  

@@ -69,3 +69,16 @@

              })

  

      return dict(login_menu=menu)

+ 

+ @app.context_processor

+ def counter_processor():

+     def counter(name):

+         if not 'counters' in flask.g:

+             flask.g.counters = {}

+         if not name in flask.g.counters:

+             flask.g.counters[name] = 0

+ 

+         flask.g.counters[name] += 1

+         return str(flask.g.counters[name])

+ 

+     return dict(counter=counter)

@@ -6,6 +6,7 @@

  import json

  

  from flask_wtf.file import FileRequired, FileField

+ from fnmatch import fnmatch

  

  try: # get rid of deprecation warning with newer flask_wtf

      from flask_wtf import FlaskForm
@@ -15,7 +16,7 @@

  from coprs import constants

  from coprs import helpers

  from coprs import models

- from coprs.logic.coprs_logic import CoprsLogic

+ from coprs.logic.coprs_logic import CoprsLogic, MockChrootsLogic

  from coprs.logic.users_logic import UsersLogic

  from coprs import exceptions

  
@@ -170,13 +171,10 @@

              return

  

          selected = set(field.data.split())

-         enabled = set(self.chroots_list())

+         enabled = set(MockChrootsLogic.active_names())

  

-         if not (selected <= enabled):

-             raise wtforms.ValidationError("Such chroot is not enabled: {}".format(", ".join(selected - enabled)))

- 

-     def chroots_list(self):

-         return [c.name for c in models.MockChroot.query.filter(models.MockChroot.is_active).all()]

+         if selected - enabled:

+             raise wtforms.ValidationError("Such chroot is not available: {}".format(", ".join(selected - enabled)))

  

  

  class NameNotNumberValidator(object):
@@ -312,17 +310,13 @@

                          have_any = True

                  return have_any

  

-         F.chroots_list = list(map(lambda x: x.name,

-                              models.MockChroot.query.filter(

-                                  models.MockChroot.is_active == True

-                              ).all()))

+         F.chroots_list = MockChrootsLogic.active_names()

          F.chroots_list.sort()

          # sets of chroots according to how we should print them in columns

          F.chroots_sets = {}

          for ch in F.chroots_list:

              checkbox_default = False

-             if mock_chroots and ch in map(lambda x: x.name,

-                                           mock_chroots):

+             if mock_chroots and ch in [x.name for x in mock_chroots]:

                  checkbox_default = True

  

              setattr(F, ch, wtforms.BooleanField(ch, default=checkbox_default, false_values=FALSE_VALUES))
@@ -404,11 +398,48 @@

          return form

  

  

+ def cleanup_chroot_blacklist(string):

+     if not string:

+         return string

+     fields = [x.lstrip().rstrip() for x in string.split(',')]

+     return ', '.join(fields)

+ 

+ 

+ def validate_chroot_blacklist(form, field):

+     if field.data:

+         string = field.data

+         fields = [x.lstrip().rstrip() for x in string.split(',')]

+         for field in fields:

+             pattern = r'^[a-z0-9-*]+$'

+             if not re.match(pattern, field):

+                 raise wtforms.ValidationError('Pattern "{0}" does not match "{1}"'.format(field, pattern))

+ 

+             matched = set()

+             all_chroots = MockChrootsLogic.active_names()

+             for chroot in all_chroots:

+                 if fnmatch(chroot, field):

+                     matched.add(chroot)

+ 

+             if not matched:

+                 raise wtforms.ValidationError('no chroot matched by pattern "{0}"'.format(field))

+ 

+             if matched == all_chroots:

+                 raise wtforms.ValidationError('patterns are black-listing all chroots')

+ 

+ 

  class BasePackageForm(FlaskForm):

      package_name = wtforms.StringField(

          "Package name",

          validators=[wtforms.validators.DataRequired()])

      webhook_rebuild = wtforms.BooleanField(default=False, false_values=FALSE_VALUES)

+     chroot_blacklist = wtforms.StringField(

+         "Chroot blacklist",

+         filters=[cleanup_chroot_blacklist],

+         validators=[

+             wtforms.validators.Optional(),

+             validate_chroot_blacklist,

+         ],

+     )

  

  

  class PackageFormScm(BasePackageForm):
@@ -696,7 +727,7 @@

  

  

  class BaseBuildFormFactory(object):

-     def __new__(cls, active_chroots, form):

+     def __new__(cls, active_chroots, form, package=None):

          class F(form):

              @property

              def selected_chroots(self):
@@ -730,11 +761,18 @@

          # overrides BasePackageForm.package_name and is unused for building

          F.package_name = wtforms.StringField()

  

-         F.chroots_list = list(map(lambda x: x.name, active_chroots))

+         # fill chroots based on project settings

+         F.chroots_list = [x.name for x in active_chroots]

          F.chroots_list.sort()

          F.chroots_sets = {}

+ 

+         package_chroots = set(F.chroots_list)

+         if package:

+             package_chroots = set([ch.name for ch in package.chroots])

+ 

          for ch in F.chroots_list:

-             setattr(F, ch, wtforms.BooleanField(ch, default=True, false_values=FALSE_VALUES))

+             default = ch in package_chroots

+             setattr(F, ch, wtforms.BooleanField(ch, default=default, false_values=FALSE_VALUES))

              if ch[0] in F.chroots_sets:

                  F.chroots_sets[ch[0]].append(ch)

              else:
@@ -743,8 +781,8 @@

  

  

  class BuildFormScmFactory(object):

-     def __new__(cls, active_chroots):

-         return BaseBuildFormFactory(active_chroots, PackageFormScm)

+     def __new__(cls, active_chroots, package=None):

+         return BaseBuildFormFactory(active_chroots, PackageFormScm, package)

  

  

  class BuildFormTitoFactory(object):
@@ -764,13 +802,13 @@

  

  

  class BuildFormPyPIFactory(object):

-     def __new__(cls, active_chroots):

-         return BaseBuildFormFactory(active_chroots, PackageFormPyPI)

+     def __new__(cls, active_chroots, package=None):

+         return BaseBuildFormFactory(active_chroots, PackageFormPyPI, package)

  

  

  class BuildFormRubyGemsFactory(object):

-     def __new__(cls, active_chroots):

-         return BaseBuildFormFactory(active_chroots, PackageFormRubyGems)

+     def __new__(cls, active_chroots, package=None):

+         return BaseBuildFormFactory(active_chroots, PackageFormRubyGems, package)

  

  

  class BuildFormDistGitFactory(object):
@@ -788,8 +826,8 @@

  

  

  class BuildFormCustomFactory(object):

-     def __new__(cls, active_chroots):

-         return BaseBuildFormFactory(active_chroots, PackageFormCustom)

+     def __new__(cls, active_chroots, package=None):

+         return BaseBuildFormFactory(active_chroots, PackageFormCustom, package)

  

  

  class BuildFormUrlFactory(object):

@@ -28,6 +28,7 @@

  from coprs.logic.actions_logic import ActionsLogic

  from coprs.models import BuildChroot

  from .coprs_logic import MockChrootsLogic

+ from coprs.logic.packages_logic import PackagesLogic

  

  log = app.logger

  
@@ -648,9 +649,8 @@

          :type batch: models.Batch

          :rtype: models.Build

          """

-         if chroot_names is None:

-             chroots = [c for c in copr.active_chroots]

-         else:

+         chroots = None

+         if chroot_names:

              chroots = []

              for chroot in copr.active_chroots:

                  if chroot.name in chroot_names:
@@ -708,9 +708,6 @@

          if skip_import and srpm_url:

              chroot_status = StatusEnum("pending")

              source_status = StatusEnum("succeeded")

-         elif srpm_url:

-             chroot_status = StatusEnum("waiting")

-             source_status = StatusEnum("importing")

          else:

              chroot_status = StatusEnum("waiting")

              source_status = StatusEnum("pending")
@@ -735,12 +732,8 @@

  

          db.session.add(build)

  

-         # add BuildChroot object for each active (or selected) chroot

-         # this copr is assigned to

-         if not chroots:

-             chroots = copr.active_chroots

- 

          for chroot in chroots:

+             # Chroots were explicitly set per-build.

              git_hash = None

              if git_hashes:

                  git_hash = git_hashes.get(chroot.name)
@@ -785,9 +778,8 @@

          )

          db.session.add(build)

  

-         chroots = package.copr.active_chroots

          status = StatusEnum("waiting")

-         for chroot in chroots:

+         for chroot in package.chroots:

              buildchroot = models.BuildChroot(

                  build=build,

                  status=status,
@@ -858,20 +850,50 @@

          """

          log.info("Updating build {} by: {}".format(build.id, upd_dict))

  

-         # update build

-         for attr in ["built_packages", "srpm_url"]:

+         # create the package if it doesn't exist

+         pkg_name = upd_dict.get('pkg_name', None)

+         if pkg_name:

+             if not PackagesLogic.get(build.copr_dir.id, pkg_name).first():

+                 try:

+                     package = PackagesLogic.add(

+                         build.copr.user, build.copr_dir,

+                         pkg_name, build.source_type, build.source_json)

+                     db.session.add(package)

+                     db.session.commit()

+                 except (sqlalchemy.exc.IntegrityError, exceptions.DuplicateException) as e:

+                     app.logger.exception(e)

+                     db.session.rollback()

+                     return

+             build.package = PackagesLogic.get(build.copr_dir.id, pkg_name).first()

+ 

+         for attr in ["built_packages", "srpm_url", "pkg_version"]:

              value = upd_dict.get(attr, None)

              if value:

                  setattr(build, attr, value)

  

          # update source build status

-         if upd_dict.get("task_id") == build.task_id:

+         if str(upd_dict.get("task_id")) == str(build.task_id):

              build.result_dir = upd_dict.get("result_dir", "")

  

-             if upd_dict.get("status") == StatusEnum("succeeded"):

+             new_status = upd_dict.get("status")

+             if new_status == StatusEnum("succeeded"):

                  new_status = StatusEnum("importing")

-             else:

-                 new_status = upd_dict.get("status")

+                 chroot_status=StatusEnum("waiting")

+                 if not build.build_chroots:

+                     # create the BuildChroots from Package setting, if not

+                     # already set explicitly for concrete build

+                     for chroot in build.package.chroots:

+                         buildchroot = models.BuildChroot(

+                             build=build,

+                             status=chroot_status,

+                             mock_chroot=chroot,

+                             git_hash=None,

+                         )

+                         db.session.add(buildchroot)

+                 else:

+                     for buildchroot in build.build_chroots:

+                         buildchroot.status = chroot_status

+                         db.session.add(buildchroot)

  

              build.source_status = new_status

              if new_status == StatusEnum("failed") or \

@@ -700,6 +700,10 @@

          return new_chroot

  

      @classmethod

+     def active_names(cls):

+         return [ch.name for ch in cls.get_multiple(active_only=True).all()]

+ 

+     @classmethod

      def new(cls, mock_chroot):

          db.session.add(mock_chroot)

  

@@ -4,6 +4,7 @@

  import json

  import base64

  import uuid

+ from fnmatch import fnmatch

  

  from sqlalchemy.ext.associationproxy import association_proxy

  from six.moves.urllib.parse import urljoin
@@ -522,6 +523,10 @@

      copr_dir_id = db.Column(db.Integer, db.ForeignKey("copr_dir.id"), index=True)

      copr_dir = db.relationship("CoprDir", backref=db.backref("packages"))

  

+     # comma-separated list of wildcards of chroot names that this package should

+     # not be built against, e.g. "fedora-*, epel-*-i386"

+     chroot_blacklist_raw = db.Column(db.Text)

+ 

      @property

      def dist_git_repo(self):

          return "{}/{}".format(self.copr_dir.full_name, self.name)
@@ -582,6 +587,55 @@

          return self.copr.id

  

  

+     @property

+     def chroot_blacklist(self):

+         if not self.chroot_blacklist_raw:

+             return []

+ 

+         blacklisted = []

+         for pattern in self.chroot_blacklist_raw.split(','):

+             pattern = pattern.strip()

+             if not pattern:

+                 continue

+             blacklisted.append(pattern)

+ 

+         return blacklisted

+ 

+ 

+     @staticmethod

+     def matched_chroot(chroot, patterns):

+         for pattern in patterns:

+             if fnmatch(chroot.name, pattern):

+                 return True

+         return False

+ 

+ 

+     @property

+     def main_pkg(self):

+         if self.copr_dir.main:

+             return self

+ 

+         main_pkg = Package.query.filter_by(

+                 name=self.name,

+                 copr_dir_id=self.copr.main_dir.id

+         ).first()

+         return main_pkg

+ 

+ 

+     @property

+     def chroots(self):

+         chroots = list(self.copr.active_chroots)

+         if not self.chroot_blacklist_raw:

+             # no specific blacklist

+             if self.copr_dir.main:

+                 return chroots

+             return self.main_pkg.chroots

+ 

+         filtered = [c for c in chroots if not self.matched_chroot(c, self.chroot_blacklist)]

+         # We never want to filter everything, this is a misconfiguration.

+         return filtered if filtered else chroots

+ 

+ 

  class Build(db.Model, helpers.Serializer):

      """

      Representation of one build in one copr
@@ -713,9 +767,6 @@

      def import_log_urls(self):

          backend_log = self.import_log_url_backend

          types = [helpers.BuildSourceEnum("upload"), helpers.BuildSourceEnum("link")]

-         if self.source_type in types:

-             if json.loads(self.source_json).get("url", "").endswith(".src.rpm"):

-                 backend_log = None

          return filter(None, [backend_log, self.import_log_url_distgit])

  

      @property
@@ -817,6 +868,10 @@

          if self.canceled:

              return StatusEnum("canceled")

  

+         use_src_statuses = ["starting", "pending", "running"]

+         if self.source_status in [StatusEnum(s) for s in use_src_statuses]:

+             return self.source_status

+ 

          for state in ["running", "starting", "pending", "failed", "succeeded", "skipped", "forked", "waiting"]:

              if StatusEnum(state) in self.chroot_states:

                  if state == "waiting":

@@ -132,10 +132,16 @@

    {% if build.canceled %}

      {{ build_state_text("canceled") }}

    {% else %}

-     {% if build.status|state_from_num == "waiting" %}

+     {% if build.status is not none %}

+       {% if build.status|state_from_num == "waiting" %}

+         {{ build_state_text(build.source_status|state_from_num) }}

+       {% else %}

+         {{ build_state_text(build.status|state_from_num) }}

+       {% endif %}

+     {% elif build.source_status %}

        {{ build_state_text(build.source_status|state_from_num) }}

      {% else %}

-       {{ build_state_text(build.status|state_from_num) }}

+       {{ build_state_text(0|state_from_num) }}

      {% endif %}

    {% endif %}

  {% endmacro %}
@@ -532,14 +538,14 @@

  

  

  {% macro render_srpm_build_method_box(form) %}

-   <!-- This closes "2. Provide the source" panel, so we can create a new one.

+   <!-- This closes "Provide the source" panel, so we can create a new one.

         It is particularly ugly and needs to be changed -->

      </div>

    </div>

  

    <div class="panel panel-default">

      <div class="panel-heading">

-       <h3 class="panel-title">3. How to build SRPM from the source</h3>

+       <h3 class="panel-title">{{ counter('instructions') }}. How to build SRPM from the source</h3>

      </div>

      <div class="panel-body">

        <div class="list-group" style="margin-bottom: 0px">

@@ -1,4 +1,4 @@

- {% from "_helpers.html" import render_field, render_form_errors, copr_url, render_pypi_python_versions_field, render_additional_build_options, render_srpm_build_method_box %}

+ {% from "_helpers.html" import render_field, render_form_errors, copr_url, render_pypi_python_versions_field, render_additional_build_options, render_srpm_build_method_box with context %}

  {% from "coprs/detail/_method_forms.html" import copr_method_form_fileds_custom %}

  

  {# This file contains forms for the "New Build" action
@@ -39,20 +39,20 @@

    {% if not hide_panels %}

      <div class="panel panel-default">

        <div class="panel-heading">

-         <h3 class="panel-title">2. Provide the source</h3>

+         <h3 class="panel-title">{{ counter('instructions') }}. Provide the source</h3>

        </div>

        <div class="panel-body">

    {% endif %}

  {% endmacro %}

  

  

- {% macro copr_build_form_end(form, view, copr, hide_panels=False, numbering=3) %}

+ {% macro copr_build_form_end(form, view, copr, hide_panels=False) %}

    {% if not hide_panels %}

        </div>

      </div>

      <div class="panel panel-default">

        <div class="panel-heading">

-         <h3 class="panel-title">{{ numbering }}. Select chroots and other options</h3>

+         <h3 class="panel-title">{{ counter('instructions') }}. Select chroots and other options</h3>

        </div>

        <div class="panel-body">

    {% endif %}
@@ -129,7 +129,7 @@

  

      {{ render_srpm_build_method_box(form) }}

  

-   {{ copr_build_form_end(form, view, copr, numbering=4) }}

+   {{ copr_build_form_end(form, view, copr) }}

  {% endmacro %}

  

  

@@ -1,4 +1,4 @@

- {% from "_helpers.html" import render_field, render_form_errors, copr_url, render_pypi_python_versions_field, render_additional_build_options, render_srpm_build_method_box %}

+ {% from "_helpers.html" import render_field, render_form_errors, copr_url, render_pypi_python_versions_field, render_additional_build_options, render_srpm_build_method_box with context %}

  {% from "coprs/detail/_method_forms.html" import copr_method_form_fileds_custom %}

  

  {% macro copr_package_form_begin(form, view, copr, package) %}
@@ -7,7 +7,7 @@

          <form class="form-horizontal" action="{{ copr_url(view, copr, source_type_text=source_type_text) }}" method="post" enctype="multipart/form-data">

            <div class="panel panel-default">

              <div class="panel-heading">

-               <h3 class="panel-title">2. Provide the source</h3>

+               <h3 class="panel-title">{{ counter('instructions') }}. Provide the source</h3>

              </div>

              <div class="panel-body">

                {{ render_field(form.package_name) }}
@@ -16,7 +16,7 @@

                method="post" enctype="multipart/form-data">

            <div class="panel panel-default">

              <div class="panel-heading">

-               <h3 class="panel-title">2. Provide the source</h3>

+               <h3 class="panel-title">{{ counter('instructions') }}. Provide the source</h3>

              </div>

              <div class="panel-body">

                <input type="hidden" name="package_name" value="{{ package.name }}" />
@@ -50,6 +50,20 @@

    </div>

  {% endmacro %}

  

+ {% macro render_generic_pkg_form(form) %}

+     </div>

+   </div>

+   <div class="panel panel-default">

+     <div class="panel-heading">

+       <h3 class="panel-title">{{ counter('instructions') }}. Generic package setup</h3>

+     </div>

+     <div class="panel-body">

+ {{ render_field(form.chroot_blacklist,

+                 placeholder="Optional - comma-separated list of wildcard-patterns, e.g.  fedora-*, *-i386",

+                 info="What chroots should be skipped for this package, by default we build for all.",

+ )}}

+ {% endmacro %}

+ 

  {% macro render_anitya_autorebuild(form) %}

    <div class="form-group">

      <label class="col-sm-2 control-label" for="textInput-markup">
@@ -76,6 +90,7 @@

      {{ render_field(form.pypi_package_name, placeholder="Package name in the Python Package Index.") }}

      {{ render_field(form.spec_template, placeholder="Spec template to be used for generating srpm.") }}

      {{ render_pypi_python_versions_field(form.python_versions) }}

+     {{ render_generic_pkg_form(form) }}

      {{ render_anitya_autorebuild(form) }}

      {{ copr_package_form_end(form, package, 'rubygems') }}

  {% endmacro %}
@@ -84,6 +99,7 @@

  {% macro copr_package_form_rubygems(form, view, copr, package) %}

    {{ copr_package_form_begin(form, view, copr, package) }}

    {{ render_field(form.gem_name, placeholder="Gem name from RubyGems.org") }}

+   {{ render_generic_pkg_form(form) }}

    {{ render_anitya_autorebuild(form) }}

    {{ copr_package_form_end(form, package, 'rubygems') }}

  {% endmacro %}
@@ -92,6 +108,7 @@

  {% macro copr_package_form_custom(form, view, copr, package) %}

    {{ copr_package_form_begin(form, view, copr, package) }}

    {{ copr_method_form_fileds_custom(form) }}

+   {{ render_generic_pkg_form(form) }}

    {{ render_webhook_rebuild(form) }}

    {{ copr_package_form_end(form, package, 'custom') }}

  {% endmacro %}
@@ -106,8 +123,9 @@

    {{ render_field(form.subdirectory, placeholder="Optional - Subdirectory where source files and .spec are located.") }}

    {{ render_field(form.spec, placeholder="Optional - Path to your .spec file under the specified subdirectory.") }}

  

-   {{ render_webhook_rebuild(form) }}

    {{ render_srpm_build_method_box(form) }}

+   {{ render_generic_pkg_form(form) }}

+   {{ render_webhook_rebuild(form) }}

    {{ copr_package_form_end(form, package, 'mock_scm') }}

  {% endmacro %}

  
@@ -129,7 +147,7 @@

  

      <div class="panel panel-default">

        <div class="panel-heading">

-         <h3 class="panel-title">1. Select packages for rebuild</h3>

+         <h3 class="panel-title">Select packages for rebuild</h3>

        </div>

        <div class="panel-body">

          <div class="form-group {% if form.errors %}has-error{% endif %}">
@@ -159,7 +177,7 @@

  

      <div class="panel panel-default">

        <div class="panel-heading">

-         <h3 class="panel-title">2. Select chroots and other options</h3>

+         <h3 class="panel-title">Select chroots and other options</h3>

        </div>

        <div class="panel-body">

        {{ render_additional_build_options(form, copr) }}

@@ -29,7 +29,7 @@

  

  <div class="panel panel-default">

    <div class="panel-heading">

-     <h3 class="panel-title">1. Select the source type - <a href="https://docs.pagure.org/copr.copr/user_documentation.html#build-source-types">Learn More</a></h3>

+     <h3 class="panel-title">{{ counter('instructions') }}. Select the source type - <a href="https://docs.pagure.org/copr.copr/user_documentation.html#build-source-types">Learn More</a></h3>

    </div>

    <div class="panel-body">

      <ul class="nav nav-tabs nav-tabs-pf">

@@ -15,7 +15,7 @@

  

  <div class="panel panel-default">

    <div class="panel-heading">

-     <h3 class="panel-title">1. Select the source type - <a href="https://docs.pagure.org/copr.copr/user_documentation.html#build-source-types">Learn More</a></h3>

+     <h3 class="panel-title">Select the source type - <a href="https://docs.pagure.org/copr.copr/user_documentation.html#build-source-types">Learn More</a></h3>

    </div>

    <div class="panel-body">

      <ul class="nav nav-tabs nav-tabs-pf">

@@ -176,8 +176,14 @@

          {% else %}

            <dd> - </dd>

          {% endif %}

+ 

+         {% if not build.build_chroots %}

+         <dt>RPM builds:</dt>

+         <dd>waiting for SRPM (package name is not known for this build, yet)</dd>

+         {% endif %}

          </dl>

  

+         {% if build.build_chroots %}

          <table class="table table-striped table-bordered">

            <thead>

              <tr>
@@ -222,6 +228,7 @@

            {% endfor %}

            </tbody>

          </table>

+         {% endif %}

        </div>

      </div>

  

@@ -16,7 +16,7 @@

  

  <div class="panel panel-default">

    <div class="panel-heading">

-     <h3 class="panel-title">1. Select the source type - <a href="https://docs.pagure.org/copr.copr/user_documentation.html#build-source-types">Learn More</a></h3>

+     <h3 class="panel-title">{{ counter('instructions') }}. Select the source type - <a href="https://docs.pagure.org/copr.copr/user_documentation.html#build-source-types">Learn More</a></h3>

    </div>

    <div class="panel-body">

      <ul class="nav nav-tabs nav-tabs-pf">

@@ -64,6 +64,14 @@

            <dd>

              {{ describe_source(package.source_type_text, package.source_json_dict, is_package=True) }}

            </dd>

+           {% if package.chroots | length != package.copr.active_chroots | list | length %}

+           <dt>Built only for:</dt>

+           <dd>

+             {% for chroot in package.chroots %}

+             {{ chroot.name }}{% if not loop.last %},{% endif %}

+             {% endfor %}

+           </dd>

+           {% endif %}

          </dl>

        </div>

      </div>

@@ -41,7 +41,9 @@

              "build_id": build.id,

              "owner": build.copr.owner_name,

              "project": build.copr_dirname,

+             # TODO: we mix PR with normal builds here :-(

              "branches": list(branches),

+             "pkg_name": build.package.name,

              "srpm_url": build.srpm_url,

          })

  
@@ -53,8 +55,6 @@

  def dist_git_upload_completed():

      app.logger.debug(flask.request.json)

      build_id = flask.request.json.get("build_id")

-     pkg_name = flask.request.json.get("pkg_name")

-     pkg_version = flask.request.json.get("pkg_evr")

  

      try:

          build = ComplexLogic.get_build_safe(build_id)
@@ -64,22 +64,6 @@

      collected_branch_chroots = []

      for branch, git_hash in flask.request.json.get("branch_commits", {}).items():

          branch_chroots = BuildsLogic.get_buildchroots_by_build_id_and_branch(build_id, branch)

- 

-         if not PackagesLogic.get(build.copr_dir.id, pkg_name).first():

-             try:

-                 package = PackagesLogic.add(

-                     build.copr.user, build.copr_dir,

-                     pkg_name, build.source_type, build.source_json)

-                 db.session.add(package)

-                 db.session.commit()

-             except (sqlalchemy.exc.IntegrityError, exceptions.DuplicateException) as e:

-                 app.logger.exception(e)

-                 db.session.rollback()

- 

-         package = PackagesLogic.get(build.copr_dir.id, pkg_name).first()

-         build.package_id = package.id

-         build.pkg_version = pkg_version

- 

          for ch in branch_chroots:

              ch.status = StatusEnum("pending")

              ch.git_hash = git_hash

@@ -110,7 +110,7 @@

                      .format(package_name, copr.full_name))

          return flask.redirect(helpers.copr_url("coprs_ns.copr_edit_package", copr, package_name=package_name))

  

-     form = form(copr.active_chroots)(data=data)

+     form = form(copr.active_chroots, package)(data=data)

      return f(copr, form, view="coprs_ns.copr_new_build" + view_suffix, package=package)

  

  
@@ -157,6 +157,7 @@

      package = ComplexLogic.get_package_safe(copr.main_dir, package_name)

      data = package.source_json_dict

      data["webhook_rebuild"] = package.webhook_rebuild

+     data["chroot_blacklist"] = package.chroot_blacklist_raw

  

      if package.has_source_type_set and not source_type_text:

          source_type_text = package.source_type_text
@@ -227,6 +228,7 @@

              package.source_type = helpers.BuildSourceEnum(source_type_text)

              package.webhook_rebuild = form.webhook_rebuild.data

              package.source_json = form.source_json

+             package.chroot_blacklist_raw = form.chroot_blacklist.data

  

              db.session.add(package)

              db.session.commit()

@@ -420,6 +420,13 @@

                                  summary="Sum 3", description="Desc 3", created_on=time.time())

          self.db.session.add_all([self.m1, self.m2, self.m3])

  

+     @pytest.fixture

+     def f_pr_dir(self):

+         self.c4_dir = models.CoprDir(name=u"foocopr:PR", copr=self.c1,

+                 main=False)

+         self.p4 = models.Package(

+             copr=self.c1, copr_dir=self.c4_dir, name="hello-world",

+             source_type=0)

  

      def request_rest_api_with_auth(self, url,

                                     login=None, token=None,

@@ -17,6 +17,19 @@

  

  

  class TestBuildsLogic(CoprsTestCase):

+     data = """

+ {

+   "builds":[

+     {

+       "id": 5,

+       "task_id": 5,

+       "srpm_url": "http://foo",

+       "status": 1,

+       "pkg_name": "foo",

+       "pkg_version": 1

+     }

+   ]

+ }"""

  

      def test_add_only_adds_active_chroots(self, f_users, f_coprs, f_builds,

                                            f_mock_chroots, f_db):
@@ -25,7 +38,18 @@

          self.db.session.commit()

          b = BuildsLogic.add(self.u2, "blah", self.c2)

          self.db.session.commit()

-         assert b.chroots[0].name == self.mc3.name

+         build_id = b.id

+         expected_name = self.mc3.name

+         assert len(b.chroots) == 0

+ 

+         self.tc.post("/backend/update/",

+                          content_type="application/json",

+                          headers=self.auth_header,

+                          data=self.data)

+ 

+         b = BuildsLogic.get(build_id).first()

+         assert len(b.chroots) == 1

+         assert b.chroots[0].name == expected_name

  

      def test_add_raises_if_copr_has_unfinished_actions(self, f_users, f_coprs,

                                                         f_actions, f_db):

@@ -18,3 +18,27 @@

  

          result_1 = set([ch.name for ch in self.b_many_chroots.get_chroots_by_status(statuses=[0, 1, 3])])

          assert expected_1 == result_1

+ 

+ 

+     def test_chroot_blacklist(self, f_users, f_coprs, f_builds, f_mock_chroots_many, f_pr_dir, f_db):

+         # test main package

+         assert len(list(self.p1.chroots)) == 15

+         self.p1.chroot_blacklist_raw = '*-19-*, epel*'

+         assert len(self.p1.chroot_blacklist) == 2

+         assert len(list(self.p1.chroots)) == 8

+ 

+         # non-main package inherits from main package by default

+         assert len(list(self.p4.chroots)) == 8

+ 

+         # but if we set the blacklist here, too, it get's precedence

+         self.p4.chroot_blacklist_raw = 'epel*'

+         assert len(self.p4.chroot_blacklist) == 1

+         assert len(list(self.p4.chroots)) == 10

+ 

+     def test_chroot_blacklist_all(self, f_users, f_coprs, f_builds, f_mock_chroots_many, f_pr_dir, f_db):

+         assert len(list(self.p1.chroots)) == 15

+         assert len(list(self.p1.copr.active_chroots)) == 15

+         self.p1.chroot_blacklist_raw = '*'

+         # even though we blacklised (by mistake) all chroots, package builds

+         # against all chroots (fallback)

+         assert len(list(self.p1.chroots)) == 15

@@ -135,6 +135,8 @@

          updated = self.models.Build.query.filter(

              self.models.Build.id == 1).one()

  

+         assert len(updated.build_chroots) == 1

+         assert updated.build_chroots[0].status == 1

          assert updated.status == 1

          assert updated.chroots_ended_on == {'fedora-18-x86_64': 1490866440}

  
@@ -264,18 +266,51 @@

  

  

  class TestImportingBuilds(CoprsTestCase):

+     data = """

+ {

+   "builds":[

+     {

+       "id": 1,

+       "task_id": 1,

+       "srpm_url": "http://foo",

+       "status": 1,

+       "pkg_name": "foo",

+       "pkg_version": 1

+     },

+     {

+       "id": 2,

+       "task_id": 2,

+       "srpm_url": "http://bar",

+       "status": 1,

+       "pkg_name": "bar",

+       "pkg_version": 2

+     }

+   ]

+ }"""

+ 

      def test_bg_priority_in_queue(self, f_users, f_coprs, f_mock_chroots, f_db):

          BuildsLogic.create_new_from_url(self.u1, self.c1, "foo", background=True)

          BuildsLogic.create_new_from_url(self.u1, self.c1, "bar")

  

+         self.tc.post("/backend/update/",

+                          content_type="application/json",

+                          headers=self.auth_header,

+                          data=self.data)

+ 

          r = self.tc.get("/backend/importing/")

          data = json.loads(r.data.decode("utf-8"))

-         assert data[0]["srpm_url"] == "bar"

+         assert data[0]["srpm_url"] == "http://bar"

  

      def test_importing_queue_multiple_bg(self, f_users, f_coprs, f_mock_chroots, f_db):

          BuildsLogic.create_new_from_url(self.u1, self.c1, "foo", background=True)

          BuildsLogic.create_new_from_url(self.u1, self.c1, "bar", background=True)

  

+         self.tc.post("/backend/update/",

+                          content_type="application/json",

+                          headers=self.auth_header,

+                          data=self.data)

+ 

          r = self.tc.get("/backend/importing/")

          data = json.loads(r.data.decode("utf-8"))

-         assert data[0]["srpm_url"] == "foo"

+         assert data[0]["srpm_url"] == "http://foo"

+         assert data[1]["srpm_url"] == "http://bar"

@@ -0,0 +1,22 @@

+ #! /bin/sh

+ 

+ # Run copr-rpmbuild script directly from git, TESTING ONLY SCRIPT!

+ # Copyright (C) 2018 Red Hat, Inc.

+ #

+ # This program is free software; you can redistribute it and/or modify

+ # it under the terms of the GNU General Public License as published by

+ # the Free Software Foundation; either version 2 of the License, or

+ # (at your option) any later version.

+ #

+ # This program is distributed in the hope that it will be useful,

+ # but WITHOUT ANY WARRANTY; without even the implied warranty of

+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

+ # GNU General Public License for more details.

+ #

+ # You should have received a copy of the GNU General Public License along

+ # with this program; if not, write to the Free Software Foundation, Inc.,

+ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

+ 

+ absdir="$(dirname "$(readlink -f "$0")")"

+ export PYTHONPATH="$absdir:$absdir/../common:$absdir/../python${PYTHONPATH+:$PYTHONPATH}"

+ python3 main.py "$@"

@@ -1,20 +1,20 @@

  from ..helpers import SourceType

  from .rubygems import RubyGemsProvider

  from .pypi import PyPIProvider

- from .spec import SpecUrlProvider

+ from .spec import UrlProvider

  from .scm import ScmProvider

  from .custom import CustomProvider

  

  

  __all__ = [RubyGemsProvider, PyPIProvider,

-            SpecUrlProvider, ScmProvider]

+            UrlProvider, ScmProvider]

  

  

  def factory(source_type):

      try:

          return {

-             SourceType.LINK: SpecUrlProvider,

-             SourceType.UPLOAD: SpecUrlProvider,

+             SourceType.LINK: UrlProvider,

+             SourceType.UPLOAD: UrlProvider,

              SourceType.RUBYGEMS: RubyGemsProvider,

              SourceType.PYPI: PyPIProvider,

              SourceType.SCM: ScmProvider,

@@ -7,9 +7,9 @@

  log = logging.getLogger("__main__")

  

  

- class SpecUrlProvider(Provider):

+ class UrlProvider(Provider):

      def __init__(self, source_json, outdir, config=None):

-         super(SpecUrlProvider, self).__init__(source_json, outdir, config)

+         super(UrlProvider, self).__init__(source_json, outdir, config)

          self.url = source_json["url"]

  

      def save_spec(self):
@@ -19,9 +19,27 @@

              spec.write(response.text)

          return path

  

-     def produce_srpm(self):

-         if not self.url.endswith(".spec"):

-             raise RuntimeError("Not a path to .spec file")

+     def build_srpm_from_spec(self):

          spec_path = self.save_spec()

          cmd = ["rpkg", "srpm", "--outdir", self.outdir, '--spec', spec_path]

          return run_cmd(cmd, cwd=self.workdir)

+ 

+     def download_srpm(self):

+         basename = os.path.basename(self.url)

+         filename = os.path.join(self.outdir, basename)

+         response = requests.get(self.url, stream=True)

+         if response.status_code != 200:

+             raise RuntimeError('Requests get status "{0}" for "{1}"'.format(

+                 response.status_code, self.url

+             ))

+ 

+         with open(filename, 'wb') as f:

+             for chunk in response:

+                 f.write(chunk)

+ 

+     def produce_srpm(self):

+         if self.url.endswith(".spec"):

+             return self.build_srpm_from_spec()

+         if self.url.endswith(".src.rpm"):

+             return self.download_srpm()

+         raise RuntimeError("Url is not a path to .spec nor .src.rpm file")

file modified
+4 -2
@@ -41,8 +41,10 @@

  log.setLevel(logging.INFO)

  log.addHandler(logging.StreamHandler(sys.stdout))

  

- VERSION = pkg_resources.require('copr-rpmbuild')[0].version

- 

+ try:

+     VERSION = pkg_resources.require('copr-rpmbuild')[0].version

+ except pkg_resources.DistributionNotFound:

+     VERSION = 'git'

  

  def daemonize():

      try:

@@ -2,7 +2,7 @@

  import pytest

  

  from copr_rpmbuild.providers import (factory, RubyGemsProvider, PyPIProvider,

-                                      SpecUrlProvider)

+                                      UrlProvider)

  

  from copr_rpmbuild.helpers import SourceType

  
@@ -14,6 +14,6 @@

      def test_factory(self):

          self.assertEqual(factory(SourceType.RUBYGEMS), RubyGemsProvider)

          self.assertEqual(factory(SourceType.PYPI), PyPIProvider)

-         self.assertEqual(factory(SourceType.LINK), SpecUrlProvider)

+         self.assertEqual(factory(SourceType.LINK), UrlProvider)

          with pytest.raises(RuntimeError):

              factory(self.not_existing_source_type)

file modified
+6 -6
@@ -1,6 +1,6 @@

  import configparser

  

- from copr_rpmbuild.providers.spec import SpecUrlProvider

+ from copr_rpmbuild.providers.spec import UrlProvider

  from . import TestCase

  

  try:
@@ -12,21 +12,21 @@

       builtins = '__builtin__'

  

  

- class TestSpecUrlProvider(TestCase):

+ class TestUrlProvider(TestCase):

      def setUp(self):

-         super(TestSpecUrlProvider, self).setUp()

+         super(TestUrlProvider, self).setUp()

          self.source_json = {"url": u"http://foo.ex/somepackage.spec"}

          self.resultdir = "/path/to/resultdir"

  

      def test_init(self):

-         provider = SpecUrlProvider(self.source_json, self.resultdir, self.config)

+         provider = UrlProvider(self.source_json, self.resultdir, self.config)

          self.assertEqual(provider.url, "http://foo.ex/somepackage.spec")

  

      @mock.patch('requests.get')

      @mock.patch("copr_rpmbuild.providers.spec.run_cmd")

      @mock.patch('{0}.open'.format(builtins), new_callable=mock.mock_open())

      def test_produce_srpm(self, mock_open, run_cmd, mock_get):

-         provider = SpecUrlProvider(self.source_json, self.resultdir, self.config)

+         provider = UrlProvider(self.source_json, self.resultdir, self.config)

          provider.produce_srpm()

          run_cmd.assert_called_with(["rpkg", "srpm", "--outdir", self.resultdir,

                                      "--spec", '{0}/somepackage.spec'.format(provider.workdir)],
@@ -35,6 +35,6 @@

      @mock.patch('requests.get')

      @mock.patch('{0}.open'.format(builtins), new_callable=mock.mock_open())

      def test_save_spec(self, mock_open, mock_get):

-         provider = SpecUrlProvider(self.source_json, self.resultdir, self.config)

+         provider = UrlProvider(self.source_json, self.resultdir, self.config)

          provider.save_spec()

          mock_open.assert_called_with("{0}/somepackage.spec".format(provider.workdir), "w")

no initial comment

1 new commit added

  • [frontend][rpmbuild] drop "downloading" state
5 years ago

rebased onto 0ed0ab71681d9a1e552806bfe712118163065160

5 years ago

rebased onto 0ed0ab71681d9a1e552806bfe712118163065160

5 years ago

Can you use backend.helpers.run_cmd() instead?

I know that this is an easier way, but .... can you create 1:M table chroot_blacklist(package_id, chroot) ?

Should this be rather in frontend/coprs_frontend/coprs/logic/...somewhere ? Coincidentely I have an idea for one api call, and I would find use for this function too.

I don't think it would have any benefits; at least as long as we plan to keep the fnmatch() logic. Can you elaborate?

Yup, that's weird WIP debugging output isn't it? :-) I'll drop this.

I'll check, I just blindly moved this code from backend to distgit.

@msuchy, thanks a lot for having a look!

rebased onto 9c33076

5 years ago

rebased onto 9c33076

5 years ago

1 new commit added

  • [frontend] drop useless debugging print()
5 years ago

1 new commit added

  • [frontend] drop useless debugging print()
5 years ago

2 new commits added

  • [frontend] render "Generic form"
  • [frontend] drop section numbers from build/package forms
5 years ago

2 new commits added

  • [frontend] render "Generic form"
  • [frontend] drop section numbers from build/package forms
5 years ago

1 new commit added

  • [frontend] package.chroots used to pre-fill package rebuild form
5 years ago

1 new commit added

  • [frontend] package.chroots used to pre-fill package rebuild form
5 years ago

1 new commit added

  • [frontend] move get_active_chroots() to logic
5 years ago

1 new commit added

  • [frontend] move get_active_chroots() to logic
5 years ago

I moved the "active_chroots" logic to logic code, per @msuchy's request.

1 new commit added

  • [frontend] test chroot_blacklist model
5 years ago

1 new commit added

  • [frontend] test chroot_blacklist model
5 years ago

1 new commit added

  • [frontend] list chroots the package is built for
5 years ago

1 new commit added

  • [frontend] fix Results chroot list for unknown package name
5 years ago

Code in this PR looks good.

2 new commits added

  • [frontend] move 'Build only for' down into 'Default Build Source'
  • [frontend] better section numbering per request
5 years ago

1 new commit added

  • [rpmbuild] revert back Suggests
5 years ago
  • I implemented "section numbering" through context processor, instead of removing numbering entirely
  • I moved the 'Build only for' out from "generic info" (instead of providing duplicit edit button)
  • dropped the Suggests: change, that was leftover for my own debugging

This is really a good PR.

1 new commit added

  • [frontend] drop debugging leftover in context_processors.py
5 years ago

Ping, anything to be fixed? I'm not sure after the meeting, is this good to go before release?

Pull-Request has been merged by msuchy

5 years ago
Metadata
Changes Summary 38
+1 -0
file changed
backend/backend/actions.py
+13 -13
file changed
backend/backend/daemons/worker.py
+2 -0
file changed
backend/backend/exceptions.py
+29 -1
file changed
backend/backend/helpers.py
+0 -33
file changed
dist-git/dist_git/helpers.py
+1 -0
file changed
dist-git/dist_git/import_task.py
+2 -1
file changed
dist-git/dist_git/importer.py
+2 -9
file changed
dist-git/dist_git/package_import.py
+2 -0
file changed
dist-git/tests/base.py
+3 -2
file changed
dist-git/tests/test_crazy_merging.py
+3 -8
file changed
dist-git/tests/test_package_import.py
+22
file added
frontend/coprs_frontend/alembic/schema/versions/51716ab39d37_package_chroot_blacklist.py
+1 -1
file changed
frontend/coprs_frontend/coprs/__init__.py
+13 -0
file changed
frontend/coprs_frontend/coprs/context_processors.py
+62 -24
file changed
frontend/coprs_frontend/coprs/forms.py
+41 -19
file changed
frontend/coprs_frontend/coprs/logic/builds_logic.py
+4 -0
file changed
frontend/coprs_frontend/coprs/logic/coprs_logic.py
+58 -3
file changed
frontend/coprs_frontend/coprs/models.py
+10 -4
file changed
frontend/coprs_frontend/coprs/templates/_helpers.html
+5 -5
file changed
frontend/coprs_frontend/coprs/templates/coprs/detail/_builds_forms.html
+24 -6
file changed
frontend/coprs_frontend/coprs/templates/coprs/detail/_package_forms.html
+1 -1
file changed
frontend/coprs_frontend/coprs/templates/coprs/detail/add_build.html
+1 -1
file changed
frontend/coprs_frontend/coprs/templates/coprs/detail/add_package.html
+7 -0
file changed
frontend/coprs_frontend/coprs/templates/coprs/detail/build.html
+1 -1
file changed
frontend/coprs_frontend/coprs/templates/coprs/detail/edit_package.html
+8 -0
file changed
frontend/coprs_frontend/coprs/templates/coprs/detail/package.html
+2 -18
file changed
frontend/coprs_frontend/coprs/views/backend_ns/backend_general.py
+3 -1
file changed
frontend/coprs_frontend/coprs/views/coprs_ns/coprs_packages.py
+7 -0
file changed
frontend/coprs_frontend/tests/coprs_test_case.py
+25 -1
file changed
frontend/coprs_frontend/tests/test_logic/test_builds_logic.py
+24 -0
file changed
frontend/coprs_frontend/tests/test_models.py
+37 -2
file changed
frontend/coprs_frontend/tests/test_views/test_backend_ns/test_backend_general.py
+22
file added
rpmbuild/copr-rpmbuild
+4 -4
file changed
rpmbuild/copr_rpmbuild/providers/__init__.py
+23 -5
file changed
rpmbuild/copr_rpmbuild/providers/spec.py
+4 -2
file changed
rpmbuild/main.py
+2 -2
file changed
rpmbuild/tests/test_providers.py
+6 -6
file changed
rpmbuild/tests/test_spec.py