Skip to content

Commit ca89128

Browse files
committed
feat: add scriptworker signing transforms
1 parent 46fdc27 commit ca89128

File tree

5 files changed

+386
-0
lines changed

5 files changed

+386
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
import re
6+
7+
from taskgraph.transforms.base import TransformSequence
8+
from taskgraph.util.schema import Schema, optionally_keyed_by, resolve_keyed_by
9+
from voluptuous import ALLOW_EXTRA, Any, Optional, Required
10+
11+
SIGNING_FORMATS = ["autograph_gpg"]
12+
SIGNING_TYPES = ["dep", "release"]
13+
DETACHED_SIGNATURE_EXTENSION = ".asc"
14+
15+
signing_schema = Schema(
16+
{
17+
Required("attributes"): {
18+
Required("artifacts"): dict,
19+
Required("build-type"): str,
20+
},
21+
Required("signing"): optionally_keyed_by(
22+
"build-type",
23+
"level",
24+
{
25+
Required("format"): optionally_keyed_by(
26+
"build-type", "level", Any(*SIGNING_FORMATS)
27+
),
28+
Optional("type"): optionally_keyed_by(
29+
"build-type", "level", Any(*SIGNING_TYPES)
30+
),
31+
Optional("ignore-artifacts"): list,
32+
},
33+
),
34+
Required("worker"): {
35+
Required("upstream-artifacts"): list,
36+
},
37+
},
38+
extra=ALLOW_EXTRA,
39+
)
40+
41+
transforms = TransformSequence()
42+
transforms.add_validate(signing_schema)
43+
44+
45+
@transforms.add
46+
def resolve_signing_keys(config, tasks):
47+
for task in tasks:
48+
for key in (
49+
"signing",
50+
"signing.format",
51+
"signing.type",
52+
):
53+
resolve_keyed_by(
54+
task,
55+
key,
56+
item_name=task["name"],
57+
**{
58+
"build-type": task["attributes"]["build-type"],
59+
"level": config.params["level"],
60+
},
61+
)
62+
yield task
63+
64+
65+
@transforms.add
66+
def set_signing_attributes(_, tasks):
67+
for task in tasks:
68+
task["attributes"]["signed"] = True
69+
yield task
70+
71+
72+
@transforms.add
73+
def set_signing_format(_, tasks):
74+
for task in tasks:
75+
for upstream_artifact in task["worker"]["upstream-artifacts"]:
76+
upstream_artifact["formats"] = [task["signing"]["format"]]
77+
yield task
78+
79+
80+
@transforms.add
81+
def set_signing_and_worker_type(config, tasks):
82+
for task in tasks:
83+
signing_type = task["signing"].get("type")
84+
if not signing_type:
85+
signing_type = "release" if config.params["level"] == "3" else "dep"
86+
87+
task.setdefault("worker", {})["signing-type"] = f"{signing_type}-signing"
88+
89+
if "worker-type" not in task:
90+
worker_type = "signing"
91+
build_type = task["attributes"]["build-type"]
92+
93+
if signing_type == "dep":
94+
worker_type = f"dep-{worker_type}"
95+
if build_type == "macos":
96+
worker_type = f"{build_type}-{worker_type}"
97+
task["worker-type"] = worker_type
98+
99+
yield task
100+
101+
102+
@transforms.add
103+
def filter_out_ignored_artifacts(_, tasks):
104+
for task in tasks:
105+
ignore = task["signing"].get("ignore-artifacts")
106+
if not ignore:
107+
yield task
108+
continue
109+
110+
def is_ignored(artifact):
111+
return not any(re.search(i, artifact) for i in ignore)
112+
113+
task["attributes"]["artifacts"] = {
114+
extension: path
115+
for extension, path in task["attributes"]["artifacts"].items()
116+
if is_ignored(path)
117+
}
118+
119+
for upstream_artifact in task["worker"]["upstream-artifacts"]:
120+
upstream_artifact["paths"] = [
121+
path for path in upstream_artifact["paths"] if is_ignored(path)
122+
]
123+
124+
yield task
125+
126+
127+
@transforms.add
128+
def set_gpg_detached_signature_artifacts(_, tasks):
129+
for task in tasks:
130+
if task["signing"]["format"] != "autograph_gpg":
131+
yield task
132+
continue
133+
134+
task["attributes"]["artifacts"] = {
135+
extension
136+
+ DETACHED_SIGNATURE_EXTENSION: path
137+
+ DETACHED_SIGNATURE_EXTENSION
138+
for extension, path in task["attributes"]["artifacts"].items()
139+
}
140+
141+
yield task
142+
143+
144+
@transforms.add
145+
def remove_signing_config(_, tasks):
146+
for task in tasks:
147+
del task["signing"]
148+
yield task
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
base_ref: refs/heads/main
3+
base_repository: https://github.com/mozilla-releng/mozilla-taskgraph
4+
base_rev: a76ea4308313211a99e8e501c5a97a5ce2c08cc1
5+
build_date: 1681151087
6+
build_number: 1
7+
do_not_optimize: []
8+
enable_always_target: true
9+
existing_tasks: {}
10+
filters:
11+
- target_tasks_method
12+
head_ref: refs/heads/main
13+
head_repository: https://github.com/mozilla-releng/mozilla-taskgraph
14+
head_rev: a0785edae4a841b6119925280c744000f59b903e
15+
head_tag: ''
16+
level: '1'
17+
moz_build_date: '20230410182447'
18+
next_version: null
19+
optimize_strategies: null
20+
optimize_target_tasks: true
21+
22+
project: mozilla-taskgraph
23+
pushdate: 0
24+
pushlog_id: '0'
25+
repository_type: git
26+
target_tasks_method: default
27+
tasks_for: github-push
28+
version: null
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
base_ref: main
3+
base_repository: https://github.com/mozilla-releng/mozilla-taskgraph
4+
base_rev: a0785edae4a841b6119925280c744000f59b903e
5+
build_date: 1681154438
6+
build_number: 1
7+
do_not_optimize: []
8+
enable_always_target: true
9+
existing_tasks: {}
10+
filters:
11+
- target_tasks_method
12+
head_ref: codecov
13+
head_repository: https://github.com/user/mozilla-taskgraph
14+
head_rev: 06c766e8e9d558eed5ccf8029164120a27af5fb1
15+
head_tag: ''
16+
level: '1'
17+
moz_build_date: '20230410192038'
18+
next_version: null
19+
optimize_strategies: null
20+
optimize_target_tasks: true
21+
22+
project: mozilla-taskgraph
23+
pushdate: 0
24+
pushlog_id: '0'
25+
repository_type: git
26+
target_tasks_method: default
27+
tasks_for: github-pull-request
28+
version: null

