From 86803ace65aea255f5639cf3321c175a49ee6940 Mon Sep 17 00:00:00 2001 From: Kairo de Araujo Date: Mon, 7 Oct 2024 10:04:11 +0200 Subject: [PATCH] chore: Implement TUF Interface and RSTUF Service - Implements the tuf/interfaces.py with ITUFService - Implements the tuf/services.py with RSTUFService - Refactor the tuf/tasks.py metadata_update to use the Service - Add unit tests Signed-off-by: Kairo de Araujo --- tests/conftest.py | 9 ++ tests/unit/tuf/test_init.py | 32 ++++ tests/unit/tuf/test_services.py | 267 ++++++++++++++++++++++++++++++++ tests/unit/tuf/test_tasks.py | 15 +- tests/unit/tuf/test_tuf.py | 121 --------------- warehouse/tuf/__init__.py | 86 +--------- warehouse/tuf/interfaces.py | 35 +++++ warehouse/tuf/services.py | 92 +++++++++++ warehouse/tuf/tasks.py | 11 +- 9 files changed, 452 insertions(+), 216 deletions(-) create mode 100644 tests/unit/tuf/test_init.py create mode 100644 tests/unit/tuf/test_services.py delete mode 100644 tests/unit/tuf/test_tuf.py create mode 100644 warehouse/tuf/interfaces.py create mode 100644 warehouse/tuf/services.py diff --git a/tests/conftest.py b/tests/conftest.py index 3e9fd971a949..d7e4dc81468c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -63,6 +63,8 @@ from warehouse.packaging.interfaces import IProjectService from warehouse.subscriptions import services as subscription_services from warehouse.subscriptions.interfaces import IBillingService, ISubscriptionService +from warehouse.tuf import services as tuf_services +from warehouse.tuf.interfaces import ITUFService from .common.db import Session from .common.db.accounts import EmailFactory, UserFactory @@ -179,6 +181,7 @@ def pyramid_services( integrity_service, macaroon_service, helpdesk_service, + tuf_service, ): services = _Services() @@ -201,6 +204,7 @@ def pyramid_services( services.register_service(integrity_service, IIntegrityService, None) services.register_service(macaroon_service, IMacaroonService, None, name="") services.register_service(helpdesk_service, IHelpDeskService, None) + services.register_service(tuf_service, ITUFService, None) return services @@ -611,6 +615,11 @@ def helpdesk_service(): return helpdesk_services.ConsoleHelpDeskService() +@pytest.fixture +def tuf_service(db_session): + return tuf_services.RSTUFService(db_session) + + class QueryRecorder: def __init__(self): self.queries = [] diff --git a/tests/unit/tuf/test_init.py b/tests/unit/tuf/test_init.py new file mode 100644 index 000000000000..8ae13dadddda --- /dev/null +++ b/tests/unit/tuf/test_init.py @@ -0,0 +1,32 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pretend + +from warehouse import tuf +from warehouse.tuf.interfaces import ITUFService +from warehouse.tuf.services import rstuf_factory + + +def test_includeme(): + config = pretend.stub( + register_service_factory=pretend.call_recorder( + lambda factory, iface, name=None: None + ), + maybe_dotted=pretend.call_recorder(lambda *a: "http://rstuf"), + ) + + tuf.includeme(config) + + assert config.register_service_factory.calls == [ + pretend.call(rstuf_factory, ITUFService), + ] diff --git a/tests/unit/tuf/test_services.py b/tests/unit/tuf/test_services.py new file mode 100644 index 000000000000..69f7382889e4 --- /dev/null +++ b/tests/unit/tuf/test_services.py @@ -0,0 +1,267 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pretend +import pytest + +from requests import Session +from zope.interface.verify import verifyClass + +from warehouse.tuf import services +from warehouse.tuf.interfaces import ITUFService +from warehouse.tuf.services import RSTUFService + + +class TestRSTUFService: + + def test_verify_service(self): + assert verifyClass(ITUFService, RSTUFService) + + def basic_init(self, db_request): + db_request.registry.settings = {"rstuf.api_url": "http://rstuf"} + + rstuf = RSTUFService(db_request) + + assert rstuf is not None + assert rstuf.api_url == "http://rstuf" + assert isinstance(rstuf.requests, Session) + + def test_create_service(self, db_request): + db_request.registry.settings = {"rstuf.api_url": "http://rstuf"} + + rstuf = RSTUFService.create_service(db_request) + + assert rstuf is not None + assert rstuf.api_url == "http://rstuf" + assert isinstance(rstuf.requests, Session) + + def test_get_task_state(self, monkeypatch, db_request): + db_request.registry.settings = {"rstuf.api_url": "http://rstuf"} + + response = pretend.stub( + raise_for_status=pretend.call_recorder(lambda: None), + json=pretend.call_recorder(lambda: {"data": {"state": "SUCCESS"}}), + ) + test_session = pretend.stub( + get=pretend.call_recorder(lambda *a, **kw: response) + ) + fake_session = pretend.call_recorder(lambda: test_session) + monkeypatch.setattr(services, "Session", fake_session) + + rstuf = RSTUFService.create_service(db_request) + + state = rstuf.get_task_state("123456") + + assert state == "SUCCESS" + + assert test_session.get.calls == [ + pretend.call("http://rstuf/api/v1/task?task_id=123456"), + ] + assert response.raise_for_status.calls == [pretend.call()] + assert response.json.calls == [pretend.call()] + + def test_post_artifacts(self, monkeypatch, db_request): + db_request.registry.settings = {"rstuf.api_url": "http://rstuf"} + + response = pretend.stub( + raise_for_status=pretend.call_recorder(lambda: None), + json=pretend.call_recorder(lambda: {"data": {"task_id": "123456"}}), + ) + test_session = pretend.stub( + post=pretend.call_recorder(lambda *a, **kw: response) + ) + fake_session = pretend.call_recorder(lambda: test_session) + monkeypatch.setattr(services, "Session", fake_session) + + rstuf = RSTUFService.create_service(db_request) + + task_id = rstuf.post_artifacts({"targets": [{"path": "name"}]}) + + assert task_id == "123456" + + assert test_session.post.calls == [ + pretend.call( + "http://rstuf/api/v1/artifacts", json={"targets": [{"path": "name"}]} + ), + ] + assert response.raise_for_status.calls == [pretend.call()] + assert response.json.calls == [pretend.call()] + + def test_post_artifacts_no_data_from_rstuf(self, monkeypatch, db_request): + db_request.registry.settings = {"rstuf.api_url": "http://rstuf"} + + response = pretend.stub( + raise_for_status=pretend.call_recorder(lambda: None), + json=pretend.call_recorder(lambda: {"data": None}), + ) + test_session = pretend.stub( + post=pretend.call_recorder(lambda *a, **kw: response) + ) + fake_session = pretend.call_recorder(lambda: test_session) + monkeypatch.setattr(services, "Session", fake_session) + + rstuf = RSTUFService.create_service(db_request) + + with pytest.raises(services.RSTUFError) as e: + rstuf.post_artifacts({"targets": [{"path": "name"}]}) + + assert "Error in RSTUF job: {'data': None}" in str(e) + + assert fake_session.calls == [pretend.call()] + assert test_session.post.calls == [ + pretend.call( + "http://rstuf/api/v1/artifacts", json={"targets": [{"path": "name"}]} + ), + ] + assert response.raise_for_status.calls == [pretend.call()] + assert response.json.calls == [pretend.call()] + + @pytest.mark.parametrize( + ("states", "exception", "message"), + [ + ( + [ + {"data": {"state": "PENDING"}}, + {"data": {"state": "STARTED"}}, + {"data": {"state": "RECEIVED"}}, + {"data": {"state": "STARTED"}}, + {"data": {"state": "SUCCESS"}}, + ], + None, + "", + ), + ( + [ + {"data": {"state": "PENDING"}}, + {"data": {"state": "STARTED"}}, + {"data": {"state": "RECEIVED"}}, + {"data": {"state": "STARTED"}}, + {"data": {"state": "FAILURE"}}, + ], + services.RSTUFError, + "RSTUF job failed, please check payload and retry", + ), + ( + [ + {"data": {"state": "PENDING"}}, + {"data": {"state": "STARTED"}}, + {"data": {"state": "RECEIVED"}}, + {"data": {"state": "STARTED"}}, + {"data": {"state": "ERRORED"}}, + ], + services.RSTUFError, + "RSTUF internal problem, please check RSTUF health", + ), + ( + [ + {"data": {"state": "PENDING"}}, + {"data": {"state": "STARTED"}}, + {"data": {"state": "RECEIVED"}}, + {"data": {"state": "STARTED"}}, + {"data": {"state": "REVOKED"}}, + ], + services.RSTUFError, + "RSTUF internal problem, please check RSTUF health", + ), + ( + [ + {"data": {"state": "PENDING"}}, + {"data": {"state": "STARTED"}}, + {"data": {"state": "RECEIVED"}}, + {"data": {"state": "STARTED"}}, + {"data": {"state": "REJECTED"}}, + ], + services.RSTUFError, + "RSTUF internal problem, please check RSTUF health", + ), + ( + [ + {"data": {"state": "PENDING"}}, + {"data": {"state": "STARTED"}}, + {"data": {"state": "RECEIVED"}}, + {"data": {"state": "STARTED"}}, + {"data": {"state": "INVALID_STATE"}}, + ], + services.RSTUFError, + "RSTUF job returned unexpected state: INVALID_STATE", + ), + ( + [ + {"data": {"state": "PENDING"}}, + {"data": {"state": "PENDING"}}, + {"data": {"state": "PENDING"}}, + {"data": {"state": "PENDING"}}, + {"data": {"state": "PENDING"}}, + ], + services.RSTUFError, + "RSTUF job failed, please check payload and retry", + ), + ], + ) + def test_wait_for_pending_than_success( + self, monkeypatch, db_request, states, exception, message + ): + db_request.registry.settings = {"rstuf.api_url": "http://rstuf"} + + # generate iter of responses + responses = iter(states) + response = pretend.stub( + raise_for_status=pretend.call_recorder(lambda: None), + json=pretend.call_recorder(lambda: next(responses)), + ) + test_session = pretend.stub( + get=pretend.call_recorder(lambda *a, **kw: response) + ) + fake_session = pretend.call_recorder(lambda: test_session) + monkeypatch.setattr(services, "Session", fake_session) + + rstuf = RSTUFService.create_service(db_request) + rstuf.delay = 0.1 # speed up the test + if message == "RSTUF job failed, please check payload and retry": + rstuf.retries = 5 # simulate failure by limiting retries + + result = None + if exception is not None: + with pytest.raises(exception) as e: + rstuf.wait_for_success("123456") + + assert message in str(e) + else: + result = rstuf.wait_for_success("123456") + + assert result is None + + assert test_session.get.calls == [ + pretend.call("http://rstuf/api/v1/task?task_id=123456"), + pretend.call("http://rstuf/api/v1/task?task_id=123456"), + pretend.call("http://rstuf/api/v1/task?task_id=123456"), + pretend.call("http://rstuf/api/v1/task?task_id=123456"), + pretend.call("http://rstuf/api/v1/task?task_id=123456"), + ] + assert response.raise_for_status.calls == [ + pretend.call(), + pretend.call(), + pretend.call(), + pretend.call(), + pretend.call(), + ] + assert response.json.calls == [ + pretend.call(), + pretend.call(), + pretend.call(), + pretend.call(), + pretend.call(), + ] + + def test_rstuf_factory(self): + rstuf = services.rstuf_factory(pretend.stub(), pretend.stub()) + + assert isinstance(rstuf, RSTUFService) diff --git a/tests/unit/tuf/test_tasks.py b/tests/unit/tuf/test_tasks.py index 40bddef4a8db..730a8319141d 100644 --- a/tests/unit/tuf/test_tasks.py +++ b/tests/unit/tuf/test_tasks.py @@ -35,18 +35,16 @@ def test_update_metadata(self, db_request, monkeypatch): render = call_recorder(lambda *a, **kw: (index_digest, None, index_size)) tuf.tasks.render_simple_detail = render - post = call_recorder(lambda *a: self.task_id) - monkeypatch.setattr(tuf.tasks, "post_artifacts", post) - - wait = call_recorder(lambda *a: None) - monkeypatch.setattr(tuf.tasks, "wait_for_success", wait) + rstuf = tuf.services.RSTUFService.create_service(db_request) + rstuf.post_artifacts = call_recorder(lambda *a: self.task_id) + rstuf.wait_for_success = call_recorder(lambda *a: None) + db_request.find_service = call_recorder(lambda *a, **kw: rstuf) tuf.tasks.update_metadata(db_request, project_id) assert one.calls == [call()] assert render.calls == [call(project, db_request, store=True)] - assert post.calls == [ + assert rstuf.post_artifacts.calls == [ call( - rstuf_url, { "targets": [ { @@ -60,7 +58,7 @@ def test_update_metadata(self, db_request, monkeypatch): }, ) ] - assert wait.calls == [call(rstuf_url, self.task_id)] + assert rstuf.wait_for_success.calls == [call(self.task_id)] def test_update_metadata_no_rstuf_api_url(self, db_request): project_id = "id" @@ -73,6 +71,7 @@ def test_update_metadata_no_rstuf_api_url(self, db_request): # Test early return, if no RSTUF API URL configured db_request.registry.settings = {"rstuf.api_url": None} + tuf.tasks.update_metadata(db_request, project_id) assert not one.calls diff --git a/tests/unit/tuf/test_tuf.py b/tests/unit/tuf/test_tuf.py deleted file mode 100644 index adbcc5862ff2..000000000000 --- a/tests/unit/tuf/test_tuf.py +++ /dev/null @@ -1,121 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -from pretend import call, call_recorder, stub - -from warehouse import tuf - - -class TestTUF: - server = "rstuf.api" - task_id = "123456" - - def test_get_task_state(self, monkeypatch): - state = "SUCCESS" - - resp_json = {"data": {"state": state}} - resp = stub( - raise_for_status=(lambda *a: None), json=(lambda *a, **kw: resp_json) - ) - get = call_recorder(lambda *a: resp) - monkeypatch.setattr(tuf.requests, "get", get) - - result = tuf.get_task_state(self.server, self.task_id) - - assert result == state - assert get.calls == [call(f"{self.server}/api/v1/task?task_id={self.task_id}")] - - def test_post_bootstrap(self, monkeypatch): - payload = ["foo"] - - resp_json = {"data": {"task_id": self.task_id}} - resp = stub( - raise_for_status=(lambda *a: None), json=(lambda *a, **kw: resp_json) - ) - post = call_recorder(lambda *a, **kw: resp) - monkeypatch.setattr(tuf.requests, "post", post) - - # Test success - result = tuf.post_bootstrap(self.server, payload) - - assert result == self.task_id - assert post.calls == [call(f"{self.server}/api/v1/bootstrap", json=payload)] - - # Test fail with incomplete response json - del resp_json["data"] - with pytest.raises(tuf.RSTUFError): - tuf.post_bootstrap(self.server, payload) - - def test_post_artifacts(self, monkeypatch): - payload = { - "targets": [ - { - "path": "foo", - "info": { - "length": 42, - "hashes": {"blake2b-256": "deadbeef"}, - }, - } - ] - } - - resp_json = {"data": {"task_id": self.task_id}} - resp = stub( - raise_for_status=(lambda *a: None), json=(lambda *a, **kw: resp_json) - ) - post = call_recorder(lambda *a, **kw: resp) - monkeypatch.setattr(tuf.requests, "post", post) - - # Test success - result = tuf.post_artifacts(self.server, payload) - - assert result == self.task_id - assert post.calls == [call(f"{self.server}/api/v1/artifacts", json=payload)] - - # Test fail with incomplete response json (i.e. no bootstrap error) - del resp_json["data"] - with pytest.raises(tuf.RSTUFNoBootstrapError): - tuf.post_artifacts(self.server, payload) - - def test_wait_for_success(self, monkeypatch): - get_task_state = call_recorder(lambda *a: "SUCCESS") - monkeypatch.setattr(tuf, "get_task_state", get_task_state) - tuf.wait_for_success(self.server, self.task_id) - - assert get_task_state.calls == [call(self.server, self.task_id)] - - @pytest.mark.parametrize( - ("state", "iterations"), - [ - ("PENDING", 20), - ("RUNNING", 20), - ("RECEIVED", 20), - ("STARTED", 20), - ("FAILURE", 1), - ("ERRORED", 1), - ("REVOKED", 1), - ("REJECTED", 1), - ("bogus", 1), - ], - ) - def test_wait_for_success_error(self, state, iterations, monkeypatch): - monkeypatch.setattr(tuf.time, "sleep", lambda *a: None) - - get_task_state = call_recorder(lambda *a: state) - monkeypatch.setattr(tuf, "get_task_state", get_task_state) - - with pytest.raises(tuf.RSTUFError): - tuf.wait_for_success(self.server, self.task_id) - - assert get_task_state.calls == [call(self.server, self.task_id)] * iterations diff --git a/warehouse/tuf/__init__.py b/warehouse/tuf/__init__.py index 5991c918aa4c..83485cd456a0 100644 --- a/warehouse/tuf/__init__.py +++ b/warehouse/tuf/__init__.py @@ -10,87 +10,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -RSTUF API client library -""" +from warehouse.tuf.interfaces import ITUFService +from warehouse.tuf.services import rstuf_factory -import time -from typing import Any - -import requests - - -class RSTUFError(Exception): - pass - - -class RSTUFNoBootstrapError(Exception): - pass - - -def get_task_state(server: str, task_id: str) -> str: - resp = requests.get(f"{server}/api/v1/task?task_id={task_id}") - resp.raise_for_status() - return resp.json()["data"]["state"] - - -def post_bootstrap(server: str, payload: Any) -> str: - resp = requests.post(f"{server}/api/v1/bootstrap", json=payload) - resp.raise_for_status() - - # TODO: Ask upstream to not return 200 on error - resp_json = resp.json() - resp_data = resp_json.get("data") - if not resp_data: - raise RSTUFError(f"Error in RSTUF job: {resp_json}") - - return resp_data["task_id"] - - -def post_artifacts(server: str, payload: Any) -> str: - """Call RSTUF artifacts API to update the relevant TUF metadata. - - Returns task id of the async update task in RSTUF. - """ - resp = requests.post(f"{server}/api/v1/artifacts", json=payload) - resp.raise_for_status() - - # 200 but no "data" means that RSTUF isn't bootstrapped yet - # TODO: Ask upstream to not return 200 on error - resp_json = resp.json() - resp_data = resp_json.get("data") - if not resp_data: - raise RSTUFNoBootstrapError(resp_json) - - return resp_data["task_id"] - - -def wait_for_success(server: str, task_id: str): - """Poll RSTUF task state API until success or error.""" - - retries = 20 - delay = 1 - - for _ in range(retries): - state = get_task_state(server, task_id) - - match state: - case "SUCCESS": - break - - case "PENDING" | "RUNNING" | "RECEIVED" | "STARTED": - time.sleep(delay) - continue - - case "FAILURE": - raise RSTUFError("RSTUF job failed, please check payload and retry") - - case "ERRORED" | "REVOKED" | "REJECTED": - raise RSTUFError("RSTUF internal problem, please check RSTUF health") - - case _: - raise RSTUFError(f"RSTUF job returned unexpected state: {state}") - - else: - raise RSTUFError("RSTUF job failed, please check payload and retry") +def includeme(config): + config.register_service_factory(rstuf_factory, ITUFService) diff --git a/warehouse/tuf/interfaces.py b/warehouse/tuf/interfaces.py new file mode 100644 index 000000000000..308837c280af --- /dev/null +++ b/warehouse/tuf/interfaces.py @@ -0,0 +1,35 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from zope.interface import Interface + + +class ITUFService(Interface): + def create_service(db_session): + """ + Create appropriate RSTUF service based on environment + """ + + def get_task_state(task_id): + """ + Fetch the RSTUF task state to based on the task id + """ + + def post_artifacts(payload): + """ + Send the Artifacts payload to RSTUF API + """ + + def wait_for_success(task_id): + """ + Wait for the RSTUF task to complete successfully + """ diff --git a/warehouse/tuf/services.py b/warehouse/tuf/services.py new file mode 100644 index 000000000000..469d79e1c07e --- /dev/null +++ b/warehouse/tuf/services.py @@ -0,0 +1,92 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import time + +from requests import Session +from zope.interface import implementer + +from warehouse.tuf.interfaces import ITUFService + + +class RSTUFError(Exception): + pass + + +class RSTUFNoBootstrapError(Exception): + pass + + +@implementer(ITUFService) +class RSTUFService: + def __init__(self, api_url, retries=20, delay=1): + self.requests = Session() + self.api_url = api_url + # TODO make retries and delay configurable + self.retries = retries + self.delay = delay + + @classmethod + def create_service(cls, db_session): + return cls(db_session.registry.settings["rstuf.api_url"]) + + def get_task_state(self, task_id): + """Get the RSTUF task state based on the task id.""" + response = self.requests.get(f"{self.api_url}/api/v1/task?task_id={task_id}") + response.raise_for_status() + return response.json()["data"]["state"] + + def post_artifacts(self, payload): + """Call RSTUF artifacts API to update the relevant TUF metadata. + + Returns task id of the async update task in RSTUF. + """ + response = self.requests.post(f"{self.api_url}/api/v1/artifacts", json=payload) + response.raise_for_status() + + response_json = response.json() + response_data = response_json.get("data") + if not response_data: + raise RSTUFError(f"Error in RSTUF job: {response_json}") + + return response_data["task_id"] + + def wait_for_success(self, task_id): + """Poll RSTUF task state API until success or error.""" + + for _ in range(self.retries): + state = self.get_task_state(task_id) + + match state: + case "SUCCESS": + break + + case "PENDING" | "RUNNING" | "RECEIVED" | "STARTED": + time.sleep(self.delay) + continue + + case "FAILURE": + raise RSTUFError("RSTUF job failed, please check payload and retry") + + case "ERRORED" | "REVOKED" | "REJECTED": + raise RSTUFError( + "RSTUF internal problem, please check RSTUF health" + ) + + case _: + raise RSTUFError(f"RSTUF job returned unexpected state: {state}") + + else: + raise RSTUFError("RSTUF job failed, please check payload and retry") + + +def rstuf_factory(context, request): + return RSTUFService(request) diff --git a/warehouse/tuf/tasks.py b/warehouse/tuf/tasks.py index 5e12b304b6fc..c1c8a69a17c5 100644 --- a/warehouse/tuf/tasks.py +++ b/warehouse/tuf/tasks.py @@ -16,7 +16,7 @@ from warehouse import tasks from warehouse.packaging.models import Project from warehouse.packaging.utils import render_simple_detail -from warehouse.tuf import post_artifacts, wait_for_success +from warehouse.tuf.interfaces import ITUFService @tasks.task(ignore_result=True, acks_late=True) @@ -27,10 +27,11 @@ def update_metadata(request: Request, project_id: UUID): distributions files and simple detail files. In reality, simple detail files are enough, as they already include all relevant distribution file infos. """ - server = request.registry.settings["rstuf.api_url"] - if not server: + if request.registry.settings.get("rstuf.api_url") is None: return + rstuf_service = request.find_service(ITUFService, context=None) + project = request.db.query(Project).filter(Project.id == project_id).one() # NOTE: We ignore the returned simple detail path with the content hash as @@ -50,5 +51,5 @@ def update_metadata(request: Request, project_id: UUID): } # TODO: Handle errors: pass, retry or notify - task_id = post_artifacts(server, payload) - wait_for_success(server, task_id) + task_id = rstuf_service.post_artifacts(payload) + rstuf_service.wait_for_success(task_id)