#938 Multilib support
Merged 2 months ago by praiskup. Opened 3 months ago by praiskup.
copr/ praiskup/copr multilib  into  master

file modified
+15

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

              auto_prune=ON_OFF_MAP[args.auto_prune],

              use_bootstrap_container=ON_OFF_MAP[args.use_bootstrap_container],

              delete_after_days=args.delete_after_days,

+             multilib=ON_OFF_MAP[args.multilib],

          )

          print("New project was successfully created.")

  

@@ -379,6 +380,7 @@ 

              use_bootstrap_container=ON_OFF_MAP[args.use_bootstrap_container],

              chroots=args.chroots,

              delete_after_days=args.delete_after_days,

+             multilib=ON_OFF_MAP[args.multilib],

          )

  

      @requires_api_auth

@@ -817,6 +819,13 @@ 

                                 help="If mock bootstrap container is used to initialize the buildroot.")

      parser_create.add_argument("--delete-after-days", default=None, metavar='DAYS',

                                 help="Delete the project after the specfied period of time")

+     parser_create.add_argument(

+         "--multilib", choices=["on", "off"], default="off",

+         help=("When users enable this copr repository on 64bit variant of "

+               "multilib capable architecture (e.g. x86_64), they will also be "

+               "able to install 32bit variants of the packages (e.g. i386 for "

+               "x86_64 arch), default is 'off'"))

+ 

      parser_create.set_defaults(func="action_create")

  

      # create the parser for the "modify_project" command

@@ -846,6 +855,12 @@ 

                                 help=("Delete the project after the specfied "

                                       "period of time, empty or -1 disables, "

                                       "(default is \"don't change\")"))

+     parser_modify.add_argument(

+         "--multilib", choices=["on", "off"],

+         help=("When users enable this copr repository on 64bit variant of "

+               "multilib capable architecture (e.g. x86_64), they will also be "

+               "able to install 32bit variants of the packages (e.g. i386 for "

+               "x86_64 arch), default is \"don't change\""))

      parser_modify.set_defaults(func="action_modify_project")

  

      # create the parser for the "delete" command

file modified
+31

@@ -375,6 +375,37 @@ 

          "unlisted_on_hp": None, "devel_mode": None, "enable_net": False,

          "use_bootstrap_container": None,

          "delete_after_days": None,

+         "multilib": False,

+     }

+     assert stdout == "New project was successfully created.\n"

+ 

+ @mock.patch('copr.v3.proxies.project.ProjectProxy.add')

+ @mock.patch('copr_cli.main.config_from_file', return_value=mock_config)

+ def test_create_multilib_project(config_from_file, project_proxy_add, capsys):

+     main.main(argv=[

+         "create", "foo",

+         '--multilib', 'on',

+         "--chroot", "fedora-rawhide-x86_64",

+         "--chroot", "fedora-rawhide-i386",

+         "--instructions", "instruction string",

+         "--repo", "repo1", "--repo", "repo2",

+         "--initial-pkgs", "pkg1",

+     ])

+     stdout, stderr = capsys.readouterr()

+ 

+     project_proxy_add.assert_called_once()

+     args, kwargs = project_proxy_add.call_args

+     assert kwargs == {

+         "auto_prune": True,

+         "ownername": None, "persistent": False, "projectname": "foo",

+         "description": None,

+         "instructions": "instruction string",

+         "chroots": ["fedora-rawhide-x86_64", "fedora-rawhide-i386"],

+         "additional_repos": ["repo1", "repo2"],

+         "unlisted_on_hp": None, "devel_mode": None, "enable_net": False,

+         "use_bootstrap_container": None,

+         "delete_after_days": None,

+         "multilib": True,

      }

      assert stdout == "New project was successfully created.\n"

  

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

+ """

+ multilib knob on copr_public

+ 

+ Revision ID: 0dbdd06fb850

+ Revises: 12abab545d7a

+ Create Date: 2019-08-20 22:41:50.747899

+ """

+ 

+ import sqlalchemy as sa

+ from alembic import op

+ 

+ 

+ revision = '0dbdd06fb850'

+ down_revision = '12abab545d7a'

+ 

+ def upgrade():

+     op.add_column('copr', sa.Column('multilib', sa.Boolean(), server_default='0', nullable=False))

+ 

