Skip to content

Commit

Permalink
Merge pull request #69 from tkdchen/STONEBLD-1831-resolve-konflux-sou…
Browse files Browse the repository at this point in the history
…rce-image

Resolve source image by image manifest
  • Loading branch information
tkdchen authored Apr 25, 2024
2 parents d6defa3 + 381640f commit a98d5f9
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 17 deletions.
36 changes: 27 additions & 9 deletions source-container-build/app/source_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,12 @@ def push_to_registry(image_build_output_dir: str, dest_images: list[str]) -> str
return f.read().strip()


def generate_konflux_source_image(image: str) -> str:
# in format: sha256:1234567
digest = fetch_image_manifest_digest(image)
return f"{image.rsplit(':', 1)[0]}:{digest.replace(':', '-')}.src"


def generate_source_images(image: str) -> list[str]:
"""Generate source container images from the built binary image
Expand All @@ -397,11 +403,7 @@ def generate_source_images(image: str) -> list[str]:
"""
# For backward-compatibility. It will be removed in near future.
deprecated_image = f"{image}.src"

# in format: sha256:1234567
digest = fetch_image_manifest_digest(image)
source_image = f"{image.rsplit(':', 1)[0]}:{digest.replace(':', '-')}.src"

source_image = generate_konflux_source_image(image)
return [deprecated_image, source_image]


Expand All @@ -420,11 +422,27 @@ def resolve_source_image_by_version_release(binary_image: str) -> str | None:
release = config_data["config"]["Labels"].get("release")
if not (version and release):
log.warning("Image %s is not labelled with version and release.", binary_image)
return
return None
# Remove possible tag or digest from binary image
source_image = f"{name}:{version}-{release}-source"
if registry_has_image(source_image):
return source_image
else:
log.info("Source container image %s does not exist.", source_image)


def resolve_source_image_by_manifest(image: str) -> str | None:
"""Resolve source image by following Konflux source image scheme
:param image: str, a binary image whose source image is resolved.
:return: the resolved source image URL. If no one is resolved, None is returned.
"""
source_image = generate_konflux_source_image(image)
if registry_has_image(source_image):
return source_image
else:
log = logging.getLogger(f"{logger.name}.resolve_source_image")
log.info("Source container image %s does not exist.", source_image)


def parse_image_name(image: str) -> tuple[str, str, str]:
Expand Down Expand Up @@ -997,11 +1015,11 @@ def build(args) -> BuildResult:

allowed = urlparse("docker://" + base_image).netloc in args.registry_allowlist
if allowed:
source_image = resolve_source_image_by_version_release(base_image)
source_image = resolve_source_image_by_version_release(
base_image
) or resolve_source_image_by_manifest(base_image)
if source_image:
parent_sources_dir = download_parent_image_sources(source_image, work_dir)
else:
logger.warning("Registry does not have source image %s", source_image)
else:
logger.info(
"Image %s does not come from supported allowed registry. "
Expand Down
55 changes: 47 additions & 8 deletions source-container-build/app/test_source_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,7 @@ def _test_include_sources(
parent_images: str = "",
expect_parent_image_sources_included: bool = False,
mock_nonexisting_source_image: bool = False,
source_image_is_resolved_by_version_release: bool = True,
):
"""Test include various sources and app source will always be included"""

Expand Down Expand Up @@ -785,23 +786,28 @@ def run_side_effect(cmd, **kwargs):
self.assertNotIn(":9.3-1", dest_image, "tag is not removed from image pullspec")

if cmd[2] == "--config":
return Mock(
stdout=json.dumps(
{"config": {"Labels": {"version": "9.3", "release": "1"}}}
)
)
# Get image config
if source_image_is_resolved_by_version_release:
config = {"config": {"Labels": {"version": "9.3", "release": "1"}}}
else:
config = {"config": {"Labels": {}}}
return Mock(stdout=json.dumps(config))

if cmd[2] == "--raw":
if not source_image_is_resolved_by_version_release:
dest_image = cmd[-1]
source_tag = self.BINARY_IMAGE_MANIFEST_DIGEST.replace(":", "-") + ".src"
self.assertTrue(dest_image.endswith(source_tag))

# Indicate the source image of parent image exists
if mock_nonexisting_source_image:
return Mock(returncode=1)
else:
return Mock(returncode=0)

if cmd[2] == "--format":
mock = Mock()
mock.stdout = self.BINARY_IMAGE_MANIFEST_DIGEST
return mock
# Get image manifest
return Mock(stdout=self.BINARY_IMAGE_MANIFEST_DIGEST)

if run_cmd == ["skopeo", "copy"]:
args = create_skopeo_cli_parser().parse_args(cmd[1:])
Expand Down Expand Up @@ -958,6 +964,13 @@ def test_not_include_parent_image_sources_from_disallowed_registry(self):
expect_parent_image_sources_included=False,
)

def test_resolve_source_image_by_image_manifest(self):
self._test_include_sources(
parent_images="registry.access.example.com/ubi9/ubi:9.3-1@sha256:123\n",
expect_parent_image_sources_included=True,
source_image_is_resolved_by_version_release=False,
)

@patch("source_build.run")
def test_create_a_temp_dir_as_workspace(self, run):
def run_side_effect(cmd, **kwargs):
Expand Down Expand Up @@ -1219,3 +1232,29 @@ def test_deduplicate(self):

expected = sorted(["requests-1.23-1.src.rpm", "flask-2.0.tar.gz"])
self.assertListEqual(expected, sorted(remains_in_parent))


class TestResolveSourceImageByManifest(unittest.TestCase):
"""Test resolve_source_image_by_manifest"""

@patch("source_build.run")
def test_source_image_is_resolved(self, mock_run: MagicMock):
manifest_digest = "sha256:123456"
skopeo_inspect_digest_rv = Mock(stdout=manifest_digest)
skopeo_inspect_raw_rv = Mock(returncode=0)
mock_run.side_effect = [skopeo_inspect_digest_rv, skopeo_inspect_raw_rv]

source_image = source_build.resolve_source_image_by_manifest("registry.io:3000/ns/app:1.0")

self.assertEqual("registry.io:3000/ns/app:sha256-123456.src", source_image)

@patch("source_build.run")
def test_source_image_does_not_exist(self, mock_run: MagicMock):
manifest_digest = "sha256:123456"
skopeo_inspect_digest_rv = Mock(stdout=manifest_digest)
skopeo_inspect_raw_rv = Mock(returncode=1)
mock_run.side_effect = [skopeo_inspect_digest_rv, skopeo_inspect_raw_rv]

source_image = source_build.resolve_source_image_by_manifest("registry.io:3000/ns/app:1.0")

self.assertIsNone(source_image)

0 comments on commit a98d5f9

Please sign in to comment.