test/conftest.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
from taskgraph.config import GraphConfig
5+
from taskgraph.transforms.base import TransformConfig
6+
7+
here = Path(__file__).parent
8+
9+
10+
@pytest.fixture(scope="session")
11+
def datadir():
12+
return here / "data"
13+
14+
15+
def fake_load_graph_config(root_dir):
16+
graph_config = GraphConfig(
17+
{
18+
"trust-domain": "test-domain",
19+
"taskgraph": {
20+
"repositories": {
21+
"ci": {"name": "Taskgraph"},
22+
}
23+
},
24+
"workers": {
25+
"aliases": {
26+
"b-linux": {
27+
"provisioner": "taskgraph-b",
28+
"implementation": "docker-worker",
29+
"os": "linux",
30+
"worker-type": "linux",
31+
},
32+
"t-linux": {
33+
"provisioner": "taskgraph-t",
34+
"implementation": "docker-worker",
35+
"os": "linux",
36+
"worker-type": "linux",
37+
},
38+
}
39+
},
40+
"task-priority": "low",
41+
"treeherder": {"group-names": {"T": "tests"}},
42+
},
43+
root_dir,
44+
)
45+
graph_config.__dict__["register"] = lambda: None
46+
return graph_config
47+
48+
49+
@pytest.fixture
50+
def graph_config(datadir):
51+
return fake_load_graph_config(str(datadir / "taskcluster" / "ci"))
52+
53+
54+
class FakeParameters(dict):
55+
strict = True
56+
57+
def is_try(self):
58+
return False
59+
60+
def file_url(self, path, pretty=False):
61+
return path
62+
63+
64+
@pytest.fixture
65+
def parameters():
66+
return FakeParameters(
67+
{
68+
"base_repository": "http://hg.example.com",
69+
"build_date": 0,
70+
"build_number": 1,
71+
"enable_always_target": True,
72+
"head_repository": "http://hg.example.com",
73+
"head_rev": "abcdef",
74+
"head_ref": "default",
75+
"level": "1",
76+
"moz_build_date": 0,
77+
"next_version": "1.0.1",
78+
"owner": "some-owner",
79+
"project": "some-project",
80+
"pushlog_id": 1,
81+
"repository_type": "hg",
82+
"target_tasks_method": "test_method",
83+
"tasks_for": "hg-push",
84+
"try_mode": None,
85+
"version": "1.0.0",
86+
}
87+
)
88+
89+
90+
@pytest.fixture
91+
def make_transform_config(parameters, graph_config):
92+
def inner(kind_config=None, kind_dependencies_tasks=None):
93+
kind_config = kind_config or {}
94+
kind_dependencies_tasks = kind_dependencies_tasks or {}
95+
return TransformConfig(
96+
"test",
97+
str(here),
98+
kind_config,
99+
parameters,
100+
kind_dependencies_tasks,
101+
graph_config,
102+
write_artifacts=False,
103+
)
104+
105+
return inner
106+
107+
108+
@pytest.fixture
109+
def run_transform(make_transform_config):
110+
def inner(func, tasks, config=None):
111+
if not isinstance(tasks, list):
112+
tasks = [tasks]
113+
114+
if not config:
115+
config = make_transform_config()
116+
return list(func(config, tasks))
117+
118+
return inner