+ def downgrade():

+     op.drop_column('copr', 'multilib')

@@ -330,6 +330,15 @@ 

                      default=True,

                      false_values=FALSE_VALUES)

  

+             multilib = wtforms.BooleanField(

+                     "Multilib support",

+                     description="""When users enable this copr repository on

+                     64bit variant of multilib capable architecture (e.g.

+                     x86_64), they will be able to install 32bit variants of the

+                     packages (e.g. i386 for x86_64 arch)""",

+                     default=False,

+                     false_values=FALSE_VALUES)

+ 

              # Deprecated, use `enable_net` instead

              build_enable_net = wtforms.BooleanField(

                      "Enable internet access during builds",

@@ -1061,6 +1070,7 @@ 

      # Deprecated, use `enable_net` instead

      build_enable_net = wtforms.BooleanField(validators=[wtforms.validators.Optional()], false_values=FALSE_VALUES)

      enable_net = wtforms.BooleanField(validators=[wtforms.validators.Optional()], false_values=FALSE_VALUES)

+     multilib = wtforms.BooleanField(validators=[wtforms.validators.Optional()], false_values=FALSE_VALUES)

  

  

  class CoprForkFormFactory(object):

@@ -189,7 +189,7 @@ 

      return pkg

  

  

- def generate_repo_url(mock_chroot, url):

+ def generate_repo_url(mock_chroot, url, arch=None):

      """ Generates url with build results for .repo file.

      No checks if copr or mock_chroot exists.

      """

@@ -208,7 +208,7 @@ 

  

      url = posixpath.join(

          url, "{0}-{1}-{2}/".format(mock_chroot.os_release,

-                                    os_version, "$basearch"))

+                                    os_version, arch or '$basearch'))

  

      return url

  

@@ -274,6 +274,8 @@ 

      # temporary project if non-null

      delete_after = db.Column(db.DateTime, index=True, nullable=True)

  

+     multilib = db.Column(db.Boolean, default=False, nullable=False, server_default="0")

+ 

  

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

      """

@@ -372,6 +374,28 @@ 

          return filter(lambda x: x.is_active, self.mock_chroots)

  

      @property

+     def active_multilib_chroots(self):

+         """

+         Return list of active mock_chroots which have the 32bit multilib

+         counterpart.

+         """

+         chroot_names = [chroot.name for chroot in self.active_chroots]

+ 

+         found_chroots = []

+         for chroot in self.active_chroots:

+             if chroot.arch not in MockChroot.multilib_pairs:

+                 continue

+ 

+             counterpart = "{}-{}-{}".format(chroot.os_release,

+                                             chroot.os_version,

+                                             MockChroot.multilib_pairs[chroot.arch])

+             if counterpart in chroot_names:

+                 found_chroots.append(chroot)

+ 

+         return found_chroots

+ 

+ 

+     @property

      def active_copr_chroots(self):

          """

          :rtype: list of CoprChroot

@@ -1185,6 +1209,10 @@ 

  

      comment = db.Column(db.Text, nullable=True)

  

+     multilib_pairs = {

+         'x86_64': 'i386',

+     }

