mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-14 14:47:12 +00:00
A suppression entry under <service>.security.suppressions now names either one CVE (id) or one image tag (image_tag), each with until. An image_tag entry accepts every finding of the service's image while the owner runs that tag; each owner of a shared digest is judged by its own tag (Config.Image at runtime, the pinned version in the mirror scan), so a container on any other tag stays unsuppressed. Host suppressions are the id entries of the engine services; an image_tag entry there covers the engine's own image. The cve lint requires image_tag to equal the service's version, and the Docker image updater drops a service's image_tag entries with their markers when it moves the service to another version, so a tag suppression never outlives its tag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
120 lines
3.4 KiB
Python
120 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from utils.cache.files import read_text
|
|
from utils.roles.mapping import ROLE_FILE_META_SERVICES
|
|
from utils.update.docker import (
|
|
_registry_cursor,
|
|
collect_entries,
|
|
sourced_services,
|
|
update_config_versions,
|
|
)
|
|
|
|
SOURCED = """bundled:
|
|
image: example/app
|
|
version: v1.0.0
|
|
update:
|
|
type: http_regex
|
|
url: https://example.invalid/bundle
|
|
pattern: "app:(v[0-9.]+)"
|
|
tracked:
|
|
image: example/side
|
|
version: v2.0.0
|
|
other_version: v3.0.0
|
|
update:
|
|
- key: other_version
|
|
type: git_tags
|
|
repository: https://example.invalid/side.git
|
|
plain:
|
|
image: example/plain
|
|
version: v4.0.0
|
|
"""
|
|
|
|
|
|
class TestRegistryCursor(unittest.TestCase):
|
|
def test_v_prefixed_pin_seeds_cursor(self) -> None:
|
|
self.assertEqual(_registry_cursor("v19.1.1"), "v")
|
|
|
|
def test_bare_numeric_pin_scans_from_start(self) -> None:
|
|
self.assertIsNone(_registry_cursor("19.1.1"))
|
|
|
|
|
|
class TestDeclaredSource(unittest.TestCase):
|
|
def test_a_version_with_its_own_source_is_left_to_that_source(self) -> None:
|
|
root = Path(tempfile.mkdtemp())
|
|
config = root / "roles" / "web-app-example" / ROLE_FILE_META_SERVICES
|
|
config.parent.mkdir(parents=True)
|
|
config.write_text(SOURCED, encoding="utf-8")
|
|
|
|
self.assertEqual(sourced_services(config), {"bundled"})
|
|
self.assertEqual(
|
|
sorted(entry.service for entry in collect_entries(root)),
|
|
["plain", "tracked"],
|
|
)
|
|
|
|
|
|
class TestUpdateDocker(unittest.TestCase):
|
|
def test_update_config_versions_updates_only_target_services(self) -> None:
|
|
original = """moodle:
|
|
version: "4.5" # Keep comment
|
|
image: bitnamilegacy/moodle
|
|
nginx:
|
|
version: alpine
|
|
image: nginx
|
|
"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
config_path = Path(tmpdir) / "main.yml"
|
|
config_path.write_text(original, encoding="utf-8")
|
|
|
|
changed = update_config_versions(config_path, {"moodle": "5.0"})
|
|
|
|
self.assertTrue(changed)
|
|
updated = read_text(str(config_path))
|
|
self.assertIn('version: "5.0" # Keep comment', updated)
|
|
self.assertIn("version: alpine", updated)
|
|
self.assertNotIn('version: "4.5" # Keep comment', updated)
|
|
|
|
def test_a_bump_drops_the_image_tag_suppression_of_the_old_tag(self) -> None:
|
|
original = """penpot:
|
|
image: penpotapp/exporter
|
|
version: "2.18.5"
|
|
security:
|
|
suppressions:
|
|
# nocheck: cve upstream ships no rebuild yet
|
|
- image_tag: "2.18.5"
|
|
until: 2026-12-31
|
|
# nocheck: shared
|
|
nginx:
|
|
image: nginx
|
|
version: "1.29"
|
|
security:
|
|
suppressions:
|
|
# nocheck: cve upstream ships no rebuild yet
|
|
- image_tag: "1.29"
|
|
until: 2026-12-31
|
|
"""
|
|
expected = original.replace(
|
|
""" version: "2.18.5"
|
|
security:
|
|
suppressions:
|
|
# nocheck: cve upstream ships no rebuild yet
|
|
- image_tag: "2.18.5"
|
|
until: 2026-12-31
|
|
""",
|
|
' version: "2.18.6"\n',
|
|
)
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
config_path = Path(tmpdir) / "main.yml"
|
|
config_path.write_text(original, encoding="utf-8")
|
|
|
|
update_config_versions(config_path, {"penpot": "2.18.6"})
|
|
|
|
self.assertEqual(read_text(str(config_path)), expected)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|