Skip to content

Commit ed4594d

Browse files
tkopecekxsuchy
authored andcommitted
Use layers digests for comparing podman images
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
1 parent b23e054 commit ed4594d

File tree

6 files changed

+59
-19
lines changed

6 files changed

+59
-19
lines changed

mock/docs/buildroot-lock-schema-1.0.0.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
"additionalProperties": false,
8484
"properties": {
8585
"image_digest": {
86-
"description": "Digest got by the 'podman image inspect --format {{ .Digest }}' command, sha256 string",
86+
"description": "SHA256 digest concatenated RootFS layer digests and Config section from 'podman image inspect' command, sha256 string",
8787
"type": "string"
8888
}
8989
}

mock/py/mockbuild/buildroot.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,13 @@ def _fallback(message):
281281
self.chroot_image, podman.image_id)
282282
podman.tag_image()
283283

284-
digest_expected = self.image_assert_digest
284+
digest_expected = self.config.get("image_assert_digest")
285285
if digest_expected:
286286
getLog().info("Checking image digest: %s",
287287
digest_expected)
288-
digest = podman.get_image_digest()
288+
digest = podman.get_oci_digest()
289289
if digest != digest_expected:
290-
getLog().warning(
290+
raise BootstrapError(
291291
f"Expected digest for image {podman.image} is"
292292
f"{digest_expected}, but {digest} found.")
293293

mock/py/mockbuild/plugins/buildroot_lock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def _executor(cmd):
104104
try:
105105
podman = Podman(self.buildroot,
106106
data["config"]["bootstrap_image"])
107-
digest = podman.get_image_digest()
107+
digest = podman.get_oci_digest()
108108
except PodmanError:
109109
digest = "unknown"
110110
data["bootstrap"] = {

mock/py/mockbuild/podman.py

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# -*- coding: utf-8 -*-
22
# vim: noai:ts=4:sw=4:expandtab
33

4+
import hashlib
5+
import json
46
import os
57
import logging
68
import subprocess
@@ -16,6 +18,47 @@ class PodmanError(Exception):
1618
"""
1719

1820

21+
def podman_get_oci_digest(image, logger=None, podman_binary=None):
22+
"""
23+
Get sha256 digest of RootFS layers. This must be identical for
24+
all images containing same order of layers, thus it can be used
25+
as the check that we've loaded same image.
26+
"""
27+
logger = logger or logging.getLogger()
28+
podman = podman_binary or "/usr/bin/podman"
29+
logger.info("Calculating %s image OCI digest", image)
30+
check = [podman, "image", "inspect", image]
31+
result = subprocess.run(check, stdout=subprocess.PIPE,
32+
stderr=subprocess.PIPE, check=False,
33+
encoding="utf8")
34+
if result.returncode:
35+
logger.error("Can't get %s podman image digest: %s", image, result.stderr)
36+
return None
37+
result = result.stdout.strip()
38+
39+
try:
40+
data = json.loads(result)[0]
41+
except json.JSONDecodeError:
42+
logger.error("The manifest data of %s are not json-formatted.", image)
43+
return None
44+
45+
if 'RootFS' not in data:
46+
logger.error("RootFS section of %s is missing.", image)
47+
return None
48+
if data['RootFS']['Type'] != 'layers':
49+
logger.error("Unexpected format for RootFS in %s.", image)
50+
return None
51+
52+
# data which should be sufficient to confirm the image
53+
data = {
54+
'RootFS': data['RootFS'],
55+
'Config': data['Config'],
56+
}
57+
# convert to json string with ordered dicts and create hash
58+
data = json.dumps(data, sort_keys=True)
59+
return hashlib.sha256(data.encode()).hexdigest()
60+
61+
1962
def podman_check_native_image_architecture(image, logger=None, podman_binary=None):
2063
"""
2164
Return True if image's architecture is "native" for this host.
@@ -132,26 +175,21 @@ def mounted_image(self):
132175
subprocess.run(cmd_umount, stdout=subprocess.PIPE,
133176
stderr=subprocess.PIPE, check=True)
134177

135-
def get_image_digest(self):
178+
def get_oci_digest(self):
136179
"""
137-
Get the "sha256:..." string for the image we work with.
180+
Get sha256 digest of RootFS layers. This must be identical for
181+
all images containing same order of layers, thus it can be used
182+
as the check that we've loaded same image.
138183
"""
139184
the_image = self.image
140185
if the_image.startswith("oci-archive:"):
141186
# We can't query digest from tarball directly, but note
142187
# the image needs to be tagged first!
143188
the_image = self._tagged_id
144-
check = [self.podman_binary, "image", "inspect", the_image,
145-
"--format", "{{ .Digest }}"]
146-
result = subprocess.run(check, stdout=subprocess.PIPE,
147-
stderr=subprocess.PIPE, check=False,
148-
encoding="utf8")
149-
if result.returncode:
150-
raise PodmanError(f"Can't get {the_image} podman image digest: {result.stderr}")
151-
result = result.stdout.strip()
152-
if len(result.splitlines()) != 1:
153-
raise PodmanError(f"The digest of {the_image} image is not a single-line string")
154-
return result
189+
digest = podman_get_oci_digest(the_image, logger=getLog())
190+
if digest is None:
191+
raise PodmanError(f"Getting OCI digest for image {self.image} failed")
192+
return digest
155193

156194
def check_native_image_architecture(self):
157195
"""

mock/tests/test_buildroot_lock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def _call_method(plugins, buildroot):
9999
_, method = plugins.add_hook.call_args[0]
100100

101101
podman_obj = MagicMock()
102-
podman_obj.get_image_digest.return_value = EXPECTED_OUTPUT["bootstrap"]["image_digest"]
102+
podman_obj.get_oci_digest.return_value = EXPECTED_OUTPUT["bootstrap"]["image_digest"]
103103
podman_cls = MagicMock(return_value=podman_obj)
104104
with patch("mockbuild.plugins.buildroot_lock.Podman", side_effect=podman_cls):
105105
method()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hermetic build process is enhanced by adding used imaged digests into the
2+
metadata and confirming that exactly same image is used in the next step.

0 commit comments

Comments
 (0)