mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-07 03:15:56 +00:00
A digest pin was emitted as `name:sha256:...`, which no registry accepts:
image_source('ghcr.io/.../csp-checker', 'sha256:d559...') returned
'ghcr.io/.../csp-checker:3.0.0'. The separator depends on the pin class, so
the class has to be decided before the reference is built.
utils/docker/image/pin.py names the three classes in preference order
(semver, then digest, then a general ref that no registry can order) and
maps each to the nocheck marker that declares it. Both discovery entry
points now end in pull_reference(), so '@' versus ':' is decided in one
place instead of at every call site.
The semver pin lint points a digest at 'docker-digest' instead of
'docker-version', so the two fallbacks stay distinguishable in the
suppression catalog. It stays warn-only.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
89 lines
3.0 KiB
Python
89 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
|
|
from utils.docker.image.discovery import docker_hub_source, image_source
|
|
from utils.docker.image.pin import (
|
|
DIGEST,
|
|
REF,
|
|
RULE_BY_CLASS,
|
|
SEMVER,
|
|
is_digest,
|
|
pin_class,
|
|
pull_reference,
|
|
reference_separator,
|
|
)
|
|
|
|
DIGEST_VALUE = "sha256:d5591131b1d898cd02fc7a17adb8ea026352d67809bc9289707778e9e03a76a4"
|
|
|
|
|
|
class TestPinClass(unittest.TestCase):
|
|
def test_a_semver_tag_is_a_semver_pin(self):
|
|
for value in ("3.0.1", "16", "17-3.5", "5.4.5-php8.3-apache", "v1.12.27"):
|
|
self.assertEqual(pin_class(value), SEMVER, value)
|
|
|
|
def test_a_content_address_is_a_digest_pin(self):
|
|
self.assertEqual(pin_class(DIGEST_VALUE), DIGEST)
|
|
|
|
def test_a_moving_name_is_neither(self):
|
|
for value in ("latest", "main", "stable", "buildx-stable-1"):
|
|
self.assertEqual(pin_class(value), REF, value)
|
|
|
|
def test_surrounding_whitespace_does_not_change_the_class(self):
|
|
self.assertEqual(pin_class(f" {DIGEST_VALUE} "), DIGEST)
|
|
self.assertEqual(pin_class(" 3.0.1 "), SEMVER)
|
|
|
|
def test_every_class_has_its_own_rule(self):
|
|
rules = [RULE_BY_CLASS[SEMVER], RULE_BY_CLASS[DIGEST], RULE_BY_CLASS[REF]]
|
|
|
|
self.assertEqual(len(set(rules)), 3, "a shared rule cannot express intent")
|
|
self.assertEqual(rules, ["docker-version", "docker-digest", "docker-ref"])
|
|
|
|
|
|
class TestPullReference(unittest.TestCase):
|
|
def test_a_digest_is_joined_with_an_at_sign(self):
|
|
self.assertEqual(reference_separator(DIGEST_VALUE), "@")
|
|
self.assertEqual(
|
|
pull_reference("ghcr.io/owner/app", DIGEST_VALUE),
|
|
f"ghcr.io/owner/app@{DIGEST_VALUE}",
|
|
)
|
|
|
|
def test_a_tag_is_joined_with_a_colon(self):
|
|
self.assertEqual(reference_separator("3.0.1"), ":")
|
|
self.assertEqual(pull_reference("ghcr.io/owner/app", "3.0.1"), "ghcr.io/owner/app:3.0.1")
|
|
|
|
def test_is_digest_rejects_a_tag_that_merely_mentions_sha256(self):
|
|
self.assertFalse(is_digest("v2-sha256:abc"))
|
|
|
|
|
|
class TestSourceKeepsTheDigest(unittest.TestCase):
|
|
"""A pull ref that silently drops the digest pins nothing at all."""
|
|
|
|
def test_image_source_keeps_a_digest(self):
|
|
self.assertEqual(
|
|
image_source("ghcr.io/owner/app", DIGEST_VALUE),
|
|
f"ghcr.io/owner/app@{DIGEST_VALUE}",
|
|
)
|
|
|
|
def test_image_source_still_tags_a_semver(self):
|
|
self.assertEqual(image_source("ghcr.io/owner/app", "3.0.1"), "ghcr.io/owner/app:3.0.1")
|
|
|
|
def test_docker_hub_source_keeps_a_digest(self):
|
|
self.assertEqual(
|
|
docker_hub_source("postgres", DIGEST_VALUE),
|
|
f"docker.io/library/postgres@{DIGEST_VALUE}",
|
|
)
|
|
|
|
def test_a_digest_never_yields_a_tag_separator(self):
|
|
source = image_source("quay.io/owner/app", DIGEST_VALUE)
|
|
|
|
self.assertNotIn(
|
|
f":{DIGEST_VALUE}",
|
|
source,
|
|
"a colon here would make the registry read the digest as a tag name",
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|