Skip to content

Commit

Permalink
Use layers digests for comparing podman images
Browse files Browse the repository at this point in the history
Previous method checked default digest provided by podman. This digest
is "local" and changed every time image is saved/load or at any other
point manifest is modified. This doesn't mean that it is a different
image.

Viable way for our purposes is to compare that all layers are identical
and in the same order. Simple way to distill this into one value is to
concatenate individual layers' digests in order of appearance in RootFS.

Related to discussion at
containers/podman#24818
  • Loading branch information
tkopecek committed Dec 16, 2024
1 parent 9f0247c commit 8f7301c
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 17 deletions.
2 changes: 1 addition & 1 deletion mock/docs/buildroot-lock-schema-1.0.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
"additionalProperties": false,
"properties": {
"image_digest": {
"description": "Digest got by the 'podman image inspect --format {{ .Digest }}' command, sha256 string",
"description": "SHA256 digest concatenated RootFS layer digests and Config section from 'podman image inspect' command, sha256 string",
"type": "string"
}
}
Expand Down
6 changes: 3 additions & 3 deletions mock/py/mockbuild/buildroot.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,13 +281,13 @@ def _fallback(message):
self.chroot_image, podman.image_id)
podman.tag_image()

digest_expected = self.image_assert_digest
digest_expected = self.config.get("image_assert_digest", True)
if digest_expected:
getLog().info("Checking image digest: %s",
digest_expected)
digest = podman.get_image_digest()
digest = podman.get_layers_digest()
if digest != digest_expected:
getLog().warning(
raise BootstrapError(
f"Expected digest for image {podman.image} is"
f"{digest_expected}, but {digest} found.")

Expand Down
38 changes: 26 additions & 12 deletions mock/py/mockbuild/podman.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# vim: noai:ts=4:sw=4:expandtab

import hashlib
import json
import os
import logging
import subprocess
Expand Down Expand Up @@ -132,26 +134,38 @@ def mounted_image(self):
subprocess.run(cmd_umount, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=True)

def get_image_digest(self):
def get_layers_digest(self):
"""
Get the "sha256:..." string for the image we work with.
Get sha256 digest of RootFS layers. This must be identical for
all images containing same order of layers, thus it can be used
as the check that we've loaded same image.
"""
the_image = self.image
if the_image.startswith("oci-archive:"):
# We can't query digest from tarball directly, but note
# the image needs to be tagged first!
the_image = self._tagged_id
check = [self.podman_binary, "image", "inspect", the_image,
"--format", "{{ .Digest }}"]
check = [self.podman_binary, "image", "inspect", self.image]
result = subprocess.run(check, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=False,
encoding="utf8")
if result.returncode:
raise PodmanError(f"Can't get {the_image} podman image digest: {result.stderr}")
result = result.stdout.strip()
if len(result.splitlines()) != 1:
raise PodmanError(f"The digest of {the_image} image is not a single-line string")
return result

try:
data = json.loads(result)[0]
except json.JSONDecodeError as exc:
raise BootstrapError(f"The manifest data of {self.image} "

Check warning

Code scanning / vcs-diff-lint

Podman.get_layers_digest: Undefined variable 'BootstrapError' Warning

Podman.get_layers_digest: Undefined variable 'BootstrapError'

Check warning

Code scanning / vcs-diff-lint

Undefined name BootstrapError Warning

Undefined name BootstrapError
"are not json-formatted.") from exc
if 'RootFS' not in data:
raise BootstrapError(f"RootFS section of {self.image} is missing.")

Check warning

Code scanning / vcs-diff-lint

Podman.get_layers_digest: Undefined variable 'BootstrapError' Warning

Podman.get_layers_digest: Undefined variable 'BootstrapError'

Check warning

Code scanning / vcs-diff-lint

Undefined name BootstrapError Warning

Undefined name BootstrapError
if data['RootFS']['Type'] != 'layers':
raise BootstrapError(f"Unexpected format for RootFS in {self.image}.")

Check warning

Code scanning / vcs-diff-lint

Podman.get_layers_digest: Undefined variable 'BootstrapError' Warning

Podman.get_layers_digest: Undefined variable 'BootstrapError'

Check warning

Code scanning / vcs-diff-lint

Undefined name BootstrapError Warning

Undefined name BootstrapError

# data which should be sufficient to confirm the image
data = {
'RootFS': data['RootFS'],
'Config': data['Config'],
}
# convert to json string with ordered dicts and create hash
data = json.dumps(data, sort_keys=True)
return hashlib.sha256(data.encode()).hexdigest()

def check_native_image_architecture(self):
"""
Expand Down
2 changes: 1 addition & 1 deletion mock/tests/test_buildroot_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def _call_method(plugins, buildroot):
_, method = plugins.add_hook.call_args[0]

podman_obj = MagicMock()
podman_obj.get_image_digest.return_value = EXPECTED_OUTPUT["bootstrap"]["image_digest"]
podman_obj.get_layers_digest.return_value = EXPECTED_OUTPUT["bootstrap"]["image_digest"]
podman_cls = MagicMock(return_value=podman_obj)
with patch("mockbuild.plugins.buildroot_lock.Podman", side_effect=podman_cls):
method()
Expand Down
2 changes: 2 additions & 0 deletions releng/release-notes-next/podman-digests.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Hermetic build process is enhanced by adding used imaged digests into the
metadata and confirming that exactly same image is used in the next step.

0 comments on commit 8f7301c

Please sign in to comment.