Skip to content

Commit f12b5f6

Browse files
committed
Replace conflicting repository-service-tuf dep
Previously, `repository-service-tuf` (i.e. the RSTUF cli) was used to bootstrap an RSTUF repo for development. This PR re-implements the relevant parts of the cli locally in Warehouse and removes the `repository-service-tuf` dependency, which conflicts with other dependencies. Change details - Add lightweight RSTUF API client library (can be re-used for pypi#15815) - Add local `warehouse tuf bootstrap` cli subcommand, to wraps lib calls - Invoke local cli via `make inittuf` - Remove dependency supersedes pypi#15958 (cc @facutuesca @woodruffw) Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
1 parent 7eb6030 commit f12b5f6

File tree

4 files changed

+108
-2
lines changed

4 files changed

+108
-2
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ initdb: .state/docker-build-base .state/db-populated
128128
inittuf: .state/db-migrated
129129
docker compose up -d rstuf-api
130130
docker compose up -d rstuf-worker
131-
docker compose run --rm web rstuf admin ceremony -b -u -f dev/rstuf/bootstrap.json --api-server http://rstuf-api
131+
docker compose run --rm web python -m warehouse tuf bootstrap dev/rstuf/bootstrap.json --api-server http://rstuf-api
132132

133133
runmigrations: .state/docker-build-base
134134
docker compose run --rm web python -m warehouse db upgrade head

requirements/dev.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@ hupper>=1.9
33
pip-tools>=1.0
44
pyramid_debugtoolbar>=2.5
55
pip-api
6-
repository-service-tuf

warehouse/cli/tuf.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
import json
14+
15+
import click
16+
17+
from warehouse.cli import warehouse
18+
from warehouse.tuf import post_bootstrap, wait_for_success
19+
20+
21+
@warehouse.group()
22+
def tuf():
23+
"""Manage TUF."""
24+
25+
26+
@tuf.command()
27+
@click.argument("payload", type=click.File("rb"), required=True)
28+
@click.option("--api-server", required=True)
29+
def bootstrap(payload, api_server):
30+
"""Use payload file to bootstrap RSTUF server."""
31+
task_id = post_bootstrap(api_server, json.load(payload))
32+
wait_for_success(api_server, task_id)
33+
print(f"Bootstrap completed using `{payload.name}`. 🔐 🎉")

warehouse/tuf/__init__.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
"""
14+
RSTUF API client library
15+
"""
16+
17+
import time
18+
19+
from typing import Any
20+
21+
import requests
22+
23+
24+
class RSTUFError(Exception):
25+
pass
26+
27+
28+
def get_task_state(server: str, task_id: str) -> str:
29+
resp = requests.get(f"{server}/api/v1/task?task_id={task_id}")
30+
resp.raise_for_status()
31+
return resp.json()["data"]["state"]
32+
33+
34+
def post_bootstrap(server: str, payload: Any) -> str:
35+
resp = requests.post(f"{server}/api/v1/bootstrap", json=payload)
36+
resp.raise_for_status()
37+
38+
# TODO: Ask upstream to not return 200 on error
39+
resp_json = resp.json()
40+
resp_data = resp_json.get("data")
41+
if not resp_data:
42+
raise RSTUFError(f"Error in RSTUF job: {resp_json}")
43+
44+
return resp_data["task_id"]
45+
46+
47+
def wait_for_success(server: str, task_id: str):
48+
"""Poll RSTUF task state API until success or error."""
49+
50+
retries = 20
51+
delay = 1
52+
53+
for _ in range(retries):
54+
state = get_task_state(server, task_id)
55+
56+
match state:
57+
case "SUCCESS":
58+
break
59+
60+
case "PENDING" | "RUNNING" | "RECEIVED" | "STARTED":
61+
time.sleep(delay)
62+
continue
63+
64+
case "FAILURE":
65+
raise RSTUFError("RSTUF job failed, please check payload and retry")
66+
67+
case "ERRORED" | "REVOKED" | "REJECTED":
68+
raise RSTUFError("RSTUF internal problem, please check RSTUF health")
69+
70+
case _:
71+
raise RSTUFError(f"RSTUF job returned unexpected state: {state}")
72+
73+
else:
74+
raise RSTUFError("RSTUF job failed, please check payload and retry")

0 commit comments

Comments
 (0)