From 401beb0643b75acae59a3b07b6d13c514ab97760 Mon Sep 17 00:00:00 2001 From: Adam Cmiel Date: Mon, 9 Dec 2024 10:23:22 +0100 Subject: [PATCH 01/11] base_images_sbom: re-configure formatting * disable flake8 line-length check (it's useless, we have 'black' for formatting checks) * drop unnecessary '# noqa' comments Signed-off-by: Adam Cmiel --- .../app/base_images_sbom_script.py | 2 +- .../app/test_base_images_sbom_script.py | 58 +++++++++---------- .../base-images-sbom-script/app/tox.ini | 6 +- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py index 78479cb..a544d76 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py @@ -18,7 +18,7 @@ def parse_image_reference_to_parts(image): :return: ParsedImage (namedTuple): the image parsed into individual parts """ - # example image: registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac # noqa + # example image: registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac # repository_with_tag = registry.access.redhat.com/ubi8/ubi:latest # digest = sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac # repository = registry.access.redhat.com/ubi8/ubi diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py index 78077e8..3dc2610 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py @@ -23,8 +23,8 @@ "scratch", ], { - "quay.io/mkosiarc_rhtap/single-container-app:f2566ab": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", # noqa - "registry.access.redhat.com/ubi8/ubi:latest": "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", # noqa + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "registry.access.redhat.com/ubi8/ubi:latest": "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", }, [ { @@ -61,8 +61,8 @@ "registry.access.redhat.com/ubi8/ubi:latest", ], { - "quay.io/mkosiarc_rhtap/single-container-app:f2566ab": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", # noqa - "registry.access.redhat.com/ubi8/ubi:latest": "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", # noqa + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "registry.access.redhat.com/ubi8/ubi:latest": "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", }, [ { @@ -91,7 +91,7 @@ ( ["registry.access.redhat.com/ubi8/ubi:latest"], { - "registry.access.redhat.com/ubi8/ubi:latest": "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", # noqa + "registry.access.redhat.com/ubi8/ubi:latest": "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", }, [ { @@ -107,7 +107,7 @@ ( ["quay.io/mkosiarc_rhtap/single-container-app:f2566ab", "scratch"], { - "quay.io/mkosiarc_rhtap/single-container-app:f2566ab": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", # noqa + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", }, [ { @@ -139,10 +139,10 @@ "scratch", ], { - "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", # noqa - "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942": "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", # noqa - "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943": "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", # noqa - "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944": "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944", # noqa + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942": "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", + "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943": "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", + "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944": "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944", }, [ { @@ -210,11 +210,11 @@ ":627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", ], { - "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", # noqa - "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942": "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", # noqa - "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943": "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", # noqa - "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944": "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944", # noqa - "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac": "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", # noqa + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942": "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", + "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943": "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", + "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944": "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944", + "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac": "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", }, [ { @@ -291,11 +291,11 @@ ":627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", ], { - "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", # noqa - "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942": "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", # noqa - "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943": "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", # noqa - "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944": "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944", # noqa - "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac": "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", # noqa + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942": "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", + "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943": "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", + "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944": "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944", + "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac": "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", }, [ { @@ -372,10 +372,10 @@ "scratch", ], { - "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", # noqa - "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942": "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", # noqa - "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943": "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", # noqa - "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944": "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944", # noqa + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942": "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", + "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943": "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", + "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944": "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944", }, [ { @@ -435,8 +435,8 @@ ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", ], { - "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", # noqa - "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942": "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", # noqa + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942": "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", }, [ { @@ -488,8 +488,8 @@ ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", ], { - "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", # noqa - "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942": "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", # noqa + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942": "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", }, [ { @@ -537,7 +537,7 @@ "oci-archive:export/out.ociarchive", ], { - "quay.io/mkosiarc_rhtap/single-container-app:f2566ab": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", # noqa + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", }, [ { diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/tox.ini b/sbom-utility-scripts/scripts/base-images-sbom-script/app/tox.ini index 846a495..d4df57f 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/tox.ini +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/tox.ini @@ -8,8 +8,12 @@ commands = pytest test_base_images_sbom_script.py [testenv:flake8] deps = flake8 -commands = flake8 --max-line-length 120 base_images_sbom_script.py test_base_images_sbom_script.py +commands = flake8 base_images_sbom_script.py test_base_images_sbom_script.py [testenv:black] deps = black commands = black --line-length 120 --check --diff . + +[flake8] +# line-length check is useless since we have auto-formatting +extend-ignore = E501 From 4a8b32a67824855a5edab9cf49e91e2b41ba7939 Mon Sep 17 00:00:00 2001 From: Adam Cmiel Date: Mon, 9 Dec 2024 07:52:53 +0100 Subject: [PATCH 02/11] base_images_sbom: add type hints This script will need some changes soon, those will be far easier to do with type checking enabled. Signed-off-by: Adam Cmiel --- .../app/base_images_sbom_script.py | 72 +++++++++++-------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py index a544d76..64a5b10 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py @@ -1,14 +1,27 @@ -import json import argparse +import json import pathlib +from typing import Any, NamedTuple, TypedDict -from collections import namedtuple from packageurl import PackageURL -ParsedImage = namedtuple("ParsedImage", "repository, digest, name") +class ParsedImage(NamedTuple): + repository: str + digest: str + name: str + + +class CDXComponent(TypedDict): + """The relevant attributes of a CycloneDX Component.""" -def parse_image_reference_to_parts(image): + type: str + name: str + purl: str + properties: list[dict[str, str]] + + +def parse_image_reference_to_parts(image: str) -> ParsedImage: """ This function expects that the image is in the expected format as generated from the output of @@ -33,28 +46,28 @@ def parse_image_reference_to_parts(image): return ParsedImage(repository=repository, digest=digest, name=name) -def get_base_images_sbom_components(base_images, base_images_digests): +def get_base_images_sbom_components(base_images: list[str], base_images_digests: dict[str, str]) -> list[CDXComponent]: """ Creates the base images sbom data - :param base_images: (List) - List of base images used during build, in the order they were used. The values here - are the keys in the base_images_digests dict. - For example: - ["registry.access.redhat.com/ubi8/ubi:latest"] - :param base_images_digests: (Dict) - Dict of base images references, where the key is the image reference as - used in the original Dockerfile (The elements of base_images param) - and the values are the full image reference with digests that was - actually used by buildah during build time. - For example: - { - "registry.access.redhat.com/ubi8/ubi:latest": - "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" - } - :return: components (List) - List of dict items in which each item contains sbom data about each base image + :param base_images: List of base images used during build, in the order they were used. The values here + are the keys in the base_images_digests dict. + For example: + ["registry.access.redhat.com/ubi8/ubi:latest"] + :param base_images_digests: Dict of base images references, where the key is the image reference as + used in the original Dockerfile (The elements of base_images param) + and the values are the full image reference with digests that was + actually used by buildah during build time. + For example: + { + "registry.access.redhat.com/ubi8/ubi:latest": + "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" + } + :return: List of dict items in which each item contains sbom data about each base image """ - components = [] - already_used_base_images = set() + components: list[CDXComponent] = [] + already_used_base_images: set[str] = set() for index, image in enumerate(base_images): # flatpak archive and scratch are not real base images. So we skip them, but @@ -102,7 +115,7 @@ def get_base_images_sbom_components(base_images, base_images_digests): if component["purl"] == purl_str: component["properties"].append(property) else: - component = { + component: CDXComponent = { "type": "container", "name": parsed_image.repository, "purl": purl_str, @@ -114,13 +127,13 @@ def get_base_images_sbom_components(base_images, base_images_digests): return components -def get_base_images_from_dockerfile(parsed_dockerfile): +def get_base_images_from_dockerfile(parsed_dockerfile: dict[str, Any]) -> list[str]: """ Reads the base images from provided parsed dockerfile - :param parsed_dockerfile: (Dict) - Contents of the parsed dockerfile - :return: base_images (List) - List of base images used during build as extracted - from the dockerfile in the order they were used. + :param parsed_dockerfile: Contents of the parsed dockerfile + :return: base_images List of base images used during build as extracted + from the dockerfile in the order they were used. Example: If the Dockerfile looks like @@ -144,7 +157,7 @@ def get_base_images_from_dockerfile(parsed_dockerfile): ] }, """ - base_images = [] + base_images: list[str] = [] # this part of the json is the relevant one that contains the # info about base images @@ -168,7 +181,7 @@ def get_base_images_from_dockerfile(parsed_dockerfile): return base_images -def parse_args(): +def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Updates the sbom file with base images data based on the provided files" ) @@ -193,8 +206,7 @@ def parse_args(): return args -def main(): - +def main() -> None: args = parse_args() with args.parsed_dockerfile.open("r") as f: From b95506a6c79bd1caa7f317624913746a8802281a Mon Sep 17 00:00:00 2001 From: Adam Cmiel Date: Mon, 9 Dec 2024 08:51:20 +0100 Subject: [PATCH 03/11] base_images_sbom: add cyclonex-update function Just move the code from main to a function and slightly simplify. This is preparation for SPDX support. Signed-off-by: Adam Cmiel --- .../app/base_images_sbom_script.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py index 64a5b10..7cdd15f 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py @@ -181,6 +181,16 @@ def get_base_images_from_dockerfile(parsed_dockerfile: dict[str, Any]) -> list[s return base_images +def update_cyclonedx_sbom(sbom: dict[str, Any], base_images: list[CDXComponent]) -> None: + """Update (in-place) a CycloneDX SBOM with a list of base images. + + Add an item containing the base image list into the .formulation section. + + :param base_images: list of base images in CycloneDX format + """ + sbom.setdefault("formulation", []).append({"components": base_images}) + + def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Updates the sbom file with base images data based on the provided files" @@ -224,10 +234,7 @@ def main() -> None: # base_images_sbom_components could be empty, when having just one stage FROM scratch if base_images_sbom_components: - if "formulation" in sbom: - sbom["formulation"].append({"components": base_images_sbom_components}) - else: - sbom.update({"formulation": [{"components": base_images_sbom_components}]}) + update_cyclonedx_sbom(sbom, base_images_sbom_components) with args.sbom.open("w") as f: json.dump(sbom, f, indent=4) From ef63afa4449c5d27ae3382921e67dc32dec54849 Mon Sep 17 00:00:00 2001 From: Adam Cmiel Date: Mon, 9 Dec 2024 08:36:06 +0100 Subject: [PATCH 04/11] base_images_sbom: implement spdx-update function Takes a list of base images in SPDX format, adds them to .packages, updates .relationships correspondingly. Signed-off-by: Adam Cmiel --- .../app/base_images_sbom_script.py | 92 ++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py index 7cdd15f..41ad2fa 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py @@ -1,7 +1,9 @@ import argparse +import datetime +import hashlib import json import pathlib -from typing import Any, NamedTuple, TypedDict +from typing import Any, NamedTuple, NewType, TypedDict from packageurl import PackageURL @@ -21,6 +23,21 @@ class CDXComponent(TypedDict): properties: list[dict[str, str]] +SPDXID = NewType("SPDXID", str) + + +class SPDXPackage(TypedDict): + """The relevant attributes of an SPDX Package (the equivalent of a CycloneDX Component).""" + + # SPDXID, name and downloadLocation are required per the SPDX 2.3 schema + # https://github.com/spdx/spdx-spec/blob/ed8c9b520963b651fce3c86422b387e92e6d9a8f/schemas/spdx-schema.json#L438 + SPDXID: SPDXID + name: str + downloadLocation: str + externalRefs: list[dict[str, str]] + annotations: list[dict[str, str]] + + def parse_image_reference_to_parts(image: str) -> ParsedImage: """ This function expects that the image is in the expected format @@ -181,6 +198,57 @@ def get_base_images_from_dockerfile(parsed_dockerfile: dict[str, Any]) -> list[s return base_images +def cdx_to_spdx(cdx: CDXComponent, annotation_date: datetime.datetime) -> SPDXPackage: + name = cdx["name"] + purl = cdx["purl"] + # consistent with index_image_sbom_script.py + spdxid = SPDXID(f"SPDXRef-image-{name}-{hashlib.sha256(purl.encode()).hexdigest()}") + + return { + "SPDXID": spdxid, + "name": name, + "downloadLocation": "NOASSERTION", + # https://github.com/konflux-ci/architecture/blob/main/ADR/0044-spdx-support.md#componentpurl + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": purl, + }, + ], + # https://github.com/konflux-ci/architecture/blob/main/ADR/0044-spdx-support.md#componentproperties + "annotations": [ + { + "annotator": "Tool: konflux:jsonencoded", + "comment": json.dumps(cdx_property, separators=(",", ":")), + # https://spdx.github.io/spdx-spec/v2.3/annotations/#122-annotation-date-field + "annotationDate": annotation_date.strftime("%Y-%m-%dT%H:%M:%SZ"), + "annotationType": "OTHER", + } + for cdx_property in cdx["properties"] + ], + } + + +def find_spdx_root_package(sbom: dict[str, Any]) -> SPDXID: + """Find the element that's in relationship DESCRIBES . + + If there isn't exactly one such element, raise an error. + """ + doc_spxid = sbom["SPDXID"] + doc_describes = [ + r["relatedSpdxElement"] + for r in sbom.get("relationships", []) + if r["spdxElementId"] == doc_spxid and r["relationshipType"] == "DESCRIBES" + ] + if len(doc_describes) != 1: + raise ValueError( + "Expected to find exactly one DESCRIBES relationship. " + f"Found {len(doc_describes)} ROOTs: {doc_describes}" + ) + return SPDXID(doc_describes[0]) + + def update_cyclonedx_sbom(sbom: dict[str, Any], base_images: list[CDXComponent]) -> None: """Update (in-place) a CycloneDX SBOM with a list of base images. @@ -191,6 +259,28 @@ def update_cyclonedx_sbom(sbom: dict[str, Any], base_images: list[CDXComponent]) sbom.setdefault("formulation", []).append({"components": base_images}) +def update_spdx_sbom(sbom: dict[str, Any], base_images: list[SPDXPackage]) -> None: + """Update (in-place) an SPDX SBOM with a list of base images. + + Add the base images to the .packages section. + Add BUILD_TOOL_OF relationships to the .relationships section. + + :param base_images: list of base images in SPDX format + """ + root = find_spdx_root_package(sbom) + relationships = [ + { + "spdxElementId": base_image["SPDXID"], + "relationshipType": "BUILD_TOOL_OF", + "relatedSpdxElement": root, + } + for base_image in base_images + ] + + sbom.setdefault("packages", []).extend(base_images) + sbom.setdefault("relationships", []).extend(relationships) + + def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Updates the sbom file with base images data based on the provided files" From 148f8906f1df90a628d80626c309553d7babc40b Mon Sep 17 00:00:00 2001 From: Adam Cmiel Date: Mon, 9 Dec 2024 10:00:45 +0100 Subject: [PATCH 05/11] base_images_sbom: enable SPDX support Auto-detect the format of the input SBOM. Call the right SBOM-update function based on the format. Signed-off-by: Adam Cmiel --- .../app/base_images_sbom_script.py | 25 +++++++++++++++---- .../app/test_base_images_sbom_script.py | 3 +++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py index 41ad2fa..3af7644 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py @@ -3,7 +3,7 @@ import hashlib import json import pathlib -from typing import Any, NamedTuple, NewType, TypedDict +from typing import Any, Literal, NamedTuple, NewType, TypedDict from packageurl import PackageURL @@ -281,6 +281,15 @@ def update_spdx_sbom(sbom: dict[str, Any], base_images: list[SPDXPackage]) -> No sbom.setdefault("relationships", []).extend(relationships) +def detect_sbom_type(sbom: dict[str, Any]) -> Literal["cyclonedx", "spdx"]: + if sbom.get("bomFormat") == "CycloneDX": + return "cyclonedx" + elif sbom.get("spdxVersion"): + return "spdx" + else: + raise ValueError("Unknown SBOM format") + + def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Updates the sbom file with base images data based on the provided files" @@ -317,14 +326,20 @@ def main() -> None: base_images_digests_raw = args.base_images_digests.read_text().splitlines() base_images_digests = dict(item.split() for item in base_images_digests_raw) + base_images_sbom_components = get_base_images_sbom_components(base_images, base_images_digests) + # base_images_sbom_components could be empty, when having just one stage FROM scratch + if not base_images_sbom_components: + return + with args.sbom.open("r") as f: sbom = json.load(f) - base_images_sbom_components = get_base_images_sbom_components(base_images, base_images_digests) - - # base_images_sbom_components could be empty, when having just one stage FROM scratch - if base_images_sbom_components: + if detect_sbom_type(sbom) == "cyclonedx": update_cyclonedx_sbom(sbom, base_images_sbom_components) + else: + annotation_date = datetime.datetime.now(datetime.UTC) + base_images_spdx = [cdx_to_spdx(c, annotation_date) for c in base_images_sbom_components] + update_spdx_sbom(sbom, base_images_spdx) with args.sbom.open("w") as f: json.dump(sbom, f, indent=4) diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py index 3dc2610..7f08f11 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py @@ -576,6 +576,7 @@ def test_main_input_sbom_does_not_contain_formulation(tmp_path, mocker): # minimal input sbom file sbom_file.write_text( """{ + "bomFormat": "CycloneDX", "project_name": "MyProject", "version": "1.0", "components": [] @@ -668,6 +669,7 @@ def test_main_input_sbom_does_not_contain_formulation_and_base_image_from_scratc # minimal input sbom file sbom_file.write_text( """{ + "bomFormat": "CycloneDX", "project_name": "MyProject", "version": "1.0", "components": [] @@ -766,6 +768,7 @@ def test_main_input_sbom_contains_formulation(tmp_path, mocker): sbom_file.write_text( """ { + "bomFormat": "CycloneDX", "project_name": "MyProject", "version": "1.0", "components": [], From db229162907005ce9def6c721bac7f293babcb96 Mon Sep 17 00:00:00 2001 From: Adam Cmiel Date: Mon, 9 Dec 2024 12:50:14 +0100 Subject: [PATCH 06/11] base_images_sbom: update README for SPDX support Also fix some minor formatting issues Signed-off-by: Adam Cmiel --- .../scripts/base-images-sbom-script/README.md | 81 ++++++++++++++++++- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/README.md b/sbom-utility-scripts/scripts/base-images-sbom-script/README.md index 72f8eb5..f2fee64 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/README.md +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/README.md @@ -7,10 +7,10 @@ It takes several inputs: 1. path to the sbom file, that will be updated in place with the base image data 2. path to a json file containing parsed Dockerfile via dockerfile-json 3. path to a file containing base images references as used during in the Dockerfile mapped to the full image references -with digests. This mapping is expected to be in the format **|**. -The full image reference with digest is generated from the output of **buildah images --format '{{ .Name }}:{{ .Tag }}@{{ .Digest }}'**. - +with digests. This mapping is expected to be in the format ` `. +The full image reference with digest is generated from the output of `buildah images --format '{{ .Name }}:{{ .Tag }}@{{ .Digest }}'`. +## CycloneDX The base images data will be added in the [formulation attribute](https://cyclonedx.org/docs/1.5/json/#formulation), by appending new item with the **components** array. This will not affect any other items in the formulation array. If the formulation part of the sbom does not exist, it is created. @@ -72,6 +72,78 @@ If a base image is used multiple times, only one component with multiple propert }, ``` +## SPDX + +The script adds the base images to the `.packages` array in the SBOM and adds +` BUILD_TOOL_OF ` relationships to the `.relationships` array. + +Where the CycloneDX SBOM uses `.properties` to indicate whether the image is a "builder" +image or a base image for SPDX we use annotations instead +(see the [relevant section of the ADR][spdx-adr-properties]). + +The result might look something like this: + +``` +{ + // ... + "packages": [ + // ... + { + "SPDXID": "SPDXRef-image-quay.io/mkosiarc_rhtap/single-container-app-9520a72cbb69edfca5cac88ea2a9e0e09142ec934952b9420d686e77765f002c", + "name": "quay.io/mkosiarc_rhtap/single-container-app", + "downloadLocation": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app" + } + ], + "annotations": [ + { + "annotator": "Tool: konflux:jsonencoded", + "comment": "{\"name\":\"konflux:container:is_builder_image:for_stage\",\"value\":\"0\"}", + "annotationDate": "2024-12-09T12:00:00Z", + "annotationType": "OTHER" + } + ] + }, + { + "SPDXID": "SPDXRef-image-registry.access.redhat.com/ubi8/ubi-0f22256f634f8205fbd9c438c387ccf2d4859250e04104571c93fdb89a62bae1", + "name": "registry.access.redhat.com/ubi8/ubi", + "downloadLocation": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi" + } + ], + "annotations": [ + { + "annotator": "Tool: konflux:jsonencoded", + "comment": "{\"name\":\"konflux:container:is_builder_image:for_stage\",\"value\":\"1\"}", + "annotationDate": "2024-12-09T12:00:00Z", + "annotationType": "OTHER" + } + ] + } + ], + "relationships": [ + // ... + { + "spdxElementId": "SPDXRef-image-quay.io/mkosiarc_rhtap/single-container-app-9520a72cbb69edfca5cac88ea2a9e0e09142ec934952b9420d686e77765f002c", + "relationshipType": "BUILD_TOOL_OF", + "relatedSpdxElement": "SPDXRef-main" + }, + { + "spdxElementId": "SPDXRef-image-registry.access.redhat.com/ubi8/ubi-0f22256f634f8205fbd9c438c387ccf2d4859250e04104571c93fdb89a62bae1", + "relationshipType": "BUILD_TOOL_OF", + "relatedSpdxElement": "SPDXRef-main" + } + ] +} +``` ## The image The image that is used in the buildah task in Konflux is built by the Github CI and pushed to @@ -84,4 +156,5 @@ during development with: ``` tox -e test ``` - \ No newline at end of file + +[spdx-adr-properties]: https://github.com/konflux-ci/architecture/blob/main/ADR/0044-spdx-support.md#componentproperties From 13252ff11dd248562ca871cbe6448eb170ede688 Mon Sep 17 00:00:00 2001 From: Adam Cmiel Date: Mon, 9 Dec 2024 10:32:44 +0100 Subject: [PATCH 07/11] test_base_images_sbom: re-format Don't break up single lines into multiple lines Signed-off-by: Adam Cmiel --- .../app/test_base_images_sbom_script.py | 223 ++++++------------ 1 file changed, 70 insertions(+), 153 deletions(-) diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py index 7f08f11..c4fc36a 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py @@ -30,9 +30,7 @@ { "type": "container", "name": "quay.io/mkosiarc_rhtap/single-container-app", - "purl": "pkg:oci/single-container-app@sha256" - ":8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io" - "/mkosiarc_rhtap/single-container-app", + "purl": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -43,8 +41,7 @@ { "type": "container", "name": "registry.access.redhat.com/ubi8/ubi", - "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" - "?repository_url=registry.access.redhat.com/ubi8/ubi", + "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -68,9 +65,7 @@ { "type": "container", "name": "quay.io/mkosiarc_rhtap/single-container-app", - "purl": "pkg:oci/single-container-app@sha256" - ":8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io" - "/mkosiarc_rhtap/single-container-app", + "purl": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -81,8 +76,7 @@ { "type": "container", "name": "registry.access.redhat.com/ubi8/ubi", - "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" - "?repository_url=registry.access.redhat.com/ubi8/ubi", + "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", "properties": [{"name": "konflux:container:is_base_image", "value": "true"}], }, ], @@ -97,8 +91,7 @@ { "type": "container", "name": "registry.access.redhat.com/ubi8/ubi", - "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" - "?repository_url=registry.access.redhat.com/ubi8/ubi", + "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", "properties": [{"name": "konflux:container:is_base_image", "value": "true"}], }, ], @@ -113,9 +106,7 @@ { "type": "container", "name": "quay.io/mkosiarc_rhtap/single-container-app", - "purl": "pkg:oci/single-container-app@sha256" - ":8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io" - "/mkosiarc_rhtap/single-container-app", + "purl": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -128,14 +119,10 @@ # four builder images, and from scratch in last stage ( [ - "quay.io/builder1/builder1:aaaaaaa@sha256" - ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", - "quay.io/builder2/builder2:bbbbbbb@sha256" - ":2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", - "quay.io/builder3/builder3:ccccccc@sha256" - ":3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", - "quay.io/builder4/builder4:ddddddd@sha256" - ":4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944", + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", + "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", + "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944", "scratch", ], { @@ -148,8 +135,7 @@ { "type": "container", "name": "quay.io/builder1/builder1", - "purl": "pkg:oci/builder1@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941" - "?repository_url=quay.io/builder1/builder1", + "purl": "pkg:oci/builder1@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/builder1/builder1", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -160,8 +146,7 @@ { "type": "container", "name": "quay.io/builder2/builder2", - "purl": "pkg:oci/builder2@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942" - "?repository_url=quay.io/builder2/builder2", + "purl": "pkg:oci/builder2@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942?repository_url=quay.io/builder2/builder2", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -172,8 +157,7 @@ { "type": "container", "name": "quay.io/builder3/builder3", - "purl": "pkg:oci/builder3@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943" - "?repository_url=quay.io/builder3/builder3", + "purl": "pkg:oci/builder3@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943?repository_url=quay.io/builder3/builder3", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -184,8 +168,7 @@ { "type": "container", "name": "quay.io/builder4/builder4", - "purl": "pkg:oci/builder4@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944" - "?repository_url=quay.io/builder4/builder4", + "purl": "pkg:oci/builder4@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944?repository_url=quay.io/builder4/builder4", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -198,16 +181,11 @@ # four builders and one parent image ( [ - "quay.io/builder1/builder1:aaaaaaa@sha256" - ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", - "quay.io/builder2/builder2:bbbbbbb@sha256" - ":2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", - "quay.io/builder3/builder3:ccccccc@sha256" - ":3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", - "quay.io/builder4/builder4:ddddddd@sha256" - ":4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944", - "registry.access.redhat.com/ubi8/ubi:latest@sha256" - ":627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", + "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", + "quay.io/builder4/builder4:ddddddd@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944", + "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", ], { "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", @@ -220,8 +198,7 @@ { "type": "container", "name": "quay.io/builder1/builder1", - "purl": "pkg:oci/builder1@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941" - "?repository_url=quay.io/builder1/builder1", + "purl": "pkg:oci/builder1@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/builder1/builder1", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -232,8 +209,7 @@ { "type": "container", "name": "quay.io/builder2/builder2", - "purl": "pkg:oci/builder2@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942" - "?repository_url=quay.io/builder2/builder2", + "purl": "pkg:oci/builder2@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942?repository_url=quay.io/builder2/builder2", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -244,8 +220,7 @@ { "type": "container", "name": "quay.io/builder3/builder3", - "purl": "pkg:oci/builder3@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943" - "?repository_url=quay.io/builder3/builder3", + "purl": "pkg:oci/builder3@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943?repository_url=quay.io/builder3/builder3", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -256,8 +231,7 @@ { "type": "container", "name": "quay.io/builder4/builder4", - "purl": "pkg:oci/builder4@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944" - "?repository_url=quay.io/builder4/builder4", + "purl": "pkg:oci/builder4@sha256:4f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420944?repository_url=quay.io/builder4/builder4", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -268,8 +242,7 @@ { "type": "container", "name": "registry.access.redhat.com/ubi8/ubi", - "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" - "?repository_url=registry.access.redhat.com/ubi8/ubi", + "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", "properties": [{"name": "konflux:container:is_base_image", "value": "true"}], }, ], @@ -277,18 +250,12 @@ # 3 builders and one final base image. builder 1 is reused three times, resulting in multiple properties ( [ - "quay.io/builder1/builder1:aaaaaaa@sha256" - ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", - "quay.io/builder2/builder2:bbbbbbb@sha256" - ":2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", - "quay.io/builder1/builder1:aaaaaaa@sha256" - ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", - "quay.io/builder3/builder3:ccccccc@sha256" - ":3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", - "quay.io/builder1/builder1:aaaaaaa@sha256" - ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", - "registry.access.redhat.com/ubi8/ubi:latest@sha256" - ":627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", ], { "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", @@ -301,8 +268,7 @@ { "type": "container", "name": "quay.io/builder1/builder1", - "purl": "pkg:oci/builder1@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941" - "?repository_url=quay.io/builder1/builder1", + "purl": "pkg:oci/builder1@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/builder1/builder1", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -321,8 +287,7 @@ { "type": "container", "name": "quay.io/builder2/builder2", - "purl": "pkg:oci/builder2@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942" - "?repository_url=quay.io/builder2/builder2", + "purl": "pkg:oci/builder2@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942?repository_url=quay.io/builder2/builder2", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -333,8 +298,7 @@ { "type": "container", "name": "quay.io/builder3/builder3", - "purl": "pkg:oci/builder3@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943" - "?repository_url=quay.io/builder3/builder3", + "purl": "pkg:oci/builder3@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943?repository_url=quay.io/builder3/builder3", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -359,16 +323,11 @@ # 3 builders and final base image is scratch. builder 1 is reused three times, resulting in multiple properties ( [ - "quay.io/builder1/builder1:aaaaaaa@sha256" - ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", - "quay.io/builder2/builder2:bbbbbbb@sha256" - ":2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", - "quay.io/builder1/builder1:aaaaaaa@sha256" - ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", - "quay.io/builder3/builder3:ccccccc@sha256" - ":3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", - "quay.io/builder1/builder1:aaaaaaa@sha256" - ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder3/builder3:ccccccc@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943", + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", "scratch", ], { @@ -381,8 +340,7 @@ { "type": "container", "name": "quay.io/builder1/builder1", - "purl": "pkg:oci/builder1@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941" - "?repository_url=quay.io/builder1/builder1", + "purl": "pkg:oci/builder1@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/builder1/builder1", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -401,8 +359,7 @@ { "type": "container", "name": "quay.io/builder2/builder2", - "purl": "pkg:oci/builder2@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942" - "?repository_url=quay.io/builder2/builder2", + "purl": "pkg:oci/builder2@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942?repository_url=quay.io/builder2/builder2", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -413,8 +370,7 @@ { "type": "container", "name": "quay.io/builder3/builder3", - "purl": "pkg:oci/builder3@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943" - "?repository_url=quay.io/builder3/builder3", + "purl": "pkg:oci/builder3@sha256:3f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420943?repository_url=quay.io/builder3/builder3", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -427,12 +383,9 @@ # 2 builders and builder 1 is then reused as final base image, resulting in multiple properties ( [ - "quay.io/builder1/builder1:aaaaaaa@sha256" - ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", - "quay.io/builder2/builder2:bbbbbbb@sha256" - ":2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", - "quay.io/builder1/builder1:aaaaaaa@sha256" - ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", ], { "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", @@ -442,8 +395,7 @@ { "type": "container", "name": "quay.io/builder1/builder1", - "purl": "pkg:oci/builder1@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941" - "?repository_url=quay.io/builder1/builder1", + "purl": "pkg:oci/builder1@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/builder1/builder1", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -458,8 +410,7 @@ { "type": "container", "name": "quay.io/builder2/builder2", - "purl": "pkg:oci/builder2@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942" - "?repository_url=quay.io/builder2/builder2", + "purl": "pkg:oci/builder2@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942?repository_url=quay.io/builder2/builder2", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -472,20 +423,15 @@ # Two images, both reused and several oci-archives and from scratch layers ( [ - "quay.io/builder1/builder1:aaaaaaa@sha256" - ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", "scratch", - "quay.io/builder2/builder2:bbbbbbb@sha256" - ":2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", + "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", "scratch", - "quay.io/builder1/builder1:aaaaaaa@sha256" - ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", "oci-archive:export/out.ociarchive", - "quay.io/builder2/builder2:bbbbbbb@sha256" - ":2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", + "quay.io/builder2/builder2:bbbbbbb@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942", "oci-archive:export/out.ociarchive", - "quay.io/builder1/builder1:aaaaaaa@sha256" - ":1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", ], { "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941": "quay.io/builder1/builder1:aaaaaaa@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", @@ -495,8 +441,7 @@ { "type": "container", "name": "quay.io/builder1/builder1", - "purl": "pkg:oci/builder1@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941" - "?repository_url=quay.io/builder1/builder1", + "purl": "pkg:oci/builder1@sha256:1f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/builder1/builder1", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -515,8 +460,7 @@ { "type": "container", "name": "quay.io/builder2/builder2", - "purl": "pkg:oci/builder2@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942" - "?repository_url=quay.io/builder2/builder2", + "purl": "pkg:oci/builder2@sha256:2f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420942?repository_url=quay.io/builder2/builder2", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -543,9 +487,7 @@ { "type": "container", "name": "quay.io/mkosiarc_rhtap/single-container-app", - "purl": "pkg:oci/single-container-app@sha256" - ":8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io" - "/mkosiarc_rhtap/single-container-app", + "purl": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -603,12 +545,8 @@ def test_main_input_sbom_does_not_contain_formulation(tmp_path, mocker): """ ) base_images_digests_raw_file.write_text( - "quay.io/mkosiarc_rhtap/single-container-app:f2566ab " - "quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256" - ":8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941\n" - "registry.access.redhat.com/ubi8/ubi:latest " - "registry.access.redhat.com/ubi8/ubi" - ":latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941\n" + "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" ) # mock the parsed args, to avoid testing parse_args function @@ -627,9 +565,7 @@ def test_main_input_sbom_does_not_contain_formulation(tmp_path, mocker): { "type": "container", "name": "quay.io/mkosiarc_rhtap/single-container-app", - "purl": "pkg:oci/single-container-app@sha256" - ":8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url" - "=quay.io/mkosiarc_rhtap/single-container-app", + "purl": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -640,8 +576,7 @@ def test_main_input_sbom_does_not_contain_formulation(tmp_path, mocker): { "type": "container", "name": "registry.access.redhat.com/ubi8/ubi", - "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" - "?repository_url=registry.access.redhat.com/ubi8/ubi", + "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", "properties": [ { "name": "konflux:container:is_base_image", @@ -701,12 +636,8 @@ def test_main_input_sbom_does_not_contain_formulation_and_base_image_from_scratc """ ) base_images_digests_raw_file.write_text( - "quay.io/mkosiarc_rhtap/single-container-app:f2566ab " - "quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256" - ":8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941\n" - "registry.access.redhat.com/ubi8/ubi:latest " - "registry.access.redhat.com/ubi8/ubi" - ":latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941\n" + "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" ) # mock the parsed args, to avoid testing parse_args function @@ -725,9 +656,7 @@ def test_main_input_sbom_does_not_contain_formulation_and_base_image_from_scratc { "type": "container", "name": "quay.io/mkosiarc_rhtap/single-container-app", - "purl": "pkg:oci/single-container-app@sha256" - ":8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url" - "=quay.io/mkosiarc_rhtap/single-container-app", + "purl": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -738,8 +667,7 @@ def test_main_input_sbom_does_not_contain_formulation_and_base_image_from_scratc { "type": "container", "name": "registry.access.redhat.com/ubi8/ubi", - "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" - "?repository_url=registry.access.redhat.com/ubi8/ubi", + "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -813,12 +741,8 @@ def test_main_input_sbom_contains_formulation(tmp_path, mocker): """ ) base_images_digests_raw_file.write_text( - "quay.io/mkosiarc_rhtap/single-container-app:f2566ab " - "quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256" - ":8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941\n" - "registry.access.redhat.com/ubi8/ubi:latest " - "registry.access.redhat.com/ubi8/ubi" - ":latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941\n" + "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" ) # mock the parsed args, to avoid testing parse_args function @@ -835,9 +759,7 @@ def test_main_input_sbom_contains_formulation(tmp_path, mocker): { "type": "container", "name": "quay.io/mkosiarc_rhtap/single-container-app", - "purl": "pkg:oci/single-container-app@sha256" - ":8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io" - "/mkosiarc_rhtap/single-container-app", + "purl": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -848,8 +770,7 @@ def test_main_input_sbom_contains_formulation(tmp_path, mocker): { "type": "container", "name": "registry.access.redhat.com/ubi8/ubi", - "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" - "?repository_url=registry.access.redhat.com/ubi8/ubi", + "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", "properties": [ { "name": "konflux:container:is_builder_image:for_stage", @@ -872,8 +793,7 @@ def test_main_input_sbom_contains_formulation(tmp_path, mocker): [ # basic example ( - "registry.access.redhat.com/ubi8/ubi:latest@sha256" - ":627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + "registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", ParsedImage( repository="registry.access.redhat.com/ubi8/ubi", digest="sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", @@ -882,8 +802,7 @@ def test_main_input_sbom_contains_formulation(tmp_path, mocker): ), # missing tag ( - "registry.access.redhat.com/ubi8/ubi:@sha256" - ":627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + "registry.access.redhat.com/ubi8/ubi:@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", ParsedImage( repository="registry.access.redhat.com/ubi8/ubi", digest="sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", @@ -892,8 +811,7 @@ def test_main_input_sbom_contains_formulation(tmp_path, mocker): ), # registry with port ( - "some_registry_with_port:5000/ubi8/ubi:latest@sha256" - ":627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + "some_registry_with_port:5000/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", ParsedImage( repository="some_registry_with_port:5000/ubi8/ubi", digest="sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", @@ -902,8 +820,7 @@ def test_main_input_sbom_contains_formulation(tmp_path, mocker): ), # multiple path components ( - "quay.io/redhat-user-workloads/rh-acs-tenant/acs/collector:358b6cfb019e436d1fa61a09fcca04e081e1c993" - "@sha256:8e5d62b32a5bb6d73ca7f54941f00ee8807563ddcb424660894dea85ed1f109d", + "quay.io/redhat-user-workloads/rh-acs-tenant/acs/collector:358b6cfb019e436d1fa61a09fcca04e081e1c993@sha256:8e5d62b32a5bb6d73ca7f54941f00ee8807563ddcb424660894dea85ed1f109d", ParsedImage( repository="quay.io/redhat-user-workloads/rh-acs-tenant/acs/collector", digest="sha256:8e5d62b32a5bb6d73ca7f54941f00ee8807563ddcb424660894dea85ed1f109d", From 1ed445b2d70b1ef39326146aa09f43789ef32803 Mon Sep 17 00:00:00 2001 From: Adam Cmiel Date: Mon, 9 Dec 2024 10:37:19 +0100 Subject: [PATCH 08/11] test_base_images_sbom: drop 'project_name' attr It's not a valid part of CycloneDX. Remove it from the example data. Signed-off-by: Adam Cmiel --- .../app/test_base_images_sbom_script.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py index c4fc36a..77570cf 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py @@ -519,7 +519,6 @@ def test_main_input_sbom_does_not_contain_formulation(tmp_path, mocker): sbom_file.write_text( """{ "bomFormat": "CycloneDX", - "project_name": "MyProject", "version": "1.0", "components": [] }""" @@ -605,7 +604,6 @@ def test_main_input_sbom_does_not_contain_formulation_and_base_image_from_scratc sbom_file.write_text( """{ "bomFormat": "CycloneDX", - "project_name": "MyProject", "version": "1.0", "components": [] }""" @@ -697,7 +695,6 @@ def test_main_input_sbom_contains_formulation(tmp_path, mocker): """ { "bomFormat": "CycloneDX", - "project_name": "MyProject", "version": "1.0", "components": [], "formulation": [ From 24147dfd3831cf024ba8592ccfe9a1ee18de6212 Mon Sep 17 00:00:00 2001 From: Adam Cmiel Date: Mon, 9 Dec 2024 11:17:28 +0100 Subject: [PATCH 09/11] test_base_images_sbom: de-duplicate tests Consolidate the test_main_* functions into a single parameterized test. Completely equivalent to the previous tests, just with less duplication. Signed-off-by: Adam Cmiel --- .../app/test_base_images_sbom_script.py | 450 ++++++++---------- 1 file changed, 204 insertions(+), 246 deletions(-) diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py index 77570cf..922a4cc 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py @@ -1,14 +1,17 @@ -import pytest import json - +from pathlib import Path +from typing import Any from unittest.mock import MagicMock +import pytest +from pytest_mock import MockerFixture + from base_images_sbom_script import ( - get_base_images_sbom_components, + ParsedImage, get_base_images_from_dockerfile, + get_base_images_sbom_components, main, parse_image_reference_to_parts, - ParsedImage, ) @@ -510,279 +513,234 @@ def test_get_base_images_sbom_components(base_images, base_images_digests, expec assert result == expected_result -def test_main_input_sbom_does_not_contain_formulation(tmp_path, mocker): - sbom_file = tmp_path / "sbom.json" - parsed_dockerfile = tmp_path / "parsed_dockerfile.json" - base_images_digests_raw_file = tmp_path / "base_images_digests.txt" - - # minimal input sbom file - sbom_file.write_text( - """{ - "bomFormat": "CycloneDX", - "version": "1.0", - "components": [] - }""" - ) - - # one builder images and one base image - parsed_dockerfile.write_text( - """ - { - "Stages": [ - { - "From": { - "Image": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab" - } - }, - { - "From": { - "Image": "registry.access.redhat.com/ubi8/ubi:latest" +@pytest.mark.parametrize( + ["input_sbom", "parsed_dockerfile", "base_images_digests_lines", "expect_sbom"], + [ + pytest.param( + # minimal CycloneDX SBOM + { + "bomFormat": "CycloneDX", + "version": "1.0", + "components": [], + }, + # one builder images and one base image + { + "Stages": [ + {"From": {"Image": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab"}}, + {"From": {"Image": "registry.access.redhat.com/ubi8/ubi:latest"}}, + ] + }, + # base image digests + [ + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + ], + # expected output + { + "bomFormat": "CycloneDX", + "version": "1.0", + "components": [], + "formulation": [ + { + "components": [ + { + "type": "container", + "name": "quay.io/mkosiarc_rhtap/single-container-app", + "purl": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", + "properties": [ + { + "name": "konflux:container:is_builder_image:for_stage", + "value": "0", + } + ], + }, + { + "type": "container", + "name": "registry.access.redhat.com/ubi8/ubi", + "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", + "properties": [ + { + "name": "konflux:container:is_base_image", + "value": "true", + } + ], + }, + ] } - } - ] - } - """ - ) - base_images_digests_raw_file.write_text( - "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941\n" - "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" - ) - - # mock the parsed args, to avoid testing parse_args function - mock_args = MagicMock() - mock_args.sbom = sbom_file - mock_args.parsed_dockerfile = parsed_dockerfile - mock_args.base_images_digests = base_images_digests_raw_file - mocker.patch("base_images_sbom_script.parse_args", return_value=mock_args) - - main() - - expected_output = { - "formulation": [ + ], + }, + id="cyclonedx-no-formulation", + ), + pytest.param( + # minimal CycloneDX SBOM + { + "bomFormat": "CycloneDX", + "version": "1.0", + "components": [], + }, + # two builder images, base from scratch + { + "Stages": [ + {"From": {"Image": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab"}}, + {"From": {"Image": "registry.access.redhat.com/ubi8/ubi:latest"}}, + {"From": {"Scratch": True}}, + ] + }, + # base image digests + [ + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + ], + # expected output { - "components": [ + "bomFormat": "CycloneDX", + "version": "1.0", + "components": [], + "formulation": [ { - "type": "container", - "name": "quay.io/mkosiarc_rhtap/single-container-app", - "purl": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", - "properties": [ + "components": [ { - "name": "konflux:container:is_builder_image:for_stage", - "value": "0", - } - ], - }, + "type": "container", + "name": "quay.io/mkosiarc_rhtap/single-container-app", + "purl": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", + "properties": [ + { + "name": "konflux:container:is_builder_image:for_stage", + "value": "0", + } + ], + }, + { + "type": "container", + "name": "registry.access.redhat.com/ubi8/ubi", + "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", + # Note: the property is "is_builder_image", not "is_base_image". + # There is no base image, the base is from scratch. + "properties": [ + { + "name": "konflux:container:is_builder_image:for_stage", + "value": "1", + } + ], + }, + ] + } + ], + }, + id="cyclonedx-no-formulation-base-from-scratch", + ), + pytest.param( + # CycloneDX SBOM with .formulation + { + "bomFormat": "CycloneDX", + "version": "1.0", + "components": [], + "formulation": [ { - "type": "container", - "name": "registry.access.redhat.com/ubi8/ubi", - "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", - "properties": [ + "components": [ { - "name": "konflux:container:is_base_image", - "value": "true", + "type": "library", + "name": "fresh", + "version": "0.5.2", + "purl": "pkg:npm/fresh@0.5.2", } - ], - }, - ] - } - ] - } - - with sbom_file.open("r") as f: - sbom = json.load(f) - - assert "formulation" in sbom - assert expected_output["formulation"] == sbom["formulation"] - - -def test_main_input_sbom_does_not_contain_formulation_and_base_image_from_scratch(tmp_path, mocker): - sbom_file = tmp_path / "sbom.json" - parsed_dockerfile = tmp_path / "parsed_dockerfile.json" - base_images_digests_raw_file = tmp_path / "base_images_digests.txt" - - # minimal input sbom file - sbom_file.write_text( - """{ - "bomFormat": "CycloneDX", - "version": "1.0", - "components": [] - }""" - ) - - # two builder images and the last one is from scratch - parsed_dockerfile.write_text( - """ - { - "Stages": [ - { - "From": { - "Image": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab" + ] } - }, - { - "From": { - "Image": "registry.access.redhat.com/ubi8/ubi:latest" - } - }, - { - "From": { - "Scratch": true - } - } - ] - } - """ - ) - base_images_digests_raw_file.write_text( - "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941\n" - "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" - ) - - # mock the parsed args, to avoid testing parse_args function - mock_args = MagicMock() - mock_args.sbom = sbom_file - mock_args.parsed_dockerfile = parsed_dockerfile - mock_args.base_images_digests = base_images_digests_raw_file - mocker.patch("base_images_sbom_script.parse_args", return_value=mock_args) - - main() - - expected_output = { - "formulation": [ + ], + }, + # two builder images, base from scratch + { + "Stages": [ + {"From": {"Image": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab"}}, + {"From": {"Image": "registry.access.redhat.com/ubi8/ubi:latest"}}, + {"From": {"Scratch": True}}, + ] + }, + # base image digests + [ + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + ], + # expected output { - "components": [ + "bomFormat": "CycloneDX", + "version": "1.0", + "components": [], + "formulation": [ { - "type": "container", - "name": "quay.io/mkosiarc_rhtap/single-container-app", - "purl": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", - "properties": [ + "components": [ { - "name": "konflux:container:is_builder_image:for_stage", - "value": "0", + "type": "library", + "name": "fresh", + "version": "0.5.2", + "purl": "pkg:npm/fresh@0.5.2", } - ], + ] }, + # The new formulation is appended to the existing formulations { - "type": "container", - "name": "registry.access.redhat.com/ubi8/ubi", - "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", - "properties": [ + "components": [ { - "name": "konflux:container:is_builder_image:for_stage", - "value": "1", - } - ], + "type": "container", + "name": "quay.io/mkosiarc_rhtap/single-container-app", + "purl": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", + "properties": [ + { + "name": "konflux:container:is_builder_image:for_stage", + "value": "0", + } + ], + }, + { + "type": "container", + "name": "registry.access.redhat.com/ubi8/ubi", + "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", + # Note: the property is "is_builder_image", not "is_base_image". + # There is no base image, the base is from scratch. + "properties": [ + { + "name": "konflux:container:is_builder_image:for_stage", + "value": "1", + } + ], + }, + ] }, - ] - } - ] - } - - with sbom_file.open("r") as f: - sbom = json.load(f) - - assert "formulation" in sbom - assert expected_output["formulation"] == sbom["formulation"] - - -def test_main_input_sbom_contains_formulation(tmp_path, mocker): + ], + }, + id="cyclonedx-has-formulation-base-from-scratch", + ), + ], +) +def test_main( + input_sbom: dict[str, Any], + parsed_dockerfile: dict[str, Any], + base_images_digests_lines: list[str], + expect_sbom: dict[str, Any], + tmp_path: Path, + mocker: MockerFixture, +): sbom_file = tmp_path / "sbom.json" - parsed_dockerfile = tmp_path / "parsed_dockerfile.json" + parsed_dockerfile_file = tmp_path / "parsed_dockerfile.json" base_images_digests_raw_file = tmp_path / "base_images_digests.txt" - # minimal sbom with existing formulation that contains components item - sbom_file.write_text( - """ - { - "bomFormat": "CycloneDX", - "version": "1.0", - "components": [], - "formulation": [ - { - "components": [ - { - "type": "library", - "name": "fresh", - "version": "0.5.2", - "purl": "pkg:npm/fresh@0.5.2" - } - ] - } - ] - } - """ - ) - - # two builder images and the last one is from scratch - parsed_dockerfile.write_text( - """ - { - "Stages": [ - { - "From": { - "Image": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab" - } - }, - { - "From": { - "Image": "registry.access.redhat.com/ubi8/ubi:latest" - } - }, - { - "From": { - "Scratch": true - } - } - ] - } - """ - ) - base_images_digests_raw_file.write_text( - "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941\n" - "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac" - ) + sbom_file.write_text(json.dumps(input_sbom)) + parsed_dockerfile_file.write_text(json.dumps(parsed_dockerfile)) + base_images_digests_raw_file.write_text("\n".join(base_images_digests_lines)) # mock the parsed args, to avoid testing parse_args function mock_args = MagicMock() mock_args.sbom = sbom_file - mock_args.parsed_dockerfile = parsed_dockerfile + mock_args.parsed_dockerfile = parsed_dockerfile_file mock_args.base_images_digests = base_images_digests_raw_file mocker.patch("base_images_sbom_script.parse_args", return_value=mock_args) main() - expected_output = { - "components": [ - { - "type": "container", - "name": "quay.io/mkosiarc_rhtap/single-container-app", - "purl": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", - "properties": [ - { - "name": "konflux:container:is_builder_image:for_stage", - "value": "0", - } - ], - }, - { - "type": "container", - "name": "registry.access.redhat.com/ubi8/ubi", - "purl": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", - "properties": [ - { - "name": "konflux:container:is_builder_image:for_stage", - "value": "1", - } - ], - }, - ] - } - with sbom_file.open("r") as f: sbom = json.load(f) - # we are appending an item to the formulation key, so it should be at the end - assert expected_output == sbom["formulation"][-1] + assert sbom == expect_sbom @pytest.mark.parametrize( From 1bf21b1338e4a7d251f028291fbc04b58a8979dc Mon Sep 17 00:00:00 2001 From: Adam Cmiel Date: Mon, 9 Dec 2024 11:57:41 +0100 Subject: [PATCH 10/11] test_base_images_sbom: add SPDX tests Signed-off-by: Adam Cmiel --- .../app/base_images_sbom_script.py | 7 +- .../app/test_base_images_sbom_script.py | 478 +++++++++++++++++- 2 files changed, 478 insertions(+), 7 deletions(-) diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py index 3af7644..c607be6 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/base_images_sbom_script.py @@ -290,6 +290,11 @@ def detect_sbom_type(sbom: dict[str, Any]) -> Literal["cyclonedx", "spdx"]: raise ValueError("Unknown SBOM format") +def _datetime_utc_now() -> datetime.datetime: + # a mockable datetime.datetime.now (just for tests): + return datetime.datetime.now(datetime.UTC) + + def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Updates the sbom file with base images data based on the provided files" @@ -337,7 +342,7 @@ def main() -> None: if detect_sbom_type(sbom) == "cyclonedx": update_cyclonedx_sbom(sbom, base_images_sbom_components) else: - annotation_date = datetime.datetime.now(datetime.UTC) + annotation_date = _datetime_utc_now() base_images_spdx = [cdx_to_spdx(c, annotation_date) for c in base_images_sbom_components] update_spdx_sbom(sbom, base_images_spdx) diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py b/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py index 922a4cc..aa4ab08 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/test_base_images_sbom_script.py @@ -1,4 +1,5 @@ import json +import datetime from pathlib import Path from typing import Any from unittest.mock import MagicMock @@ -514,7 +515,7 @@ def test_get_base_images_sbom_components(base_images, base_images_digests, expec @pytest.mark.parametrize( - ["input_sbom", "parsed_dockerfile", "base_images_digests_lines", "expect_sbom"], + ["input_sbom", "parsed_dockerfile", "base_images_digests_lines", "expect_result"], [ pytest.param( # minimal CycloneDX SBOM @@ -710,13 +711,467 @@ def test_get_base_images_sbom_components(base_images, base_images_digests, expec }, id="cyclonedx-has-formulation-base-from-scratch", ), + pytest.param( + # Unknown SBOM format + {}, + # one builder images and one base image + { + "Stages": [ + {"From": {"Image": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab"}}, + {"From": {"Image": "registry.access.redhat.com/ubi8/ubi:latest"}}, + ] + }, + # base image digests + [ + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + ], + # expected output + ValueError(r"Unknown SBOM format"), + id="unknown-sbom-format", + ), + pytest.param( + # SPDX SBOM with no root package + { + "SPDXID": "SPDXRef-Document", + "spdxVersion": "SPDX-2.3", + "name": "MyProject", + "documentNamespace": "http://example.com/uid-1234", + }, + # one builder images and one base image + { + "Stages": [ + {"From": {"Image": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab"}}, + {"From": {"Image": "registry.access.redhat.com/ubi8/ubi:latest"}}, + ] + }, + # base image digests + [ + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + ], + # expected output + ValueError(r"Found 0 ROOTs: \[\]"), + id="spdx-missing-root", + ), + pytest.param( + # SPDX SBOM with too many roots + { + "SPDXID": "SPDXRef-Document", + "spdxVersion": "SPDX-2.3", + "name": "MyProject", + "documentNamespace": "http://example.com/uid-1234", + "packages": [ + { + "SPDXID": "SPDXRef-root1", + "name": "", + "downloadLocation": "NOASSERTION", + }, + { + "SPDXID": "SPDXRef-root2", + "name": "", + "downloadLocation": "NOASSERTION", + }, + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Document", + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-root1", + }, + { + "spdxElementId": "SPDXRef-Document", + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-root2", + }, + ], + }, + # one builder images and one base image + { + "Stages": [ + {"From": {"Image": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab"}}, + {"From": {"Image": "registry.access.redhat.com/ubi8/ubi:latest"}}, + ] + }, + # base image digests + [ + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + ], + # expected output + ValueError(r"Found 2 ROOTs: \['SPDXRef-root1', 'SPDXRef-root2'\]"), + id="spdx-too-many-roots", + ), + pytest.param( + # minimal valid SPDX SBOM + { + "SPDXID": "SPDXRef-Document", + "spdxVersion": "SPDX-2.3", + "name": "MyProject", + "documentNamespace": "http://example.com/uid-1234", + "packages": [ + { + "SPDXID": "SPDXRef-image-my-cool-image", + "name": "MyMainPackage", + "downloadLocation": "NOASSERTION", + }, + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Document", + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + ], + }, + # one builder images and one base image + { + "Stages": [ + {"From": {"Image": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab"}}, + {"From": {"Image": "registry.access.redhat.com/ubi8/ubi:latest"}}, + ] + }, + # base image digests + [ + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + ], + # expected output + { + "SPDXID": "SPDXRef-Document", + "spdxVersion": "SPDX-2.3", + "name": "MyProject", + "documentNamespace": "http://example.com/uid-1234", + "packages": [ + { + "SPDXID": "SPDXRef-image-my-cool-image", + "name": "MyMainPackage", + "downloadLocation": "NOASSERTION", + }, + { + "SPDXID": "SPDXRef-image-quay.io/mkosiarc_rhtap/single-container-app-9520a72cbb69edfca5cac88ea2a9e0e09142ec934952b9420d686e77765f002c", + "name": "quay.io/mkosiarc_rhtap/single-container-app", + "downloadLocation": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", + } + ], + "annotations": [ + { + "annotator": "Tool: konflux:jsonencoded", + "comment": '{"name":"konflux:container:is_builder_image:for_stage","value":"0"}', + "annotationDate": "2024-12-09T12:00:00Z", + "annotationType": "OTHER", + } + ], + }, + { + "SPDXID": "SPDXRef-image-registry.access.redhat.com/ubi8/ubi-0f22256f634f8205fbd9c438c387ccf2d4859250e04104571c93fdb89a62bae1", + "name": "registry.access.redhat.com/ubi8/ubi", + "downloadLocation": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", + } + ], + "annotations": [ + { + "annotator": "Tool: konflux:jsonencoded", + "comment": '{"name":"konflux:container:is_base_image","value":"true"}', + "annotationDate": "2024-12-09T12:00:00Z", + "annotationType": "OTHER", + } + ], + }, + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Document", + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + { + "spdxElementId": "SPDXRef-image-quay.io/mkosiarc_rhtap/single-container-app-9520a72cbb69edfca5cac88ea2a9e0e09142ec934952b9420d686e77765f002c", + "relationshipType": "BUILD_TOOL_OF", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + { + "spdxElementId": "SPDXRef-image-registry.access.redhat.com/ubi8/ubi-0f22256f634f8205fbd9c438c387ccf2d4859250e04104571c93fdb89a62bae1", + "relationshipType": "BUILD_TOOL_OF", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + ], + }, + id="spdx-minimal", + ), + pytest.param( + # minimal valid SPDX SBOM + { + "SPDXID": "SPDXRef-Document", + "spdxVersion": "SPDX-2.3", + "name": "MyProject", + "documentNamespace": "http://example.com/uid-1234", + "packages": [ + { + "SPDXID": "SPDXRef-image-my-cool-image", + "name": "MyMainPackage", + "downloadLocation": "NOASSERTION", + }, + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Document", + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + ], + }, + # two builder images, base from scratch + { + "Stages": [ + {"From": {"Image": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab"}}, + {"From": {"Image": "registry.access.redhat.com/ubi8/ubi:latest"}}, + {"From": {"Scratch": True}}, + ] + }, + # base image digests + [ + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + ], + # expected output + { + "SPDXID": "SPDXRef-Document", + "spdxVersion": "SPDX-2.3", + "name": "MyProject", + "documentNamespace": "http://example.com/uid-1234", + "packages": [ + { + "SPDXID": "SPDXRef-image-my-cool-image", + "name": "MyMainPackage", + "downloadLocation": "NOASSERTION", + }, + { + "SPDXID": "SPDXRef-image-quay.io/mkosiarc_rhtap/single-container-app-9520a72cbb69edfca5cac88ea2a9e0e09142ec934952b9420d686e77765f002c", + "name": "quay.io/mkosiarc_rhtap/single-container-app", + "downloadLocation": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", + } + ], + "annotations": [ + { + "annotator": "Tool: konflux:jsonencoded", + "comment": '{"name":"konflux:container:is_builder_image:for_stage","value":"0"}', + "annotationDate": "2024-12-09T12:00:00Z", + "annotationType": "OTHER", + } + ], + }, + { + "SPDXID": "SPDXRef-image-registry.access.redhat.com/ubi8/ubi-0f22256f634f8205fbd9c438c387ccf2d4859250e04104571c93fdb89a62bae1", + "name": "registry.access.redhat.com/ubi8/ubi", + "downloadLocation": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", + } + ], + "annotations": [ + { + "annotator": "Tool: konflux:jsonencoded", + "comment": '{"name":"konflux:container:is_builder_image:for_stage","value":"1"}', + "annotationDate": "2024-12-09T12:00:00Z", + "annotationType": "OTHER", + } + ], + }, + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Document", + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + { + "spdxElementId": "SPDXRef-image-quay.io/mkosiarc_rhtap/single-container-app-9520a72cbb69edfca5cac88ea2a9e0e09142ec934952b9420d686e77765f002c", + "relationshipType": "BUILD_TOOL_OF", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + { + "spdxElementId": "SPDXRef-image-registry.access.redhat.com/ubi8/ubi-0f22256f634f8205fbd9c438c387ccf2d4859250e04104571c93fdb89a62bae1", + "relationshipType": "BUILD_TOOL_OF", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + ], + }, + id="spdx-minimal-base-from-scratch", + ), + pytest.param( + # SPDX SBOM with some existing packages + { + "SPDXID": "SPDXRef-Document", + "spdxVersion": "SPDX-2.3", + "name": "MyProject", + "documentNamespace": "http://example.com/uid-1234", + "packages": [ + { + "SPDXID": "SPDXRef-image-my-cool-image", + "name": "MyMainPackage", + "downloadLocation": "NOASSERTION", + }, + { + "SPDXID": "SPDXRef-some-runtime-dep", + "name": "SomeDependency", + "downloadLocation": "NOASSERTION", + }, + { + "SPDXID": "SPDXRef-some-builddep", + "name": "BuildTool", + "downloadLocation": "NOASSERTION", + }, + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Document", + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + { + "spdxElementId": "SPDXRef-image-my-cool-image", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-some-runtime-dep", + }, + { + "spdxElementId": "SPDXRef-some-builddep", + "relationshipType": "BUILD_TOOL_OF", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + ], + }, + # two builder images, base from scratch + { + "Stages": [ + {"From": {"Image": "quay.io/mkosiarc_rhtap/single-container-app:f2566ab"}}, + {"From": {"Image": "registry.access.redhat.com/ubi8/ubi:latest"}}, + {"From": {"Scratch": True}}, + ] + }, + # base image digests + [ + "quay.io/mkosiarc_rhtap/single-container-app:f2566ab quay.io/mkosiarc_rhtap/single-container-app:f2566ab@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941", + "registry.access.redhat.com/ubi8/ubi:latest registry.access.redhat.com/ubi8/ubi:latest@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac", + ], + # expected output + { + "SPDXID": "SPDXRef-Document", + "spdxVersion": "SPDX-2.3", + "name": "MyProject", + "documentNamespace": "http://example.com/uid-1234", + "packages": [ + { + "SPDXID": "SPDXRef-image-my-cool-image", + "name": "MyMainPackage", + "downloadLocation": "NOASSERTION", + }, + { + "SPDXID": "SPDXRef-some-runtime-dep", + "name": "SomeDependency", + "downloadLocation": "NOASSERTION", + }, + { + "SPDXID": "SPDXRef-some-builddep", + "name": "BuildTool", + "downloadLocation": "NOASSERTION", + }, + { + "SPDXID": "SPDXRef-image-quay.io/mkosiarc_rhtap/single-container-app-9520a72cbb69edfca5cac88ea2a9e0e09142ec934952b9420d686e77765f002c", + "name": "quay.io/mkosiarc_rhtap/single-container-app", + "downloadLocation": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:oci/single-container-app@sha256:8f99627e843e931846855c5d899901bf093f5093e613a92745696a26b5420941?repository_url=quay.io/mkosiarc_rhtap/single-container-app", + } + ], + "annotations": [ + { + "annotator": "Tool: konflux:jsonencoded", + "comment": '{"name":"konflux:container:is_builder_image:for_stage","value":"0"}', + "annotationDate": "2024-12-09T12:00:00Z", + "annotationType": "OTHER", + } + ], + }, + { + "SPDXID": "SPDXRef-image-registry.access.redhat.com/ubi8/ubi-0f22256f634f8205fbd9c438c387ccf2d4859250e04104571c93fdb89a62bae1", + "name": "registry.access.redhat.com/ubi8/ubi", + "downloadLocation": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:oci/ubi@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac?repository_url=registry.access.redhat.com/ubi8/ubi", + } + ], + "annotations": [ + { + "annotator": "Tool: konflux:jsonencoded", + "comment": '{"name":"konflux:container:is_builder_image:for_stage","value":"1"}', + "annotationDate": "2024-12-09T12:00:00Z", + "annotationType": "OTHER", + } + ], + }, + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Document", + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + { + "spdxElementId": "SPDXRef-image-my-cool-image", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-some-runtime-dep", + }, + { + "spdxElementId": "SPDXRef-some-builddep", + "relationshipType": "BUILD_TOOL_OF", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + { + "spdxElementId": "SPDXRef-image-quay.io/mkosiarc_rhtap/single-container-app-9520a72cbb69edfca5cac88ea2a9e0e09142ec934952b9420d686e77765f002c", + "relationshipType": "BUILD_TOOL_OF", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + { + "spdxElementId": "SPDXRef-image-registry.access.redhat.com/ubi8/ubi-0f22256f634f8205fbd9c438c387ccf2d4859250e04104571c93fdb89a62bae1", + "relationshipType": "BUILD_TOOL_OF", + "relatedSpdxElement": "SPDXRef-image-my-cool-image", + }, + ], + }, + id="spdx-nonminimal-base-from-scratch", + ), ], ) def test_main( input_sbom: dict[str, Any], parsed_dockerfile: dict[str, Any], base_images_digests_lines: list[str], - expect_sbom: dict[str, Any], + expect_result: dict[str, Any] | Exception, tmp_path: Path, mocker: MockerFixture, ): @@ -735,12 +1190,23 @@ def test_main( mock_args.base_images_digests = base_images_digests_raw_file mocker.patch("base_images_sbom_script.parse_args", return_value=mock_args) - main() + # mock datetime.now + mocker.patch( + "base_images_sbom_script._datetime_utc_now", + return_value=datetime.datetime(2024, 12, 9, 12, 0, 0, tzinfo=datetime.UTC), + ) + + if not isinstance(expect_result, Exception): + main() + + with sbom_file.open("r") as f: + sbom = json.load(f) - with sbom_file.open("r") as f: - sbom = json.load(f) + assert sbom == expect_result - assert sbom == expect_sbom + else: + with pytest.raises(type(expect_result), match=str(expect_result)): + main() @pytest.mark.parametrize( From 19e010102ceaf61325138fabe075a19ec307a558 Mon Sep 17 00:00:00 2001 From: Adam Cmiel Date: Mon, 9 Dec 2024 13:59:35 +0100 Subject: [PATCH 11/11] sbom-utility-scripts: upgrade to python3.12 Previously, the container image used python3.9 while tests ran on python(unknown but > 3.9). Upgrade container to python3.12 and set tests to run on 3.12 explicitly. Signed-off-by: Adam Cmiel --- sbom-utility-scripts/Dockerfile | 2 +- sbom-utility-scripts/scripts/add-image-reference-script/tox.ini | 1 + .../scripts/base-images-sbom-script/app/tox.ini | 1 + sbom-utility-scripts/scripts/index-image-sbom-script/tox.ini | 1 + sbom-utility-scripts/scripts/merge-cachi2-sboms-script/tox.ini | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sbom-utility-scripts/Dockerfile b/sbom-utility-scripts/Dockerfile index e9e727a..2b47c6e 100644 --- a/sbom-utility-scripts/Dockerfile +++ b/sbom-utility-scripts/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.access.redhat.com/ubi9/python-39:1-197.1725907694@sha256:278ae38e8f28ccba3cb7cd542f684d739a84f771e418fc8018d07a522205b05c +FROM registry.access.redhat.com/ubi9/python-312:9.5@sha256:88ea2d10c741f169681102b46b16c66d20c94c3cc561edbb6444b0de3a1c81b3 WORKDIR /scripts diff --git a/sbom-utility-scripts/scripts/add-image-reference-script/tox.ini b/sbom-utility-scripts/scripts/add-image-reference-script/tox.ini index a0ff21a..0d6449e 100644 --- a/sbom-utility-scripts/scripts/add-image-reference-script/tox.ini +++ b/sbom-utility-scripts/scripts/add-image-reference-script/tox.ini @@ -2,6 +2,7 @@ env_list = flake8,black,test [testenv:test] +basepython = 3.12 deps = -r requirements-test.txt -r requirements.txt commands = pytest ./ \ diff --git a/sbom-utility-scripts/scripts/base-images-sbom-script/app/tox.ini b/sbom-utility-scripts/scripts/base-images-sbom-script/app/tox.ini index d4df57f..d1cc7ae 100644 --- a/sbom-utility-scripts/scripts/base-images-sbom-script/app/tox.ini +++ b/sbom-utility-scripts/scripts/base-images-sbom-script/app/tox.ini @@ -2,6 +2,7 @@ env_list = flake8,black,test [testenv:test] +basepython = 3.12 deps = -r requirements-test.txt -r requirements.txt commands = pytest test_base_images_sbom_script.py diff --git a/sbom-utility-scripts/scripts/index-image-sbom-script/tox.ini b/sbom-utility-scripts/scripts/index-image-sbom-script/tox.ini index 3a0a1b5..bb77f80 100644 --- a/sbom-utility-scripts/scripts/index-image-sbom-script/tox.ini +++ b/sbom-utility-scripts/scripts/index-image-sbom-script/tox.ini @@ -2,6 +2,7 @@ env_list = flake8,black,test [testenv:test] +basepython = 3.12 deps = -r requirements-test.txt -r requirements.txt commands = pytest -s -vv test_image_index_sbom_script.py diff --git a/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/tox.ini b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/tox.ini index db6ddad..0c7ec59 100644 --- a/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/tox.ini +++ b/sbom-utility-scripts/scripts/merge-cachi2-sboms-script/tox.ini @@ -2,6 +2,7 @@ env_list = flake8,black,test [testenv:test] +basepython = 3.12 deps = -r requirements-test.txt commands = pytest test_merge_cachi2_sboms.py