From 7b2ce7928f9343d9c45d4ee7ce4d938c5c882eb7 Mon Sep 17 00:00:00 2001 From: Martin Basti Date: Wed, 6 Nov 2024 20:30:16 +0100 Subject: [PATCH] cachi2: update add_image_content_manifest plugin Update add_image_content_manifest to process cachi2 results. ICM must be generated from cachi2 SBOM in this case. Signed-off-by: Martin Basti --- .../plugins/add_image_content_manifest.py | 13 ++++ .../test_add_image_content_manifest.py | 74 ++++++++++++++++++- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/atomic_reactor/plugins/add_image_content_manifest.py b/atomic_reactor/plugins/add_image_content_manifest.py index 5232dfca3..5b55c5731 100644 --- a/atomic_reactor/plugins/add_image_content_manifest.py +++ b/atomic_reactor/plugins/add_image_content_manifest.py @@ -16,8 +16,10 @@ from atomic_reactor.constants import (IMAGE_BUILD_INFO_DIR, INSPECT_ROOTFS, INSPECT_ROOTFS_LAYERS, + CACHI2_BUILD_DIR, PLUGIN_ADD_IMAGE_CONTENT_MANIFEST, PLUGIN_FETCH_MAVEN_KEY, + PLUGIN_CACHI2_POSTPROCESS, PLUGIN_RESOLVE_REMOTE_SOURCE) from atomic_reactor.config import get_cachito_session from atomic_reactor.dirs import BuildDir @@ -25,6 +27,7 @@ from atomic_reactor.util import (validate_with_schema, read_content_sets, map_to_user_params, allow_path_in_dockerignore) from atomic_reactor.utils.pnc import PNCUtil +from atomic_reactor.utils.cachi2 import convert_SBOM_to_ICM class AddImageContentManifestPlugin(Plugin): @@ -100,6 +103,8 @@ def __init__(self, workflow, destdir=IMAGE_BUILD_INFO_DIR): remote_source_results = wf_data.plugins_results.get(PLUGIN_RESOLVE_REMOTE_SOURCE) or [] self.remote_source_ids = [remote_source['id'] for remote_source in remote_source_results] + self.cachi2_remote_sources = wf_data.plugins_results.get(PLUGIN_CACHI2_POSTPROCESS) or [] + fetch_maven_results = wf_data.plugins_results.get(PLUGIN_FETCH_MAVEN_KEY) or {} self.pnc_artifact_ids = fetch_maven_results.get('pnc_artifact_ids') or [] @@ -130,6 +135,12 @@ def layer_index(self) -> int: return len(inspect[INSPECT_ROOTFS][INSPECT_ROOTFS_LAYERS]) + def _get_cachi2_icm(self) -> dict: + global_sbom_path = self.workflow.build_dir.path/CACHI2_BUILD_DIR/"bom.json" + with open(global_sbom_path, "r") as f: + sbom = json.load(f) + return convert_SBOM_to_ICM(sbom) + @functools.cached_property def _icm_base(self) -> dict: """Create the platform-independent skeleton of the ICM document. @@ -140,6 +151,8 @@ def _icm_base(self) -> dict: if self.remote_source_ids: icm = self.cachito_session.get_image_content_manifest(self.remote_source_ids) + elif self.cachi2_remote_sources: # we doesn't support Cachito and Cachi2 together + icm = self._get_cachi2_icm() if self.pnc_artifact_ids: purl_specs = self.pnc_util.get_artifact_purl_specs(self.pnc_artifact_ids) diff --git a/tests/plugins/test_add_image_content_manifest.py b/tests/plugins/test_add_image_content_manifest.py index d92e1a849..a95804d29 100644 --- a/tests/plugins/test_add_image_content_manifest.py +++ b/tests/plugins/test_add_image_content_manifest.py @@ -21,8 +21,10 @@ from tests.utils.test_cachito import CACHITO_URL, CACHITO_REQUEST_ID from atomic_reactor.constants import ( + CACHI2_BUILD_DIR, INSPECT_ROOTFS, INSPECT_ROOTFS_LAYERS, + PLUGIN_CACHI2_POSTPROCESS, PLUGIN_FETCH_MAVEN_KEY, PLUGIN_RESOLVE_REMOTE_SOURCE, DOCKERIGNORE, @@ -39,6 +41,28 @@ } CACHITO_ICM_URL = '{}/api/v1/content-manifest?requests={}'.format(CACHITO_URL, CACHITO_REQUEST_ID) + +CACHI2_SBOM = { + "bomFormat": "CycloneDX", + "components": [{ + "name": "retrodep", + "purl": "pkg:golang/github.com%2Frelease-engineering%2Fretrodep%2Fv2@v2.0.2", + "properties": [{ + "name": "cachi2:found_by", + "value": "cachi2", + }], + "type": "library", + }], + "metadata": { + "tools": [{ + "vendor": "red hat", + "name": "cachi2" + }] + }, + "specVersion": "1.4", + "version": 1 +} + PNC_ARTIFACT = { 'id': 1234, 'publicUrl': 'http://test.com/artifact.jar', @@ -88,6 +112,28 @@ } ] } + +CACHI2_ICM_DICT = { + 'metadata': { + 'icm_version': 1, + 'icm_spec': ( + 'https://raw.githubusercontent.com/containerbuildsystem/atomic-reactor/' + 'f4abcfdaf8247a6b074f94fa84f3846f82d781c6/atomic_reactor/schemas/' + 'content_manifest.json'), + 'image_layer_index': 1, + }, + 'content_sets': [], + 'image_contents': [ + { + 'purl': + 'pkg:golang/github.com%2Frelease-engineering%2Fretrodep%2Fv2@v2.0.2', + }, + { + 'purl': PNC_ARTIFACT['purl'], + } + ] +} + ICM_JSON = dedent( '''\ { @@ -157,7 +203,8 @@ def mock_get_icm(requests_mock): def mock_env(workflow, df_content, base_layers=0, remote_sources=None, - r_c_m_override=None, pnc_artifacts=True, dockerignore=False): + r_c_m_override=None, pnc_artifacts=True, dockerignore=False, + cachi2_sbom=None): if base_layers > 0: inspection_data = { @@ -206,6 +253,19 @@ def mock_env(workflow, df_content, base_layers=0, remote_sources=None, platforms = list(CONTENT_SETS.keys()) workflow.build_dir.init_build_dirs(platforms, workflow.source) + if cachi2_sbom: + env.set_plugin_result( + PLUGIN_CACHI2_POSTPROCESS, + {"plugin": "did run, real value doesn't matter"} + ) + + # save cachi2 SBOM which is source for ICM + path = workflow.build_dir.path/CACHI2_BUILD_DIR/"bom.json" + path.parent.mkdir() + with open(path, "w") as f: + json.dump(cachi2_sbom, f) + f.flush() + return env.create_runner() @@ -239,6 +299,7 @@ def check_in_build_dir(build_dir): @pytest.mark.parametrize('manifest_file_exists', [True, False]) @pytest.mark.parametrize('content_sets', [True, False]) +@pytest.mark.parametrize('cachi2', [True, False]) @pytest.mark.parametrize( ('df_content, expected_df, base_layers, manifest_file'), [ ( @@ -288,13 +349,18 @@ def check_in_build_dir(build_dir): ), ]) def test_add_image_content_manifest(workflow, requests_mock, - manifest_file_exists, content_sets, + manifest_file_exists, content_sets, cachi2, df_content, expected_df, base_layers, manifest_file, ): mock_get_icm(requests_mock) mock_content_sets_config(workflow.source.path, empty=(not content_sets)) - runner = mock_env(workflow, df_content, base_layers, remote_sources=REMOTE_SOURCES) + if cachi2: + runner_opts = {"cachi2_sbom": CACHI2_SBOM} + else: + runner_opts = {"remote_sources": REMOTE_SOURCES} + + runner = mock_env(workflow, df_content, base_layers, **runner_opts) if manifest_file_exists: workflow.build_dir.any_platform.path.joinpath(manifest_file).touch() @@ -304,7 +370,7 @@ def test_add_image_content_manifest(workflow, requests_mock, runner.run() return - expected_output = deepcopy(ICM_DICT) + expected_output = deepcopy(CACHI2_ICM_DICT if cachi2 else ICM_DICT) expected_output['metadata']['image_layer_index'] = base_layers if base_layers else 0 runner.run()