99from pathlib import Path
1010from typing import Any
1111
12+ import yaml
1213from cyclonedx .exception import CycloneDxException
1314from spdx_tools .spdx .jsonschema .document_converter import DocumentConverter
1415from spdx_tools .spdx .model .document import Document
2021from mobster .cmd .generate .base import GenerateCommandWithOutputTypeSelector
2122from mobster .cmd .generate .oci_image .add_image import extend_sbom_with_image_reference
2223from mobster .cmd .generate .oci_image .base_images_dockerfile import (
23- extend_sbom_with_base_images_from_dockerfile ,
24- get_base_images_refs_from_dockerfile ,
24+ extend_sbom_with_base_images ,
2525 get_digest_for_image_ref ,
26- get_image_objects_from_file ,
2726)
27+ from mobster .cmd .generate .oci_image .buildprobe import SBOMMetadata
2828from mobster .cmd .generate .oci_image .contextual_sbom .builder import (
2929 BuilderContextualizationError ,
3030 BuilderPkgMetadata ,
4848from mobster .image import Image
4949from mobster .log import log_elapsed
5050from mobster .sbom .merge import merge_sboms
51- from mobster .utils import identify_arch , load_sbom_from_json
51+ from mobster .utils import load_sbom_from_json
5252
5353logging .captureWarnings (True ) # CDX validation uses `warn()`
5454LOGGER = logging .getLogger (__name__ )
@@ -99,6 +99,15 @@ async def _load_and_filter_hermeto_sbom(self) -> dict[str, Any]:
9999 arch = self .cli_args .arch or mobster .utils .identify_arch ()
100100 return filter_hermeto_sbom_by_arch (hermeto_sbom , arch )
101101
102+ def _load_metadata (self ) -> None :
103+ """
104+ Load a metadata file from the --metadata-path argument into
105+ self._metadata.
106+ """
107+ with open (self .cli_args .metadata_path , encoding = "utf-8" ) as metadata_file :
108+ raw_metadata = yaml .safe_load (metadata_file )
109+ self ._metadata = SBOMMetadata .from_dict (raw_metadata )
110+
102111 async def _handle_bom_inputs (
103112 self ,
104113 ) -> dict [str , Any ]:
@@ -113,13 +122,19 @@ async def _handle_bom_inputs(
113122 self .cli_args .from_hermeto is None
114123 and self .cli_args .from_syft is None
115124 and self .cli_args .image_pullspec is None
125+ and self .cli_args .metadata_path is None
116126 ):
117127 raise ArgumentError (
118128 None ,
119- "At least one of --from-syft, --from-hermeto or --image-pullspec"
120- " must be provided" ,
129+ "At least one of --from-syft, --from-hermeto, --image-pullspec, "
130+ "or --metadata-path must be provided" ,
121131 )
122132
133+ if self .cli_args .metadata_path is not None :
134+ self ._load_metadata ()
135+ # if we don't have an sbom provided to us, use syft to generate it
136+ if self .cli_args .from_syft is None and self .cli_args .from_hermeto is None :
137+ return await syft .scan_image (self ._metadata .image .pullspec )
123138 if self .cli_args .from_syft is not None :
124139 # Merging Syft & Hermeto SBOMs
125140 if len (self .cli_args .from_syft ) > 1 or self .cli_args .from_hermeto :
@@ -228,7 +243,7 @@ async def _assess_and_dispatch_contextual_workflow(
228243 (non-modified) SBOM is furtherly processed by mobster.
229244 Args:
230245 component_sbom_doc: The component SBOM created for this image.
231- base_images_refs: List of references from the parsed Dockerfile .
246+ base_images_refs: List of references from the build .
232247 image_arch: CPU architecture of this image.
233248
234249 Returns:
@@ -264,11 +279,12 @@ async def execute(self) -> Any:
264279 """
265280 LOGGER .debug ("Generating SBOM document for OCI image" )
266281
282+ # Get/merge the raw SBOM
267283 merged_sbom_dict = await self ._handle_bom_inputs ()
268284 sbom : Document | CycloneDX1BomWrapper
269- image_arch = identify_arch ()
285+ image_arch = self . cli_args . arch or mobster . utils . identify_arch ()
270286
271- # Parsing into objects
287+ # Parse into objects
272288 if merged_sbom_dict .get ("bomFormat" ) == "CycloneDX" :
273289 if self .cli_args .contextualize :
274290 raise ArgumentError (
@@ -280,9 +296,32 @@ async def execute(self) -> Any:
280296 else :
281297 raise ValueError ("Unknown SBOM Format!" )
282298
283- # Extending with image reference
284- if self .cli_args .image_pullspec :
285- image_arch = self .cli_args .arch or mobster .utils .identify_arch ()
299+ base_images_refs = []
300+ base_images_map : dict [str , Image ] = {}
301+
302+ # Extend with image reference
303+ if self .cli_args .metadata_path :
304+ image = Image .from_image_index_url_and_digest (
305+ self ._metadata .image .pullspec ,
306+ self ._metadata .image .digest ,
307+ arch = image_arch ,
308+ )
309+ await extend_sbom_with_image_reference (sbom , image , False )
310+ for base_image_data in self ._metadata .base_images :
311+ base_image = Image .from_image_index_url_and_digest (
312+ base_image_data .pullspec ,
313+ base_image_data .digest ,
314+ )
315+ base_images_refs .append (base_image_data .pullspec )
316+ base_images_map [base_image_data .pullspec ] = base_image
317+ await extend_sbom_with_base_images (sbom , base_images_refs , base_images_map )
318+ for extra_image_data in self ._metadata .extra_images :
319+ extra_image = Image .from_image_index_url_and_digest (
320+ extra_image_data .pullspec ,
321+ extra_image_data .digest ,
322+ )
323+ await extend_sbom_with_image_reference (sbom , extra_image , True )
324+ elif self .cli_args .image_pullspec :
286325 if not self .cli_args .image_digest :
287326 LOGGER .info (
288327 "Provided pullspec but not digest."
@@ -308,37 +347,6 @@ async def execute(self) -> Any:
308347 "Provided image digest but no pullspec. The digest value is ignored."
309348 )
310349
311- base_images_refs = []
312- base_images_map : dict [str , Image ] = {}
313-
314- # Extending with base images references from a dockerfile
315- if self .cli_args .parsed_dockerfile_path :
316- with open (
317- self .cli_args .parsed_dockerfile_path , encoding = "utf-8"
318- ) as parsed_dockerfile_io :
319- parsed_dockerfile = json .load (parsed_dockerfile_io )
320-
321- base_images_refs = await get_base_images_refs_from_dockerfile (
322- parsed_dockerfile , self .cli_args .dockerfile_target
323- )
324-
325- if self .cli_args .base_image_digest_file :
326- LOGGER .debug (
327- "Supplied pre-parsed image digest file, will operate offline."
328- )
329- base_images_map = await get_image_objects_from_file (
330- self .cli_args .base_image_digest_file
331- )
332- await extend_sbom_with_base_images_from_dockerfile (
333- sbom , base_images_refs , base_images_map
334- )
335-
336- # Extending with additional base images
337- for image_ref in self .cli_args .additional_base_image :
338- image_object = Image .from_oci_artifact_reference (image_ref )
339- await extend_sbom_with_image_reference (
340- sbom , image_object , is_builder_image = True
341- )
342350 with log_elapsed ("Contextual workflow" , logging .INFO ):
343351 contextual_sbom = await self ._assess_and_dispatch_contextual_workflow (
344352 sbom , base_images_refs , base_images_map , image_arch
0 commit comments