Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions src/taskgraph/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@
from taskgraph.util import docker
from taskgraph.util.taskcluster import get_artifact_url, get_session

DEPLOY_WARNING = """
*****************************************************************
WARNING: Image is not suitable for deploying/pushing.

To automatically tag the image the following files are required:
- {image_dir}/REGISTRY
- {image_dir}/VERSION

The REGISTRY file contains the Docker registry hosting the image.
A default REGISTRY file may also be defined in the parent docker
directory.

The VERSION file contains the version of the image.
*****************************************************************
"""


def get_image_digest(image_name):
from taskgraph.generator import load_tasks_for_kind
Expand Down Expand Up @@ -105,19 +121,18 @@ def build_image(name, tag, args=None):

buf = BytesIO()
docker.stream_context_tar(".", image_dir, buf, "", args)
subprocess.run(
["docker", "image", "build", "--no-cache", "-t", tag, "-"], input=buf.getvalue()
)
cmdargs = ["docker", "image", "build", "--no-cache", "-"]
if tag:
cmdargs.insert(-1, f"-t={tag}")
subprocess.run(cmdargs, input=buf.getvalue())

print(f"Successfully built {name} and tagged with {tag}")
msg = f"Successfully built {name}"
if tag:
msg += f" and tagged with {tag}"
print(msg)

if tag.endswith(":latest"):
print("*" * 50)
print("WARNING: no VERSION file found in image directory.")
print("Image is not suitable for deploying/pushing.")
print("Create an image suitable for deploying/pushing by creating")
print("a VERSION file in the image directory.")
print("*" * 50)
if not tag or tag.endswith(":latest"):
print(DEPLOY_WARNING.format(image_dir=os.path.relpath(image_dir), image=name))


def load_image(url, imageName=None, imageTag=None):
Expand Down
19 changes: 15 additions & 4 deletions src/taskgraph/util/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io
import os
import re
from typing import Optional

from taskgraph.util.archive import create_tar_gz_from_files
from taskgraph.util.memoize import memoize
Expand All @@ -16,25 +17,35 @@
from .yaml import load_yaml


def docker_image(name, by_tag=False):
def docker_image(name: str, by_tag: bool = False) -> Optional[str]:
"""
Resolve in-tree prebuilt docker image to ``<registry>/<repository>@sha256:<digest>``,
or ``<registry>/<repository>:<tag>`` if `by_tag` is `True`.

Args:
name (str): The image to build.
by_tag (bool): If True, will apply a tag based on VERSION file.
Otherwise will apply a hash based on HASH file.
Returns:
Optional[str]: Image if it can be resolved, otherwise None.
"""
try:
with open(os.path.join(IMAGE_DIR, name, "REGISTRY")) as f:
registry = f.read().strip()
except OSError:
with open(os.path.join(IMAGE_DIR, "REGISTRY")) as f:
registry = f.read().strip()
try:
with open(os.path.join(IMAGE_DIR, "REGISTRY")) as f:
registry = f.read().strip()
except OSError:
return None

if not by_tag:
hashfile = os.path.join(IMAGE_DIR, name, "HASH")
try:
with open(hashfile) as f:
return f"{registry}/{name}@{f.read().strip()}"
except OSError:
raise Exception(f"Failed to read HASH file {hashfile}")
return None

try:
with open(os.path.join(IMAGE_DIR, name, "VERSION")) as f:
Expand Down
1 change: 1 addition & 0 deletions test/data/taskcluster/docker/hello-world-tag/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM hello-world
1 change: 1 addition & 0 deletions test/data/taskcluster/docker/hello-world-tag/REGISTRY
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
1 change: 1 addition & 0 deletions test/data/taskcluster/docker/hello-world-tag/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0
1 change: 1 addition & 0 deletions test/data/taskcluster/docker/hello-world/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM hello-world
55 changes: 55 additions & 0 deletions test/test_docker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import pytest

from taskgraph import docker


@pytest.fixture(autouse=True, scope="module")
def mock_docker_path(module_mocker, datadir):
module_mocker.patch(
"taskgraph.util.docker.IMAGE_DIR", str(datadir / "taskcluster" / "docker")
)


@pytest.fixture
def mock_docker_build(mocker):
def side_effect(topsrcdir, context_dir, out_file, image_name=None, args=None):
out_file.write(b"xyz")

m_stream = mocker.patch.object(docker.docker, "stream_context_tar")
m_stream.side_effect = side_effect

m_run = mocker.patch.object(docker.subprocess, "run")
return (m_stream, m_run)


def test_build_image(capsys, mock_docker_build):
m_stream, m_run = mock_docker_build
image = "hello-world-tag"
tag = f"test/{image}:1.0"

assert docker.build_image(image, None) is None
m_stream.assert_called_once()
m_run.assert_called_once_with(
["docker", "image", "build", "--no-cache", f"-t={tag}", "-"],
input=b"xyz",
)

out, _ = capsys.readouterr()
assert f"Successfully built {image} and tagged with {tag}" in out
assert "Image is not suitable for deploying/pushing" not in out


def test_build_image_no_tag(capsys, mock_docker_build):
m_stream, m_run = mock_docker_build
image = "hello-world"

assert docker.build_image(image, None) is None
m_stream.assert_called_once()
m_run.assert_called_once_with(
["docker", "image", "build", "--no-cache", "-"],
input=b"xyz",
)

out, _ = capsys.readouterr()
assert f"Successfully built {image}" in out
assert "Image is not suitable for deploying/pushing" in out