+ 

      @classmethod

      def latest_fedora_branched_chroot(cls, arch='x86_64'):

          return (cls.query

@@ -242,6 +242,24 @@ 

  {% endmacro %}

  

  

+ {%- macro owner_url(view, owner) %}

+   {#- Given the owner object (user or group) generate proper URL for view

+ 

+   Note that if you wan't to use this method for routes which _accept_ "coprname"

+   argument, you wan't to use `copr_url` below.

+ 

+   Usage:

+     owner_url('coprs_ns.foo', groupX)

+     owner_url('coprs_ns.foo', user1, arg1='bar', arg2='baz')

+   #}

+   {%- if owner.at_name %}

+     {{- url_for(view, group_name=owner.name, **kwargs)|fix_url_https_frontend }}

+   {%- else %}

+     {{- url_for(view, username=owner.name, **kwargs)|fix_url_https_frontend }}

+   {%- endif %}

+ {% endmacro %}

+ 

+ 

  {%- macro copr_url(view, copr) %}

    {#- Examine given copr and generate proper URL for the `view`

  

@@ -250,17 +268,13 @@ 

  

    Usage:

      copr_url('coprs_ns.foo', copr)

-     copr_url('coprs_ns.foo', copr, arg1='bar', arg2='baz)

+     copr_url('coprs_ns.foo', copr, arg1='bar', arg2='baz')

    #}

-   {%- if not copr.is_a_group_project %}

-     {{- url_for(view, username=copr.user.name, coprname=copr.name, **kwargs) }}

-   {%- else %}

-     {{- url_for(view, group_name=copr.group.name, coprname=copr.name, **kwargs) }}

-   {%- endif %}

+   {{- owner_url(view, copr.owner, coprname=copr.name, **kwargs) }}

  {%- endmacro %}

  

  

- {%- macro owner_url(copr) %}

+ {%- macro copr_owner_url(copr) %}

    {% if copr.is_a_group_project %}

      {{- url_for('groups_ns.list_projects_by_group', group_name=copr.group.name) }}

    {% else %}

@@ -431,24 +445,23 @@ 

  {%- endmacro -%}

  

  

- {% macro repo_file_href(copr, repo) %}

-   {% if copr.is_a_group_project: %}

-   {{- url_for('coprs_ns.generate_repo_file',

-       group_name=copr.group.name,

-       copr_dirname=copr.main_dir.name,

-       name_release=repo.name_release,

-       repofile=repo.repo_file,

-       _external=True

-     )|fix_url_https_frontend -}}

-   {% else %}

-   {{- url_for('coprs_ns.generate_repo_file',

-       username=copr.user.name,

-       copr_dirname=copr.main_dir.name,

-       name_release=repo.name_release,

-       repofile=repo.repo_file,

-       _external=True

-     )|fix_url_https_frontend -}}

-   {% endif %}

+ {% macro repo_file_href(copr, repo, arch=None) %}

+ {%- if not arch %}

+   {{- owner_url('coprs_ns.generate_repo_file',

+                 copr.owner,

+                 copr_dirname=copr.main_dir.name,

+                 name_release=repo.name_release,

+                 repofile=repo.repo_file,

+                 _external=True) -}}

+ {%- else %}

+   {{- owner_url('coprs_ns.generate_repo_file',

+                 copr.owner,

+                 copr_dirname=copr.main_dir.name,

+                 name_release=repo.name_release,

+                 repofile=repo.repo_file,

+                 arch=arch,

+                 _external=True) -}}

+ {%- endif %}

  {% endmacro %}

  

  

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

          [form.persistent, g.user.admin],

          [form.use_bootstrap_container],

          [form.follow_fedora_branching],

+         [form.multilib],

      ])}}

  

      {{ render_field(form.delete_after_days,

@@ -1,5 +1,5 @@ 

  {% extends "coprs/detail.html" %}

- {% from "_helpers.html" import copr_name, owner_url, initialize_datatables %}

+ {% from "_helpers.html" import copr_name, copr_owner_url, initialize_datatables %}

  

  {% block title %}Forks of {{ copr_name(copr) }}{% endblock %}

  {% set selected_tab = "forks" %}

@@ -27,7 +27,7 @@ 

    {% for fork in copr.forks %}

      <tr class="fork-row" >

        <td class="col-md-3" data-order="{{ fork.owner_name }}">

-         <b><a href="{{ owner_url(fork) }}">{{ fork.owner_name }}</a></b>

+         <b><a href="{{ copr_owner_url(fork) }}">{{ fork.owner_name }}</a></b>

        </td>

        <td>

          <b><a href="{{ copr_url('coprs_ns.copr_detail', fork) }}">{{ fork.name }}</a></b>

@@ -64,6 +64,13 @@ 

              <a class="btn btn-default btn-margin" href="{{ repo_file_href(copr, repo) }}">

                <span class="pficon pficon-save"></span> {{ friendly_os_name(repo.os_release, repo.os_version) }}

              </a>

+             {% if repo.arch_repos %}

+               {% for arch in repo.arch_repos %}

+                 <a class="btn btn-default btn-margin" href="{{ repo_file_href(copr, repo, arch) }}">

+                     <span class="pficon pficon-save"></span> multilib {{ arch }}+{{ repo.arch_repos[arch] }}

+                 </a>

+               {% endfor %}

+             {% endif %}

              <small class="text-muted"> ({{ repo.dl_stat }} downloads) </small>

            </td>

          </tr>

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

              contact=form.contact.data,

              disable_createrepo=form.disable_createrepo.data,

              delete_after_days=form.delete_after_days.data,

+             multilib=form.multilib.data,

          )

          db.session.commit()

      except (DuplicateException,

@@ -36,7 +36,8 @@ 

  

  from coprs.logic.complex_logic import ComplexLogic

  

- from coprs.views.misc import login_required, page_not_found, req_with_copr, req_with_copr, generic_error

+ from coprs.views.misc import (login_required, page_not_found, req_with_copr,

+                               generic_error, req_with_copr_dir)

  

  from coprs.views.coprs_ns import coprs_ns

  

@@ -188,6 +189,7 @@ 

                  use_bootstrap_container=form.use_bootstrap_container.data,

                  follow_fedora_branching=form.follow_fedora_branching.data,

                  delete_after_days=form.delete_after_days.data,

+                 multilib=form.multilib.data,

              )

  

              db.session.commit()

@@ -296,6 +298,18 @@ 

          else:

              repos_info[chroot.name_release]["arch_list"].append(chroot.arch)

              repos_info[chroot.name_release]["rpm_dl_stat"][chroot.arch] = chroot_rpms_dl_stat

+ 

+     if copr.multilib:

+         for name_release in repos_info:

+             arches = repos_info[name_release]['arch_list']

+             arch_repos = {}

+             for ch64, ch32 in models.MockChroot.multilib_pairs.items():

+                 if set([ch64, ch32]).issubset(set(arches)):

+                     arch_repos[ch64] = ch32

+ 

+             repos_info[name_release]['arch_repos'] = arch_repos

+ 

+ 

      repos_info_list = sorted(repos_info.values(), key=lambda rec: rec["name_release"])

      builds = builds_logic.BuildsLogic.get_multiple_by_copr(copr=copr).limit(1).all()

  

@@ -456,6 +470,7 @@ 

      copr.use_bootstrap_container = form.use_bootstrap_container.data

      copr.follow_fedora_branching = form.follow_fedora_branching.data

      copr.delete_after_days = form.delete_after_days.data

+     copr.multilib = form.multilib.data

      if flask.g.user.admin:

          copr.auto_prune = form.auto_prune.data

      else:

@@ -702,31 +717,58 @@ 

  @coprs_ns.route("/<username>/<copr_dirname>/repo/<name_release>/<repofile>")

  @coprs_ns.route("/g/<group_name>/<copr_dirname>/repo/<name_release>/", defaults={"repofile": None})

  @coprs_ns.route("/g/<group_name>/<copr_dirname>/repo/<name_release>/<repofile>")

- def generate_repo_file(copr_dirname, name_release, repofile, username=None, group_name=None):

+ @req_with_copr_dir

+ def generate_repo_file(copr_dir, name_release, repofile):

      """ Generate repo file for a given repo name.

          Reponame = username-coprname """

  

-     ownername = username if username else ('@'+group_name)

-     copr_dir = ComplexLogic.get_copr_dir_safe(ownername, copr_dirname)

-     return render_generate_repo_file(copr_dir, name_release)

+     arch = flask.request.args.get('arch')

+     return render_generate_repo_file(copr_dir, name_release, arch)

+ 

  

+ def render_repo_template(copr_dir, mock_chroot, arch=None):

+     repo_id = "copr:{0}:{1}:{2}{3}".format(

+         app.config["PUBLIC_COPR_HOSTNAME"].split(":")[0],

+         copr_dir.copr.owner_name.replace("@", "group_"),

+         copr_dir.name,

+         ":ml" if arch else ""

+     )

+     url = os.path.join(copr_dir.repo_url, '') # adds trailing slash

+     repo_url = generate_repo_url(mock_chroot, url, arch)

+     pubkey_url = urljoin(url, "pubkey.gpg")

+     return flask.render_template("coprs/copr_dir.repo", copr_dir=copr_dir,

+                                  url=repo_url, pubkey_url=pubkey_url,

+                                  repo_id=repo_id) + "\n"

  

- def render_generate_repo_file(copr_dir, name_release):

+ 

+ def render_generate_repo_file(copr_dir, name_release, arch=None):

      name_release = app.config["CHROOT_NAME_RELEASE_ALIAS"].get(name_release, name_release)

-     mock_chroot = coprs_logic.MockChrootsLogic.get_from_name(name_release, noarch=True).first()

+     copr = copr_dir.copr

+ 

+     # if the arch isn't specified, find the fist one starting with name_release

+     searched_chroot = name_release if not arch else name_release + "-" + arch

+ 

+     mock_chroot = None

+     for mc in copr.active_chroots:

+         if not mc.name.startswith(searched_chroot):

+             continue

+         mock_chroot = mc

  

      if not mock_chroot:

-         raise ObjectNotFound("Chroot {} does not exist".format(name_release))

+         raise ObjectNotFound("Chroot {} does not exist in {}".format(

+             searched_chroot, copr.full_name))

+ 

+     # normal, arch agnostic repofile

+     response_content = render_repo_template(copr_dir, mock_chroot)

+ 

+     # append multilib counterpart repo only upon explicit request (ach != None),

+     # and only if the chroot actually is multilib capable

+     copr = copr_dir.copr

+     if arch and copr.multilib and mock_chroot in copr.active_multilib_chroots:

+         response_content += "\n" + render_repo_template(copr_dir, mock_chroot, 'i386')

+ 

+     response = flask.make_response(response_content)

  

-     repo_id = "copr:{0}:{1}:{2}".format(app.config["PUBLIC_COPR_HOSTNAME"].split(":")[0],

-                                         copr_dir.copr.owner_name.replace("@", "group_"),

-                                         copr_dir.name)

-     url = os.path.join(copr_dir.repo_url, '') # adds trailing slash

-     repo_url = generate_repo_url(mock_chroot, url)

-     pubkey_url = urljoin(url, "pubkey.gpg")

-     response = flask.make_response(

-         flask.render_template("coprs/copr_dir.repo", copr_dir=copr_dir, url=repo_url, pubkey_url=pubkey_url,

-                               repo_id=repo_id))

      response.mimetype = "text/plain"

      response.headers["Content-Disposition"] = \

          "filename={0}.repo".format(copr_dir.repo_name)

@@ -353,6 +353,19 @@ 

      return wrapper

  

  

+ def req_with_copr_dir(f):

+     @wraps(f)

+     def wrapper(**kwargs):

+         if "group_name" in kwargs:

+             ownername = '@' + kwargs.pop("group_name")

+         else:

+             ownername = kwargs.pop("username")

+         copr_dirname = kwargs.pop("copr_dirname")

+         copr_dir = ComplexLogic.get_copr_dir_safe(ownername, copr_dirname)

+         return f(copr_dir, **kwargs)

+     return wrapper

+ 

+ 

  def send_build_icon(build):

      if not build:

          return send_file("static/status_images/unknown.png",

@@ -560,3 +560,22 @@ 

                      session["openid"] = username

                  return fn(fn_self, *args)

          return decorator.decorator(wrapper, fn)

+ 

+ 

+ def new_app_context(fn):

+     """

+     This is decorator function.  Use this anytime you need to run more than one

+     'self.tc.{get,post,..}()' requests in one test, or when you see something

+     like this in your test error output:

+         E   sqlalchemy.orm.exc.DetachedInstanceError: Instance <..>

+             is not bound to a Session; attribute refresh operation cannot

+             proceed (Background on this error at: http://sqlalche.me/e/bhk3)

+     For more info see

+     https://stackoverflow.com/questions/19395697/sqlalchemy-session-not-getting-removed-properly-in-flask-testing

+     """

+     @wraps(fn)

+     def wrapper(fn, fn_self, *args):

+         with coprs.app.app_context():

+             return fn(fn_self, *args)

+ 

+     return decorator.decorator(wrapper, fn)

@@ -69,6 +69,12 @@ 

              dict(args=(m3, https_url),

                   expected="https://example.com/path/rhel7-7.1-$basearch/")])

  

+         test_sets.extend([

+             dict(args=(m3, http_url, 'i386'),

+                  expected="http://example.com/path/rhel7-7.1-i386/"),

+             dict(args=(m3, https_url, 'ppc64le'),

+                  expected="https://example.com/path/rhel7-7.1-ppc64le/")])

+ 

          app.config["USE_HTTPS_FOR_RESULTS"] = True

          for test_set in test_sets:

              result = generate_repo_url(*test_set["args"])

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

  import json

  import flask

  import pytest

+ import re

  

  from unittest import mock

  

@@ -12,7 +13,8 @@ 

  from coprs.logic.coprs_logic import CoprsLogic, CoprDirsLogic

  from coprs.logic.actions_logic import ActionsLogic

  

- from tests.coprs_test_case import CoprsTestCase, TransactionDecorator

+ from tests.coprs_test_case import (CoprsTestCase, TransactionDecorator,

+     new_app_context)

  

  

  class TestMonitor(CoprsTestCase):

@@ -696,6 +698,72 @@ 

          assert b"baseurl=https://" in r.data

          app.config["ENFORCE_PROTOCOL_FOR_BACKEND_URL"] = orig

  

+     @new_app_context

+     def test_repofile_multilib(self, f_users, f_coprs, f_mock_chroots,

+                                f_mock_chroots_many, f_custom_builds, f_db):

+ 

+         r_non_ml_chroot = self.tc.get(

+             "/coprs/{0}/{1}/repo/fedora-18/some.repo&arch=x86_64".format(

+                 self.u1.name, self.c1.name))

+ 

+         for f_version in range(19, 24):

+             for arch in ['x86_64', 'i386']:

+                 # with disabled multilib there's no change between fedora repos,

+                 # no matter what the version or architecture is

+                 r_ml_chroot = self.tc.get(

+                     "/coprs/{0}/{1}/repo/fedora-{2}/some.repo&arch={3}".format(

+                         self.u1.name, self.c1.name, f_version, arch))

+                 assert r_ml_chroot.data == r_non_ml_chroot.data

+ 

+         self.c1.multilib = True

+         self.db.session.commit()

+ 

+         # The project is now multilib, but f18 chroot doesn't have i386

+         # countepart in c1

+ 

+         r_non_ml_chroot = self.tc.get(

+             "/coprs/{0}/{1}/repo/fedora-18/some.repo?arch=x86_64".format(

+                 self.u1.name, self.c1.name))

+ 

+         r_ml_first_chroot = self.tc.get(

+             "/coprs/{0}/{1}/repo/fedora-19/some.repo?arch=x86_64".format(

+                 self.u1.name, self.c1.name))

+ 

+         for f_version in range(19, 24):

+             # All the Fedora 19..23 chroots have both i386 and x86_64 enabled in

+             # c1, so all the repofiles need to be the same.

+             r_ml_chroot = self.tc.get(

+                 "/coprs/{0}/{1}/repo/fedora-{2}/some.repo?arch=x86_64".format(

+                     self.u1.name, self.c1.name, f_version))

+             assert r_ml_chroot.data == r_ml_first_chroot.data

+             assert r_ml_chroot.data != r_non_ml_chroot.data

+ 

+         def parse_repofile(string):

+             lines = string.split('\n')

+             repoids = [x.strip('[]') for x in lines if re.match(r'^\[.*\]$', x)]

+             baseurls = [x.split('=')[1] for x in lines if re.match(r'^baseurl=.*', x)]

+             gpgkeys = [x.split('=')[1] for x in lines if re.match(r'^gpgkey=.*', x)]

+             return repoids, baseurls, gpgkeys

+ 

+         non_ml_repofile = r_non_ml_chroot.data.decode('utf-8')

+         ml_repofile = r_ml_first_chroot.data.decode('utf-8')

+ 

+         repoids, baseurls, gpgkeys = parse_repofile(non_ml_repofile)

+         assert len(repoids) == len(baseurls) == len(gpgkeys) == 1

+ 

+         normal_gpgkey = gpgkeys[0]

+         normal_repoid = repoids[0]

+         normal_baseurl = baseurls[0]

+ 

+         repoids, baseurls, gpgkeys = parse_repofile(ml_repofile)

+         assert len(repoids) == len(baseurls) == len(gpgkeys) == 2

+ 

+         assert normal_repoid == repoids[0]

+         assert normal_repoid + ':ml' == repoids[1]

+         assert gpgkeys[0] == gpgkeys[1] == normal_gpgkey

+         assert normal_baseurl == baseurls[0]

+         assert normal_baseurl.rsplit('-', 1)[0] == baseurls[1].rsplit('-', 1)[0]

+ 

  

  class TestSearch(CoprsTestCase):

  

@@ -60,7 +60,7 @@ 

      def add(self, ownername, projectname, chroots, description=None, instructions=None, homepage=None,

              contact=None, additional_repos=None, unlisted_on_hp=False, enable_net=True, persistent=False,

              auto_prune=True, use_bootstrap_container=False, devel_mode=False,

-             delete_after_days=None):

+             delete_after_days=None, multilib=False):

          """

          Create a project

  

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

              "use_bootstrap_container": use_bootstrap_container,

              "devel_mode": devel_mode,

              "delete_after_days": delete_after_days,

+             "multilib": multilib,

          }

          request = Request(endpoint, api_base_url=self.api_base_url, method=POST,

                            params=params, data=data, auth=self.auth)

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

      def edit(self, ownername, projectname, chroots=None, description=None, instructions=None, homepage=None,

               contact=None, additional_repos=None, unlisted_on_hp=None, enable_net=None,

               auto_prune=None, use_bootstrap_container=None, devel_mode=None,

-              delete_after_days=None):

+              delete_after_days=None, multilib=None):

          """

          Edit a project

  