test/test_transforms_signing.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import pytest
2+
from taskgraph.util.templates import merge
3+
4+
from mozilla_taskgraph.transforms.scriptworker.signing import (
5+
transforms as signing_transforms,
6+
)
7+
8+
9+
@pytest.mark.parametrize(
10+
"params,task,expected",
11+
(
12+
pytest.param(
13+
{"level": "3"},
14+
{
15+
"signing": {
16+
"format": "autograph_gpg",
17+
},
18+
},
19+
{
20+
"worker-type": "signing",
21+
"worker": {
22+
"signing-type": "release-signing",
23+
},
24+
},
25+
id="level-3",
26+
),
27+
pytest.param(
28+
{"level": "1"},
29+
{
30+
"signing": {
31+
"format": "autograph_gpg",
32+
},
33+
},
34+
{},
35+
id="level-1",
36+
),
37+
),
38+
)
39+
def test_signing_transforms(
40+
make_transform_config, run_transform, params, task, expected
41+
):
42+
task.setdefault("name", "task")
43+
task.setdefault("worker", {}).setdefault("upstream-artifacts", [])
44+
attributes = task.setdefault("attributes", {})
45+
attributes.setdefault("artifacts", {})
46+
attributes.setdefault("build-type", "linux")
47+
48+
config = make_transform_config()
49+
config.params.update(params)
50+
51+
tasks = run_transform(signing_transforms, task, config)
52+
53+
assert len(tasks) == 1
54+
task = tasks[0]
55+
56+
default_expected = {
57+
"attributes": {"artifacts": {}, "build-type": "linux", "signed": True},
58+
"name": "task",
59+
"worker": {"signing-type": "dep-signing", "upstream-artifacts": []},
60+
"worker-type": "dep-signing",
61+
}
62+
63+
expected = merge(default_expected, expected)
64+
assert task == expected

0 commit comments

Comments
 (0)