This is an automated email from the git hooks/post-receive script.
praiskup pushed a commit to branch master in repository copr/copr.
commit d326a95dcce907549395fb728de460b53c9d8195 Author: Pavel Raiskup praiskup@redhat.com AuthorDate: Wed Aug 21 15:35:08 2019 +0200
frontend, cli, python: support multilib projects
We create a new flask route for generating arch-specific repofiles. Using this, and using the new per-project knob Project.multilib, frontend can easily determine whether multilib repofile should be generated or not.
For now, if the project is configured for multilib, we don't do any magic on backend. We simply provide repofile with two (32bit and 64bit) repositories (the 32bit one contains `:ml` suffix in repoid). And then - if user does 'dnf copr enable' against multilib project - both repositories are available on the system.
This trivial approach can be pretty easily enhanced in future, especially if we wanted to support similar approach for per-package multilib detection (as e.g. pungi has), we'd only have to drop the second - multilib - variant of repo from repofile.
The fact that we are adding a new routenow, and that we have the `:ml` suffix in repoid - we need to update the dnf-plugins-core package as well [1]. But for compat with previous versions of dnf-plugins-core we keep the old routes as well.
[1] https://github.com/rpm-software-management/dnf-plugins-core/pull/350
Fixes: #1 Merges: #938 --- cli/copr_cli/main.py | 15 +++++ cli/tests/test_cli.py | 31 ++++++++++ .../0dbdd06fb850_multilib_knob_on_copr_public.py | 20 +++++++ frontend/coprs_frontend/coprs/forms.py | 10 ++++ frontend/coprs_frontend/coprs/helpers.py | 4 +- frontend/coprs_frontend/coprs/models.py | 28 +++++++++ .../coprs_frontend/coprs/templates/_helpers.html | 12 +++- .../coprs/templates/coprs/_coprs_forms.html | 1 + .../coprs/templates/coprs/detail/overview.html | 7 +++ .../coprs/views/apiv3_ns/apiv3_projects.py | 1 + .../coprs/views/coprs_ns/coprs_general.py | 69 +++++++++++++++++---- frontend/coprs_frontend/tests/coprs_test_case.py | 19 ++++++ frontend/coprs_frontend/tests/test_helpers.py | 6 ++ .../test_views/test_coprs_ns/test_coprs_general.py | 70 +++++++++++++++++++++- python/copr/v3/proxies/project.py | 6 +- 15 files changed, 280 insertions(+), 19 deletions(-)
diff --git a/cli/copr_cli/main.py b/cli/copr_cli/main.py index 79878b3..d08b4ee 100644 --- a/cli/copr_cli/main.py +++ b/cli/copr_cli/main.py @@ -358,6 +358,7 @@ class Commands(object): 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 @@ class Commands(object): 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 @@ def setup_parser(): 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 @@ def setup_parser(): 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 diff --git a/cli/tests/test_cli.py b/cli/tests/test_cli.py index 5eb7b0d..2a3204f 100644 --- a/cli/tests/test_cli.py +++ b/cli/tests/test_cli.py @@ -375,6 +375,37 @@ def test_create_project(config_from_file, project_proxy_add, capsys): "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"
diff --git a/frontend/coprs_frontend/alembic/schema/versions/0dbdd06fb850_multilib_knob_on_copr_public.py b/frontend/coprs_frontend/alembic/schema/versions/0dbdd06fb850_multilib_knob_on_copr_public.py new file mode 100644 index 0000000..cbdd57d --- /dev/null +++ b/frontend/coprs_frontend/alembic/schema/versions/0dbdd06fb850_multilib_knob_on_copr_public.py @@ -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') diff --git a/frontend/coprs_frontend/coprs/forms.py b/frontend/coprs_frontend/coprs/forms.py index 9e8f8d9..3c03c15 100644 --- a/frontend/coprs_frontend/coprs/forms.py +++ b/frontend/coprs_frontend/coprs/forms.py @@ -330,6 +330,15 @@ class CoprFormFactory(object): 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 @@ class CoprModifyForm(FlaskForm): # 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): diff --git a/frontend/coprs_frontend/coprs/helpers.py b/frontend/coprs_frontend/coprs/helpers.py index 49bd9f9..083ad8f 100644 --- a/frontend/coprs_frontend/coprs/helpers.py +++ b/frontend/coprs_frontend/coprs/helpers.py @@ -189,7 +189,7 @@ def parse_package_name(pkg): 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 @@ def generate_repo_url(mock_chroot, url):
url = posixpath.join( url, "{0}-{1}-{2}/".format(mock_chroot.os_release, - os_version, "$basearch")) + os_version, arch or '$basearch'))
return url
diff --git a/frontend/coprs_frontend/coprs/models.py b/frontend/coprs_frontend/coprs/models.py index 42d7542..c68a61e 100644 --- a/frontend/coprs_frontend/coprs/models.py +++ b/frontend/coprs_frontend/coprs/models.py @@ -274,6 +274,8 @@ class _CoprPublic(db.Model, helpers.Serializer, CoprSearchRelatedData): # 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 @@ class Copr(db.Model, helpers.Serializer): 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 @@ class MockChroot(db.Model, helpers.Serializer):
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 diff --git a/frontend/coprs_frontend/coprs/templates/_helpers.html b/frontend/coprs_frontend/coprs/templates/_helpers.html index 37f3ae8..97f0c4e 100644 --- a/frontend/coprs_frontend/coprs/templates/_helpers.html +++ b/frontend/coprs_frontend/coprs/templates/_helpers.html @@ -445,13 +445,23 @@ https://admin.fedoraproject.org/accounts/group/view/%7B%7Bname%7D%7D {%- endmacro -%}
-{% macro repo_file_href(copr, repo) %} +{% 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 %}
diff --git a/frontend/coprs_frontend/coprs/templates/coprs/_coprs_forms.html b/frontend/coprs_frontend/coprs/templates/coprs/_coprs_forms.html index 9ce1c83..2ecedc4 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/_coprs_forms.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/_coprs_forms.html @@ -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, diff --git a/frontend/coprs_frontend/coprs/templates/coprs/detail/overview.html b/frontend/coprs_frontend/coprs/templates/coprs/detail/overview.html index 82fe603..311cfff 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/detail/overview.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/detail/overview.html @@ -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> diff --git a/frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_projects.py b/frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_projects.py index e1b037c..520e3ad 100644 --- a/frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_projects.py +++ b/frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_projects.py @@ -127,6 +127,7 @@ def add_project(ownername): 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, diff --git a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py index 1cb92ed..7f6f06d 100644 --- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py +++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py @@ -189,6 +189,7 @@ def copr_new(username=None, group_name=None): 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() @@ -297,6 +298,18 @@ def render_copr_detail(copr): 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()
@@ -457,6 +470,7 @@ def process_copr_update(copr, form): 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: @@ -707,25 +721,54 @@ def process_legal_flag(copr): def generate_repo_file(copr_dir, name_release, repofile): """ Generate repo file for a given repo name. Reponame = username-coprname """ - 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) diff --git a/frontend/coprs_frontend/tests/coprs_test_case.py b/frontend/coprs_frontend/tests/coprs_test_case.py index 4d820eb..b1425da 100644 --- a/frontend/coprs_frontend/tests/coprs_test_case.py +++ b/frontend/coprs_frontend/tests/coprs_test_case.py @@ -560,3 +560,22 @@ class TransactionDecorator(object): 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-... + """ + @wraps(fn) + def wrapper(fn, fn_self, *args): + with coprs.app.app_context(): + return fn(fn_self, *args) + + return decorator.decorator(wrapper, fn) diff --git a/frontend/coprs_frontend/tests/test_helpers.py b/frontend/coprs_frontend/tests/test_helpers.py index c978d67..400c905 100644 --- a/frontend/coprs_frontend/tests/test_helpers.py +++ b/frontend/coprs_frontend/tests/test_helpers.py @@ -69,6 +69,12 @@ class TestHelpers(CoprsTestCase): 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"]) diff --git a/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_general.py b/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_general.py index 3468024..1f8307c 100644 --- a/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_general.py +++ b/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_general.py @@ -1,6 +1,7 @@ import json import flask import pytest +import re
from unittest import mock
@@ -12,7 +13,8 @@ from coprs import app, models 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 @@ class TestCoprRepoGeneration(CoprsTestCase): 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):
diff --git a/python/copr/v3/proxies/project.py b/python/copr/v3/proxies/project.py index add5d86..ae22ed1 100644 --- a/python/copr/v3/proxies/project.py +++ b/python/copr/v3/proxies/project.py @@ -60,7 +60,7 @@ class ProjectProxy(BaseProxy): 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 @@ class ProjectProxy(BaseProxy): "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 @@ class ProjectProxy(BaseProxy): 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 @@ class ProjectProxy(BaseProxy): "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)