@@ -147,6 +148,7 @@ 

              "use_bootstrap_container": use_bootstrap_container,

              "devel_mode": devel_mode,

              "delete_after_days": delete_after_days,

+             "multilib": multilib,

          }

          request = Request(endpoint, api_base_url=self.api_base_url, method=POST,

                            params=params, data=data, auth=self.auth)

file modified
+4 -2

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

  #!/bin/bash

- #python -B -m pytest --cov-report term-missing --cov ./copr_cli/ tests $@

  

- PYTHONPATH=.:$PYTHONPATH python -B -m pytest --cov-report term-missing --cov ./copr/client_v2/ -s $@

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

+ export PYTHONPATH="$absdir"

+ 

+ python3 -B -m pytest --cov-report term-missing copr/test "$@"

Metadata Update from @praiskup:
- Pull-request tagged with: needs-work

3 months ago

rebased onto b8a8bbea15cd0f289caf8cb4414480d69967307c

3 months ago

I need to add unittests and sanity tests, but otherwise it looks to be ready for review.

Web UI looks like this:
Screenshot_20190830_155612.png

The dnf copr enable/disable/remove API stays unchanged by this PR (maintainer of copr project is about to decide whether the project is or is not multilib, users are just consumer and they don't care).

From WebUI perspective, one needs to pick particular button to get the multilib version. This seems to be good start to me, we can improve anytime in future.

The multilib variant of repofile looks like:

[copr:localhost:praiskup:ml1]
name=Copr repo for ml1 owned by praiskup
baseurl=http://copr-be.cloud.fedoraproject.org/results/praiskup/ml1/fedora-$releasever-$basearch/
type=rpm-md
skip_if_unavailable=True
gpgcheck=1
gpgkey=http://copr-be.cloud.fedoraproject.org/results/praiskup/ml1/pubkey.gpg
repo_gpgcheck=0
enabled=1
enabled_metadata=1

[copr:localhost:praiskup:ml1:ml]
name=Copr repo for ml1 owned by praiskup
baseurl=http://copr-be.cloud.fedoraproject.org/results/praiskup/ml1/fedora-$releasever-i386/
type=rpm-md
skip_if_unavailable=True
gpgcheck=1
gpgkey=http://copr-be.cloud.fedoraproject.org/results/praiskup/ml1/pubkey.gpg
repo_gpgcheck=0
enabled=1
enabled_metadata=1

rebased onto 49675d3bbe1bf959087f0bb25752f01ca6378fbe

2 months ago

on mtg we discussed this layout, I'll will fix that:

Screenshot_20190903_094418.png

Plus @jkadlcik/@msuchy did not like the new route idea, so I'll switch this to the ?arch=x86_64 variant.

rebased onto e812a588e86fa79ae5cfdcd9cc10d6ff377dc6c2

2 months ago

Metadata Update from @praiskup:
- Pull-request untagged with: needs-work

2 months ago

rebased onto 6ad78dd

2 months ago

Ok, merging. The CI failures are irrelevant to this PR.

Commit d326a95 fixes this pull-request

Pull-Request has been merged by praiskup

2 months ago
Metadata