Skip to content

Commit cb550d2

Browse files
authored
Merge pull request #2670 from jku/add-conformance-workflow
Add a conformance test workflow
2 parents 3947033 + b14452d commit cb550d2

File tree

4 files changed

+145
-1
lines changed

4 files changed

+145
-1
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/env python
2+
"""Conformance client for python-tuf, part of tuf-conformance"""
3+
4+
# Copyright 2024 tuf-conformance contributors
5+
# SPDX-License-Identifier: MIT OR Apache-2.0
6+
7+
import argparse
8+
import os
9+
import shutil
10+
import sys
11+
from datetime import datetime, timedelta, timezone
12+
13+
from tuf.ngclient import Updater, UpdaterConfig
14+
15+
16+
def init(metadata_dir: str, trusted_root: str) -> None:
17+
"""Initialize local trusted metadata"""
18+
19+
# No need to actually run python-tuf code at this point
20+
shutil.copyfile(trusted_root, os.path.join(metadata_dir, "root.json"))
21+
print(f"python-tuf test client: Initialized repository in {metadata_dir}")
22+
23+
24+
def refresh(
25+
metadata_url: str,
26+
metadata_dir: str,
27+
days_in_future: str,
28+
max_root_rotations: int,
29+
) -> None:
30+
"""Refresh local metadata from remote"""
31+
32+
updater = Updater(
33+
metadata_dir,
34+
metadata_url,
35+
config=UpdaterConfig(max_root_rotations=int(max_root_rotations)),
36+
)
37+
if days_in_future != "0":
38+
day_int = int(days_in_future)
39+
day_in_future = datetime.now(timezone.utc) + timedelta(days=day_int)
40+
updater._trusted_set.reference_time = day_in_future # noqa: SLF001
41+
updater.refresh()
42+
print(f"python-tuf test client: Refreshed metadata in {metadata_dir}")
43+
44+
45+
def download_target(
46+
metadata_url: str,
47+
metadata_dir: str,
48+
target_name: str,
49+
download_dir: str,
50+
target_base_url: str,
51+
) -> None:
52+
"""Download target."""
53+
54+
updater = Updater(
55+
metadata_dir,
56+
metadata_url,
57+
download_dir,
58+
target_base_url,
59+
config=UpdaterConfig(prefix_targets_with_hash=False),
60+
)
61+
target_info = updater.get_targetinfo(target_name)
62+
if not target_info:
63+
raise RuntimeError(f"{target_name} not found in repository")
64+
updater.download_target(target_info)
65+
66+
67+
def main() -> int:
68+
"""Main TUF Client Example function"""
69+
70+
parser = argparse.ArgumentParser(description="TUF Client Example")
71+
parser.add_argument("--metadata-url", required=False)
72+
parser.add_argument("--metadata-dir", required=True)
73+
parser.add_argument("--target-name", required=False)
74+
parser.add_argument("--target-dir", required=False)
75+
parser.add_argument("--target-base-url", required=False)
76+
parser.add_argument("--days-in-future", required=False, default="0")
77+
parser.add_argument(
78+
"--max-root-rotations", required=False, default=32, type=int
79+
)
80+
81+
sub_command = parser.add_subparsers(dest="sub_command")
82+
init_parser = sub_command.add_parser(
83+
"init",
84+
help="Initialize client with given trusted root",
85+
)
86+
init_parser.add_argument("trusted_root")
87+
88+
sub_command.add_parser(
89+
"refresh",
90+
help="Refresh the client metadata",
91+
)
92+
93+
sub_command.add_parser(
94+
"download",
95+
help="Downloads a target",
96+
)
97+
98+
command_args = parser.parse_args()
99+
100+
# initialize the TUF Client Example infrastructure
101+
if command_args.sub_command == "init":
102+
init(command_args.metadata_dir, command_args.trusted_root)
103+
elif command_args.sub_command == "refresh":
104+
refresh(
105+
command_args.metadata_url,
106+
command_args.metadata_dir,
107+
command_args.days_in_future,
108+
command_args.max_root_rotations,
109+
)
110+
elif command_args.sub_command == "download":
111+
download_target(
112+
command_args.metadata_url,
113+
command_args.metadata_dir,
114+
command_args.target_name,
115+
command_args.target_dir,
116+
command_args.target_base_url,
117+
)
118+
else:
119+
parser.print_help()
120+
121+
return 0
122+
123+
124+
if __name__ == "__main__":
125+
sys.exit(main())

.github/workflows/conformance.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
on:
2+
# manual dispatch only while the conformance test suite is under rapid development
3+
workflow_dispatch:
4+
5+
name: CI
6+
jobs:
7+
conformance:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Checkout conformance client
11+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
12+
13+
- name: Run test suite
14+
uses: theupdateframework/tuf-conformance@main
15+
with:
16+
entrypoint: ".github/scripts/conformance-client.py"

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ ignore = [
124124
"S603", # bandit: this flags all uses of subprocess.run as vulnerable
125125
"T201", # print is ok in verify_release
126126
]
127+
".github/scripts/*" = [
128+
"T201", # print is ok in conformance client
129+
]
127130

128131
[tool.ruff.lint.flake8-annotations]
129132
mypy-init-return = true

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ changedir = {toxinidir}
4545
deps =
4646
-r{toxinidir}/requirements/lint.txt
4747
--editable {toxinidir}
48-
lint_dirs = tuf examples tests verify_release
48+
lint_dirs = tuf examples tests verify_release .github/scripts
4949
passenv = RUFF_OUTPUT_FORMAT
5050
commands =
5151
ruff check {[testenv:lint]lint_dirs}

0 commit comments

Comments
 (0)