From 776fb1e991224661584971daf8a002b9170ebc1c Mon Sep 17 00:00:00 2001 From: Jakub Kadlčík Date: Nov 16 2016 00:03:14 +0000 Subject: [PATCH 1/8] [frontend] add api method for translating module NVR to DNF repo url --- diff --git a/frontend/coprs_frontend/coprs/forms.py b/frontend/coprs_frontend/coprs/forms.py index 0d2ab5b..8ca4b7a 100644 --- a/frontend/coprs_frontend/coprs/forms.py +++ b/frontend/coprs_frontend/coprs/forms.py @@ -774,3 +774,8 @@ class CreateModuleForm(wtf.Form): self.errors["profiles"] = ["Missing profile name"] return False return True + + +class ModuleRepo(wtf.Form): + owner = wtforms.StringField("Owner Name", validators=[wtforms.validators.DataRequired()]) + nvr = wtforms.StringField("Name-Version-Release", validators=[wtforms.validators.DataRequired()]) diff --git a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py index 000b030..665a483 100644 --- a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py +++ b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py @@ -902,3 +902,24 @@ def copr_build_config(copr, chroot): raise LegacyApiError('Chroot not found.') return flask.jsonify(output) + + +@api_ns.route("/module/repo/", methods=["POST"]) +def copr_module_repo(): + """ + :return: URL to a DNF repository for the module + """ + # @TODO Current premise is that Copr's name == NVR + # @TODO Get rid of it after creating `module` database table + + form = forms.ModuleRepo(csrf_enabled=False) + if not form.validate_on_submit(): + raise LegacyApiError(form.errors) + + if form.owner.data[0] == "@": + copr = ComplexLogic.get_group_copr_safe(form.owner.data[1:], form.nvr.data) + else: + copr = ComplexLogic.get_copr_safe(form.owner.data, form.nvr.data) + + repo = helpers.copr_url("coprs_ns.generate_module_repo_file", copr, _external=True) + return flask.jsonify({"output": "ok", "repo": repo}) diff --git a/frontend/coprs_frontend/tests/test_views/test_api_ns/test_api_general.py b/frontend/coprs_frontend/tests/test_views/test_api_ns/test_api_general.py index 9c0ecd8..f730633 100644 --- a/frontend/coprs_frontend/tests/test_views/test_api_ns/test_api_general.py +++ b/frontend/coprs_frontend/tests/test_views/test_api_ns/test_api_general.py @@ -101,3 +101,29 @@ class TestCreateCopr(CoprsTestCase): # self.db.session.add_all([self.u1, self.mc1]) # # + + +class TestModuleRepo(CoprsTestCase): + endpoint = "/api/module/repo/" + + def test_api_module_repo(self, f_users, f_coprs, f_db): + self.db.session.add_all([self.u1, self.c1]) + + data = {"owner": self.u1.name, "nvr": self.c1.name} + r = self.tc.post(self.endpoint, data=data) + response = json.loads(r.data.decode("utf-8")) + assert response["output"] == "ok" + assert response["repo"] == "http://localhost/coprs/user1/foocopr/repo/modules/" + + r = self.tc.get(response["repo"]) + assert r.status_code == 200 + assert "[{}-{}]".format(self.u1.name, self.c1.name) in r.data + assert "url = " in r.data + + def test_api_module_repo_no_params(self): + error = "This field is required." + r = self.tc.post(self.endpoint, data={}) + response = json.loads(r.data.decode("utf-8")) + assert response["output"] == "notok" + assert error in response["error"]["owner"] + assert error in response["error"]["nvr"] From 69b1538553dc44a0cfe5f86992e7a190ec435e5b Mon Sep 17 00:00:00 2001 From: Jakub Kadlčík Date: Nov 16 2016 00:06:12 +0000 Subject: [PATCH 2/8] [python] add method for fetching /api/module/repo --- diff --git a/python/copr/client/client.py b/python/copr/client/client.py index 2b6755d..3944c08 100644 --- a/python/copr/client/client.py +++ b/python/copr/client/client.py @@ -1344,3 +1344,32 @@ class CoprClient(UnicodeMixin): ] ) return response + + def get_module_repo(self, owner, nvr): + """ Gets URL to module DNF repository + + :param owner: str owner name (can be user or @group) + :param nvr: str module name-version-release + + :return: :py:class:`~.responses.CoprResponse` + with additional fields: + + - **handle:** :py:class:`~.responses.BaseHandle` + - text fields: "repo" + + """ + url = "{}/module/repo/".format(self.api_url) + data = {"owner": owner, "nvr": nvr} + + fetch = self._fetch(url, data=data, skip_auth=True, method="post") + response = CoprResponse( + client=self, + method="get_module_repo", + data=fetch, + parsers=[ + CommonMsgErrorOutParser, + ProjectListParser + ] + ) + response.handle = BaseHandle(client=self, response=response) + return response From 402d7d959dadd47200311abe4c0a146217ff7c6c Mon Sep 17 00:00:00 2001 From: Jakub Kadlčík Date: Nov 16 2016 00:06:12 +0000 Subject: [PATCH 3/8] [frontend] return URL to module data on backend --- diff --git a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py index 665a483..7497050 100644 --- a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py +++ b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py @@ -921,5 +921,4 @@ def copr_module_repo(): else: copr = ComplexLogic.get_copr_safe(form.owner.data, form.nvr.data) - repo = helpers.copr_url("coprs_ns.generate_module_repo_file", copr, _external=True) - return flask.jsonify({"output": "ok", "repo": repo}) + return flask.jsonify({"output": "ok", "repo": copr.modules_url}) diff --git a/frontend/coprs_frontend/tests/test_views/test_api_ns/test_api_general.py b/frontend/coprs_frontend/tests/test_views/test_api_ns/test_api_general.py index f730633..4b58938 100644 --- a/frontend/coprs_frontend/tests/test_views/test_api_ns/test_api_general.py +++ b/frontend/coprs_frontend/tests/test_views/test_api_ns/test_api_general.py @@ -113,12 +113,7 @@ class TestModuleRepo(CoprsTestCase): r = self.tc.post(self.endpoint, data=data) response = json.loads(r.data.decode("utf-8")) assert response["output"] == "ok" - assert response["repo"] == "http://localhost/coprs/user1/foocopr/repo/modules/" - - r = self.tc.get(response["repo"]) - assert r.status_code == 200 - assert "[{}-{}]".format(self.u1.name, self.c1.name) in r.data - assert "url = " in r.data + assert response["repo"] == "http://copr-be-dev.cloud.fedoraproject.org/results/user1/foocopr/modules" def test_api_module_repo_no_params(self): error = "This field is required." From 8b67cc61ef4a78822c82535b2ca783356e5d0ef6 Mon Sep 17 00:00:00 2001 From: Jakub Kadlčík Date: Nov 16 2016 00:06:12 +0000 Subject: [PATCH 4/8] [frontend] implement method for querying copr by owner --- diff --git a/frontend/coprs_frontend/coprs/logic/complex_logic.py b/frontend/coprs_frontend/coprs/logic/complex_logic.py index e875c1b..e5608e0 100644 --- a/frontend/coprs_frontend/coprs/logic/complex_logic.py +++ b/frontend/coprs_frontend/coprs/logic/complex_logic.py @@ -91,6 +91,12 @@ class ComplexLogic(object): .format(user_name, copr_name)) @staticmethod + def get_copr_by_owner_safe(owner_name, copr_name, **kwargs): + if owner_name[0] == "@": + return ComplexLogic.get_group_copr_safe(owner_name[1:], copr_name, **kwargs) + return ComplexLogic.get_copr_safe(owner_name, copr_name, **kwargs) + + @staticmethod def get_copr_by_id_safe(copr_id): try: return CoprsLogic.get_by_id(copr_id).one() From 5497b1fa2a178888e2b904a43888cf08ea48c195 Mon Sep 17 00:00:00 2001 From: Jakub Kadlčík Date: Nov 16 2016 00:07:19 +0000 Subject: [PATCH 5/8] [frontend][python] request project name from client --- diff --git a/frontend/coprs_frontend/coprs/forms.py b/frontend/coprs_frontend/coprs/forms.py index 8ca4b7a..bfa3083 100644 --- a/frontend/coprs_frontend/coprs/forms.py +++ b/frontend/coprs_frontend/coprs/forms.py @@ -778,4 +778,7 @@ class CreateModuleForm(wtf.Form): class ModuleRepo(wtf.Form): owner = wtforms.StringField("Owner Name", validators=[wtforms.validators.DataRequired()]) - nvr = wtforms.StringField("Name-Version-Release", validators=[wtforms.validators.DataRequired()]) + copr = wtforms.StringField("Copr Name", validators=[wtforms.validators.DataRequired()]) + name = wtforms.StringField("Name", validators=[wtforms.validators.DataRequired()]) + version = wtforms.StringField("Version", validators=[wtforms.validators.DataRequired()]) + release = wtforms.StringField("Release", validators=[wtforms.validators.DataRequired()]) diff --git a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py index 7497050..75d728c 100644 --- a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py +++ b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py @@ -909,16 +909,11 @@ def copr_module_repo(): """ :return: URL to a DNF repository for the module """ - # @TODO Current premise is that Copr's name == NVR - # @TODO Get rid of it after creating `module` database table - form = forms.ModuleRepo(csrf_enabled=False) if not form.validate_on_submit(): raise LegacyApiError(form.errors) - if form.owner.data[0] == "@": - copr = ComplexLogic.get_group_copr_safe(form.owner.data[1:], form.nvr.data) - else: - copr = ComplexLogic.get_copr_safe(form.owner.data, form.nvr.data) + copr = ComplexLogic.get_copr_by_owner_safe(form.owner.data, form.copr.data) + module = ModulesLogic.get_by_nvr(copr, form.name.data, form.version.data, form.release.data).first() - return flask.jsonify({"output": "ok", "repo": copr.modules_url}) + return flask.jsonify({"output": "ok", "repo": module.copr.modules_url}) diff --git a/python/copr/client/client.py b/python/copr/client/client.py index 3944c08..6140482 100644 --- a/python/copr/client/client.py +++ b/python/copr/client/client.py @@ -1345,11 +1345,14 @@ class CoprClient(UnicodeMixin): ) return response - def get_module_repo(self, owner, nvr): + def get_module_repo(self, owner, copr, name, version, release): """ Gets URL to module DNF repository :param owner: str owner name (can be user or @group) - :param nvr: str module name-version-release + :param copr: str copr name + :param name: str module name + :param version: str module version + :param release: str module release :return: :py:class:`~.responses.CoprResponse` with additional fields: @@ -1359,7 +1362,7 @@ class CoprClient(UnicodeMixin): """ url = "{}/module/repo/".format(self.api_url) - data = {"owner": owner, "nvr": nvr} + data = {"owner": owner, "copr": copr, "name": name, "version": version, "release": release} fetch = self._fetch(url, data=data, skip_auth=True, method="post") response = CoprResponse( From 1ec5934e8d5fe352c4f79f1c868eeba904b063bf Mon Sep 17 00:00:00 2001 From: Jakub Kadlčík Date: Nov 16 2016 00:07:19 +0000 Subject: [PATCH 6/8] [frontend] move modules_url as repo_url to Module class --- diff --git a/frontend/coprs_frontend/coprs/models.py b/frontend/coprs_frontend/coprs/models.py index 08f605b..14edec2 100644 --- a/frontend/coprs_frontend/coprs/models.py +++ b/frontend/coprs_frontend/coprs/models.py @@ -343,10 +343,6 @@ class Copr(db.Model, helpers.Serializer, CoprSearchRelatedData): self.full_name]) @property - def modules_url(self): - return "/".join([self.repo_url, "modules"]) - - @property def repo_id(self): if self.is_a_group_project: return "group_{}-{}".format(self.group.name, self.name) @@ -1157,3 +1153,7 @@ class Module(db.Model, helpers.Serializer): @property def action(self): return Action.query.filter(Action.object_type == "module").filter(Action.object_id == self.id).first() + + @property + def repo_url(self): + return "/".join([self.copr.repo_url, "modules"]) diff --git a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py index 75d728c..d4254d2 100644 --- a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py +++ b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py @@ -916,4 +916,4 @@ def copr_module_repo(): copr = ComplexLogic.get_copr_by_owner_safe(form.owner.data, form.copr.data) module = ModulesLogic.get_by_nvr(copr, form.name.data, form.version.data, form.release.data).first() - return flask.jsonify({"output": "ok", "repo": module.copr.modules_url}) + return flask.jsonify({"output": "ok", "repo": module.repo_url}) From 9eda78505a4ad8200e5f089d7302ffb73df15aef Mon Sep 17 00:00:00 2001 From: Jakub Kadlčík Date: Nov 16 2016 00:26:08 +0000 Subject: [PATCH 7/8] [frontend][python] get module repo for given architecture --- diff --git a/frontend/coprs_frontend/coprs/forms.py b/frontend/coprs_frontend/coprs/forms.py index bfa3083..45d0e14 100644 --- a/frontend/coprs_frontend/coprs/forms.py +++ b/frontend/coprs_frontend/coprs/forms.py @@ -782,3 +782,4 @@ class ModuleRepo(wtf.Form): name = wtforms.StringField("Name", validators=[wtforms.validators.DataRequired()]) version = wtforms.StringField("Version", validators=[wtforms.validators.DataRequired()]) release = wtforms.StringField("Release", validators=[wtforms.validators.DataRequired()]) + arch = wtforms.StringField("Arch", validators=[wtforms.validators.DataRequired()]) diff --git a/frontend/coprs_frontend/coprs/models.py b/frontend/coprs_frontend/coprs/models.py index 14edec2..19d97b4 100644 --- a/frontend/coprs_frontend/coprs/models.py +++ b/frontend/coprs_frontend/coprs/models.py @@ -1154,6 +1154,8 @@ class Module(db.Model, helpers.Serializer): def action(self): return Action.query.filter(Action.object_type == "module").filter(Action.object_id == self.id).first() - @property - def repo_url(self): - return "/".join([self.copr.repo_url, "modules"]) + def repo_url(self, arch): + # @TODO Get rid of OS name from module path, see how koji does it + # https://kojipkgs.stg.fedoraproject.org/repos/module-base-runtime-0.25-9/latest/x86_64/toplink/packages/module-build-macros/0.1/ + module_dir = "fedora-rawhide-{}+{}-{}-{}".format(arch, self.name, self.version, self.release) + return "/".join([self.copr.repo_url, "modules", module_dir, "latest", arch]) diff --git a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py index d4254d2..28937e6 100644 --- a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py +++ b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py @@ -916,4 +916,4 @@ def copr_module_repo(): copr = ComplexLogic.get_copr_by_owner_safe(form.owner.data, form.copr.data) module = ModulesLogic.get_by_nvr(copr, form.name.data, form.version.data, form.release.data).first() - return flask.jsonify({"output": "ok", "repo": module.repo_url}) + return flask.jsonify({"output": "ok", "repo": module.repo_url(form.arch.data)}) diff --git a/python/copr/client/client.py b/python/copr/client/client.py index 6140482..5e1cf90 100644 --- a/python/copr/client/client.py +++ b/python/copr/client/client.py @@ -1345,7 +1345,7 @@ class CoprClient(UnicodeMixin): ) return response - def get_module_repo(self, owner, copr, name, version, release): + def get_module_repo(self, owner, copr, name, version, release, arch): """ Gets URL to module DNF repository :param owner: str owner name (can be user or @group) @@ -1353,6 +1353,7 @@ class CoprClient(UnicodeMixin): :param name: str module name :param version: str module version :param release: str module release + :param arch: str build architecture :return: :py:class:`~.responses.CoprResponse` with additional fields: @@ -1362,7 +1363,7 @@ class CoprClient(UnicodeMixin): """ url = "{}/module/repo/".format(self.api_url) - data = {"owner": owner, "copr": copr, "name": name, "version": version, "release": release} + data = {"owner": owner, "copr": copr, "name": name, "version": version, "release": release, "arch": arch} fetch = self._fetch(url, data=data, skip_auth=True, method="post") response = CoprResponse( From 46ed3a5ffac576cbf41f6f9b20233abe419673b5 Mon Sep 17 00:00:00 2001 From: Jakub Kadlčík Date: Nov 21 2016 05:16:07 +0000 Subject: [PATCH 8/8] [frontend] fix module repo tests --- diff --git a/frontend/coprs_frontend/tests/coprs_test_case.py b/frontend/coprs_frontend/tests/coprs_test_case.py index cc88a22..4234b85 100644 --- a/frontend/coprs_frontend/tests/coprs_test_case.py +++ b/frontend/coprs_frontend/tests/coprs_test_case.py @@ -385,6 +385,17 @@ class CoprsTestCase(object): created_on=int(time.time())) self.db.session.add_all([self.a1, self.a2, self.a3]) + @pytest.fixture + def f_modules(self): + self.m1 = models.Module(name="first-module", version="1", release="1", copr_id=self.c1.id, copr=self.c1, + summary="Sum 1", description="Desc 1", created_on=time.time()) + self.m1 = models.Module(name="second-module", version="strver", release="3", copr_id=self.c1.id, copr=self.c1, + summary="Sum 2", description="Desc 2", created_on=time.time()) + self.m1 = models.Module(name="third-module", version="3", release="1", copr_id=self.c2.id, copr=self.c2, + summary="Sum 3", description="Desc 3", created_on=time.time()) + self.db.session.add_all([self.m1]) + + def request_rest_api_with_auth(self, url, login=None, token=None, content=None, method="GET", diff --git a/frontend/coprs_frontend/tests/test_views/test_api_ns/test_api_general.py b/frontend/coprs_frontend/tests/test_views/test_api_ns/test_api_general.py index 4b58938..6a71599 100644 --- a/frontend/coprs_frontend/tests/test_views/test_api_ns/test_api_general.py +++ b/frontend/coprs_frontend/tests/test_views/test_api_ns/test_api_general.py @@ -106,19 +106,20 @@ class TestCreateCopr(CoprsTestCase): class TestModuleRepo(CoprsTestCase): endpoint = "/api/module/repo/" - def test_api_module_repo(self, f_users, f_coprs, f_db): - self.db.session.add_all([self.u1, self.c1]) + def test_api_module_repo(self, f_users, f_coprs, f_modules, f_db): + data = {"owner": self.u1.name, "copr": self.c1.name, "name": "first-module", + "version": "1", "release": "1", "arch": "x86_64"} - data = {"owner": self.u1.name, "nvr": self.c1.name} r = self.tc.post(self.endpoint, data=data) response = json.loads(r.data.decode("utf-8")) assert response["output"] == "ok" - assert response["repo"] == "http://copr-be-dev.cloud.fedoraproject.org/results/user1/foocopr/modules" + assert response["repo"] == "http://copr-be-dev.cloud.fedoraproject.org/results/user1/foocopr/modules/"\ + "fedora-rawhide-x86_64+first-module-1-1/latest/x86_64" def test_api_module_repo_no_params(self): error = "This field is required." r = self.tc.post(self.endpoint, data={}) response = json.loads(r.data.decode("utf-8")) assert response["output"] == "notok" - assert error in response["error"]["owner"] - assert error in response["error"]["nvr"] + for key in ["owner", "copr", "name", "version", "release", "arch"]: + assert error in response["error"][key]