mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-16 10:32:02 +00:00
fix(oci): preserve multi-arch digest during recreate
This commit is contained in:
@@ -166,6 +166,7 @@ def resolve_candidate(reference, architecture):
|
|||||||
if '@' in reference:
|
if '@' in reference:
|
||||||
transport = repo + '@' + reference.split('@', 1)[1]
|
transport = repo + '@' + reference.split('@', 1)[1]
|
||||||
raw = command('skopeo', 'inspect', '--raw', 'docker://' + transport)
|
raw = command('skopeo', 'inspect', '--raw', 'docker://' + transport)
|
||||||
|
registry_digest = 'sha256:' + sha(raw)
|
||||||
manifest = json.loads(raw)
|
manifest = json.loads(raw)
|
||||||
if 'manifests' in manifest:
|
if 'manifests' in manifest:
|
||||||
matches = [m for m in manifest['manifests'] if m.get('platform', {}).get('architecture') == architecture
|
matches = [m for m in manifest['manifests'] if m.get('platform', {}).get('architecture') == architecture
|
||||||
@@ -192,7 +193,8 @@ def resolve_candidate(reference, architecture):
|
|||||||
# The build date identifies the image as the publisher released it: it is
|
# The build date identifies the image as the publisher released it: it is
|
||||||
# what changes when an image is rebuilt, whether or not the application
|
# what changes when an image is rebuilt, whether or not the application
|
||||||
# version inside it moved.
|
# version inside it moved.
|
||||||
return {'manifest_digest': digest, 'layers': [layer['digest'] for layer in json.loads(raw).get('layers', [])],
|
return {'manifest_digest': digest, 'registry_digest': registry_digest,
|
||||||
|
'layers': [layer['digest'] for layer in json.loads(raw).get('layers', [])],
|
||||||
'defaults': defaults, 'version': version, 'created': config.get('created')}
|
'defaults': defaults, 'version': version, 'created': config.get('created')}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,10 @@ def resolve_archive(desired, config, current=None, check=None, required_digest=N
|
|||||||
digest = candidate['manifest_digest']
|
digest = candidate['manifest_digest']
|
||||||
if not re.fullmatch(r'sha256:[a-f0-9]{64}', digest):
|
if not re.fullmatch(r'sha256:[a-f0-9]{64}', digest):
|
||||||
raise ValueError(translate('Invalid registry digest'))
|
raise ValueError(translate('Invalid registry digest'))
|
||||||
if required_digest is not None and digest != required_digest:
|
registry_digest = candidate.get('registry_digest')
|
||||||
|
if registry_digest is not None and not re.fullmatch(r'sha256:[a-f0-9]{64}', registry_digest):
|
||||||
|
raise ValueError(translate('Invalid registry digest'))
|
||||||
|
if required_digest is not None and registry_digest != required_digest:
|
||||||
raise ValueError(translate('The registry did not return the saved image digest'))
|
raise ValueError(translate('The registry did not return the saved image digest'))
|
||||||
msg_ok(f"{translate('Image:')} {reference} ({candidate.get('version') or digest[7:19]})")
|
msg_ok(f"{translate('Image:')} {reference} ({candidate.get('version') or digest[7:19]})")
|
||||||
if current and same_image(candidate, current):
|
if current and same_image(candidate, current):
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
"""A Recreate pins the registry/index digest while downloading a platform manifest."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
sys.path.insert(0, str(ROOT / "remote"))
|
||||||
|
|
||||||
|
import oci_installation_state as state
|
||||||
|
import oci_update_current as update
|
||||||
|
|
||||||
|
|
||||||
|
def digest(data):
|
||||||
|
return "sha256:" + state.sha(data)
|
||||||
|
|
||||||
|
|
||||||
|
class RecreateRegistryDigestTests(unittest.TestCase):
|
||||||
|
def test_candidate_keeps_the_multi_arch_registry_digest_separate_from_platform_manifest(self):
|
||||||
|
platform = json.dumps({
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"config": {"digest": "sha256:" + "c" * 64},
|
||||||
|
"layers": [],
|
||||||
|
}).encode()
|
||||||
|
index = json.dumps({
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"manifests": [{"digest": digest(platform),
|
||||||
|
"platform": {"architecture": "amd64", "os": "linux"}}],
|
||||||
|
}).encode()
|
||||||
|
config = json.dumps({"architecture": "amd64", "os": "linux",
|
||||||
|
"config": {"Labels": {}, "Env": []}}).encode()
|
||||||
|
with patch.object(state, "command", side_effect=[index, platform, config]):
|
||||||
|
candidate = state.resolve_candidate("postgres@" + digest(index), "amd64")
|
||||||
|
|
||||||
|
self.assertEqual(candidate["registry_digest"], digest(index))
|
||||||
|
self.assertEqual(candidate["manifest_digest"], digest(platform))
|
||||||
|
|
||||||
|
def test_recreate_accepts_saved_index_digest_and_returns_platform_manifest(self):
|
||||||
|
index_digest = "sha256:" + "a" * 64
|
||||||
|
platform_digest = "sha256:" + "b" * 64
|
||||||
|
candidate = {"registry_digest": index_digest, "manifest_digest": platform_digest,
|
||||||
|
"layers": [], "created": "2026-10-05T00:00:00Z"}
|
||||||
|
desired = {"template": {"container_contract": {"image": {"reference": "postgres:latest"}}},
|
||||||
|
"deployment": {"template_storage": "local"}}
|
||||||
|
with patch.object(update, "run_quiet", return_value=json.dumps(candidate)) as query:
|
||||||
|
archive, digest_value = update.resolve_archive(
|
||||||
|
desired, b"arch: amd64\n", current={"manifest_digest": platform_digest},
|
||||||
|
required_digest=index_digest)
|
||||||
|
|
||||||
|
self.assertIsNone(archive)
|
||||||
|
self.assertEqual(digest_value, platform_digest)
|
||||||
|
self.assertIn("postgres@" + index_digest, query.call_args.args[0])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user