Skip to content

Commit d2dee6a

Browse files
fix: removed hardcoding from docker-image dir and kinds (#728)
* fix: removed hardcoding from docker-image dir and kinds * fix: updated to review and added tests * fix: updated docker tests * fix: updated default case and add kinds dir to graph config
1 parent 4f589d8 commit d2dee6a

File tree

8 files changed

+191
-18
lines changed

8 files changed

+191
-18
lines changed

src/taskgraph/config.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
# The trust-domain for this graph.
2727
# (See https://firefox-source-docs.mozilla.org/taskcluster/taskcluster/taskgraph.html#taskgraph-trust-domain) # noqa
2828
Required("trust-domain"): str,
29+
Optional(
30+
"docker-image-kind",
31+
description="Name of the docker image kind (default: docker-image)",
32+
): str,
2933
Required("task-priority"): optionally_keyed_by(
3034
"project",
3135
"level",
@@ -157,6 +161,14 @@ def vcs_root(self):
157161
def taskcluster_yml(self):
158162
return os.path.join(self.vcs_root, ".taskcluster.yml")
159163

164+
@property
165+
def docker_dir(self):
166+
return os.path.join(self.root_dir, "docker")
167+
168+
@property
169+
def kinds_dir(self):
170+
return os.path.join(self.root_dir, "kinds")
171+
160172

161173
def validate_graph_config(config):
162174
validate_schema(graph_config_schema, config, "Invalid graph configuration:")

src/taskgraph/docker.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,29 +97,29 @@ def load_image_by_task_id(task_id, tag=None):
9797
return tag
9898

9999

100-
def build_context(name, outputFile, args=None):
100+
def build_context(name, outputFile, graph_config, args=None):
101101
"""Build a context.tar for image with specified name."""
102102
if not name:
103103
raise ValueError("must provide a Docker image name")
104104
if not outputFile:
105105
raise ValueError("must provide a outputFile")
106106

107-
image_dir = docker.image_path(name)
107+
image_dir = docker.image_path(name, graph_config)
108108
if not os.path.isdir(image_dir):
109109
raise Exception(f"image directory does not exist: {image_dir}")
110110

111111
docker.create_context_tar(".", image_dir, outputFile, args)
112112

113113

114-
def build_image(name, tag, args=None):
114+
def build_image(name, tag, graph_config, args=None):
115115
"""Build a Docker image of specified name.
116116
117117
Output from image building process will be printed to stdout.
118118
"""
119119
if not name:
120120
raise ValueError("must provide a Docker image name")
121121

122-
image_dir = docker.image_path(name)
122+
image_dir = docker.image_path(name, graph_config)
123123
if not os.path.isdir(image_dir):
124124
raise Exception(f"image directory does not exist: {image_dir}")
125125

src/taskgraph/main.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,12 @@ def show_taskgraph(options):
565565

566566
@command("build-image", help="Build a Docker image")
567567
@argument("image_name", help="Name of the image to build")
568+
@argument(
569+
"--root",
570+
"-r",
571+
default="taskcluster",
572+
help="relative path for the root of the taskgraph definition",
573+
)
568574
@argument(
569575
"-t", "--tag", help="tag that the image should be built as.", metavar="name:tag"
570576
)
@@ -575,13 +581,20 @@ def show_taskgraph(options):
575581
metavar="context.tar",
576582
)
577583
def build_image(args):
584+
from taskgraph.config import load_graph_config # noqa: PLC0415
578585
from taskgraph.docker import build_context, build_image # noqa: PLC0415
579586

580587
validate_docker()
588+
589+
root = args["root"]
590+
graph_config = load_graph_config(root)
591+
581592
if args["context_only"] is None:
582-
build_image(args["image_name"], args["tag"], os.environ)
593+
build_image(args["image_name"], args["tag"], os.environ, graph_config)
583594
else:
584-
build_context(args["image_name"], args["context_only"], os.environ)
595+
build_context(
596+
args["image_name"], args["context_only"], os.environ, graph_config
597+
)
585598

586599

587600
@command(

src/taskgraph/transforms/task.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ def build_docker_worker_payload(config, task, task_def):
575575
}
576576

577577
# Find VOLUME in Dockerfile.
578-
volumes = dockerutil.parse_volumes(name)
578+
volumes = dockerutil.parse_volumes(name, config.graph_config)
579579
for v in sorted(volumes):
580580
if v in worker["volumes"]:
581581
raise Exception(

src/taskgraph/util/docker.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -206,28 +206,34 @@ def stream_context_tar(topsrcdir, context_dir, out_file, args=None):
206206

207207

208208
@functools.lru_cache(maxsize=None)
209-
def image_paths():
209+
def image_paths(graph_config):
210210
"""Return a map of image name to paths containing their Dockerfile."""
211-
config = load_yaml("taskcluster", "kinds", "docker-image", "kind.yml")
211+
212+
config = load_yaml(
213+
graph_config.kinds_dir,
214+
graph_config.get("docker-image-kind", "docker-image"),
215+
"kind.yml",
216+
)
217+
212218
return {
213-
k: os.path.join(IMAGE_DIR, v.get("definition", k))
219+
k: os.path.join(graph_config.docker_dir, v.get("definition", k))
214220
for k, v in config["tasks"].items()
215221
}
216222

217223

218-
def image_path(name):
219-
paths = image_paths()
224+
def image_path(name, graph_config):
225+
paths = image_paths(graph_config)
220226
if name in paths:
221227
return paths[name]
222-
return os.path.join(IMAGE_DIR, name)
228+
return os.path.join(graph_config.docker_dir, name)
223229

224230

225231
@functools.lru_cache(maxsize=None)
226-
def parse_volumes(image):
232+
def parse_volumes(image, graph_config):
227233
"""Parse VOLUME entries from a Dockerfile for an image."""
228234
volumes = set()
229235

230-
path = image_path(image)
236+
path = image_path(image, graph_config)
231237

232238
with open(os.path.join(path, "Dockerfile"), "rb") as fh:
233239
for line in fh:
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
meta:
6+
- &uv_version 0.6.1
7+
8+
loader: taskgraph.loader.transform:loader
9+
10+
transforms:
11+
- taskgraph.transforms.docker_image:transforms
12+
- taskgraph.transforms.cached_tasks:transforms
13+
- taskgraph.transforms.task:transforms
14+
15+
# make a task for each docker-image we might want. For the moment, since we
16+
# write artifacts for each, these are whitelisted, but ideally that will change
17+
# (to use subdirectory clones of the proper directory), at which point we can
18+
# generate tasks for every docker image in the directory, secure in the
19+
# knowledge that unnecessary images will be omitted from the target task graph
20+
tasks:
21+
decision:
22+
symbol: I(d)
23+
parent: run-task
24+
fetch:
25+
symbol: I(fetch)
26+
index-task:
27+
symbol: I(idx)
28+
python:
29+
symbol: I(py)
30+
args:
31+
PYTHON_VERSIONS: "3.13 3.12 3.11 3.10 3.9 3.8"
32+
UV_VERSION: *uv_version
33+
run-task:
34+
symbol: I(rt)
35+
args:
36+
UV_VERSION: *uv_version
37+
skopeo:
38+
symbol: I(skopeo)

test/test_docker.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55

66
from taskgraph import docker
7+
from taskgraph.config import GraphConfig
78

89

910
@pytest.fixture(autouse=True, scope="module")
@@ -30,7 +31,15 @@ def test_build_image(capsys, mock_docker_build):
3031
image = "hello-world-tag"
3132
tag = f"test/{image}:1.0"
3233

33-
assert docker.build_image(image, None) is None
34+
graph_config = GraphConfig(
35+
{
36+
"trust-domain": "test-domain",
37+
"docker-image-kind": "docker-image",
38+
},
39+
"test/data/taskcluster",
40+
)
41+
42+
assert docker.build_image(image, None, graph_config=graph_config) is None
3443
m_stream.assert_called_once()
3544
m_run.assert_called_once_with(
3645
["docker", "image", "build", "--no-cache", f"-t={tag}", "-"],
@@ -47,7 +56,15 @@ def test_build_image_no_tag(capsys, mock_docker_build):
4756
m_stream, m_run = mock_docker_build
4857
image = "hello-world"
4958

50-
assert docker.build_image(image, None) is None
59+
graph_config = GraphConfig(
60+
{
61+
"trust-domain": "test-domain",
62+
"docker-image-kind": "docker-image",
63+
},
64+
"test/data/taskcluster",
65+
)
66+
67+
assert docker.build_image(image, None, graph_config=graph_config) is None
5168
m_stream.assert_called_once()
5269
m_run.assert_called_once_with(
5370
["docker", "image", "build", "--no-cache", "-"],
@@ -71,8 +88,16 @@ def mock_run(*popenargs, check=False, **kwargs):
7188
m_run.side_effect = mock_run
7289
image = "hello-world"
7390

91+
graph_config = GraphConfig(
92+
{
93+
"trust-domain": "test-domain",
94+
"docker-image-kind": "docker-image",
95+
},
96+
"test/data/taskcluster",
97+
)
98+
7499
with pytest.raises(Exception):
75-
docker.build_image(image, None)
100+
docker.build_image(image, None, graph_config=graph_config)
76101
m_stream.assert_called_once()
77102
m_run.assert_called_once_with(
78103
["docker", "image", "build", "--no-cache", "-"],

test/test_util_docker.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import taskcluster_urls as liburls
1616

17+
from taskgraph.config import GraphConfig
1718
from taskgraph.util import docker
1819

1920
from .mockedopen import MockedOpen
@@ -269,3 +270,81 @@ def test_stream_context_tar(self):
269270
)
270271
finally:
271272
shutil.rmtree(tmp)
273+
274+
def test_image_paths_with_custom_kind(self):
275+
"""Test image_paths function with graph_config parameter."""
276+
temp_dir = tempfile.mkdtemp()
277+
try:
278+
# Create the kinds directory structure
279+
kinds_dir = os.path.join(temp_dir, "kinds", "docker-test-image")
280+
os.makedirs(kinds_dir)
281+
282+
# Create the kind.yml file with task definitions
283+
kind_yml_path = os.path.join(kinds_dir, "kind.yml")
284+
with open(kind_yml_path, "w") as f:
285+
f.write("tasks:\n")
286+
f.write(" test-image:\n")
287+
f.write(" definition: test-image\n")
288+
f.write(" another-image:\n")
289+
f.write(" definition: custom-path\n")
290+
291+
# Create graph config pointing to our test directory
292+
temp_graph_config = GraphConfig(
293+
{
294+
"trust-domain": "test-domain",
295+
"docker-image-kind": "docker-test-image",
296+
},
297+
temp_dir,
298+
)
299+
300+
paths = docker.image_paths(temp_graph_config)
301+
302+
expected_docker_dir = os.path.join(temp_graph_config.root_dir, "docker")
303+
self.assertEqual(
304+
paths["test-image"], os.path.join(expected_docker_dir, "test-image")
305+
)
306+
self.assertEqual(
307+
paths["another-image"], os.path.join(expected_docker_dir, "custom-path")
308+
)
309+
finally:
310+
shutil.rmtree(temp_dir)
311+
312+
def test_parse_volumes_with_graph_config(self):
313+
"""Test parse_volumes function with graph_config parameter."""
314+
temp_dir = tempfile.mkdtemp()
315+
try:
316+
kinds_dir = os.path.join(temp_dir, "kinds", "docker-test-image")
317+
os.makedirs(kinds_dir)
318+
319+
kind_yml_path = os.path.join(kinds_dir, "kind.yml")
320+
with open(kind_yml_path, "w") as f:
321+
f.write("tasks:\n")
322+
f.write(" test-image:\n")
323+
f.write(" definition: test-image\n")
324+
325+
docker_dir = os.path.join(temp_dir, "docker")
326+
os.makedirs(docker_dir)
327+
328+
image_dir = os.path.join(docker_dir, "test-image")
329+
os.makedirs(image_dir)
330+
331+
dockerfile_path = os.path.join(image_dir, "Dockerfile")
332+
with open(dockerfile_path, "wb") as fh:
333+
fh.write(b"VOLUME /foo/bar \n")
334+
fh.write(b"VOLUME /hello /world \n")
335+
336+
test_graph_config = GraphConfig(
337+
{
338+
"trust-domain": "test-domain",
339+
"docker-image-kind": "docker-test-image",
340+
},
341+
temp_dir,
342+
)
343+
344+
volumes = docker.parse_volumes("test-image", test_graph_config)
345+
346+
expected_volumes = {"/foo/bar", "/hello", "/world"}
347+
self.assertEqual(volumes, expected_volumes)
348+
349+
finally:
350+
shutil.rmtree(temp_dir)

0 commit comments

Comments
 (0)