@@ -148,6 +148,74 @@ def parse_json_string(json_string):
148
148
raise_exception (f"Error parsing JSON: { e } " )
149
149
150
150
151
+ import subprocess
152
+ import time
153
+ import sys
154
+ import os
155
+
156
+ def download_sbom_with_retry (platform_arg : str , image_url : str , sbom : str ):
157
+ """
158
+ Downloads an SBOM with retry logic
159
+
160
+ Args:
161
+ platform_arg: The platform argument for the cosign command.
162
+ image_url: The URL of the image to download the SBOM for.
163
+ sbom: The path to the file where the SBOM should be saved.
164
+ """
165
+ # TODO improve by ./cosign tree image and check for the "SBOMs" string - if present, the sboms is there, if missing it's not there
166
+ max_try = 5
167
+ wait_sec = 2
168
+ status = - 1
169
+ err_file = "err" # Temporary file to store stderr
170
+ command_bin = "cosign"
171
+
172
+ for run in range (1 , max_try + 1 ):
173
+ status = 0
174
+ command = [
175
+ command_bin ,
176
+ "download" ,
177
+ "sbom" ,
178
+ platform_arg ,
179
+ image_url ,
180
+ ]
181
+
182
+ try :
183
+ with open (sbom , "w" ) as outfile , open (err_file , "w" ) as errfile :
184
+ result = subprocess .run (
185
+ command ,
186
+ stdout = outfile ,
187
+ stderr = errfile ,
188
+ check = False # Don't raise an exception on non-zero exit code
189
+ )
190
+ status = result .returncode
191
+ except FileNotFoundError :
192
+ print (f"Error: The '{ command_bin } ' command was not found. Make sure it's in your PATH or the current directory." , file = sys .stderr )
193
+ return
194
+
195
+ if status == 0 :
196
+ break
197
+ else :
198
+ print (f"Attempt { run } failed with status { status } . Retrying in { wait_sec } seconds..." , file = sys .stderr )
199
+ time .sleep (wait_sec )
200
+
201
+ if status != 0 :
202
+ print (f"Failed to get SBOM after { max_try } tries" , file = sys .stderr )
203
+ try :
204
+ with open (err_file , "r" ) as f :
205
+ error_output = f .read ()
206
+ print (error_output , file = sys .stderr )
207
+ except FileNotFoundError :
208
+ print (f"Error file '{ err_file } ' not found." , file = sys .stderr )
209
+ # finally:
210
+ # raise_exception(f"Invalid item: {item}")
211
+ else :
212
+ print (f"Successfully downloaded SBOM to: { sbom } " )
213
+
214
+ # Clean up the temporary error file
215
+ if os .path .exists (err_file ):
216
+ os .remove (err_file )
217
+
218
+
151
219
def process_dependency_item (item , container_id , annotation_type ):
152
220
"""Processes a single item (dictionary) from the JSON data."""
153
221
@@ -187,51 +255,66 @@ def process_dependency_item(item, container_id, annotation_type):
187
255
raise_exception (f"{ name } version check failed. Expected '{ version } ', found '{ output } '." )
188
256
189
257
190
- def check_against_image (tag , tag_annotations , tag_name , image ):
191
- # Check if the sbom for the image is available
192
- # If yes, process it directly
193
- # If not, then run the image and gather the data from it directly
194
-
195
- container_id = run_podman_container (f {tag_name }- container , image )
196
- if not container_id :
197
- raise_exception (f"Failed to start a container from image '{ image } ' for the '{ tag_name } ' tag!" )
258
+ def check_sbom_available (image ):
259
+ # TODO
260
+ return None
198
261
199
- ntb_sw_annotation = "opendatahub.io/notebook-software"
200
- python_dep_annotation = "opendatahub.io/notebook-python-dependencies"
201
262
202
- errors = []
203
- try :
204
- software = tag_annotations .get (ntb_sw_annotation )
205
- if not software :
206
- raise_exception (f"Missing '{ ntb_sw_annotation } ' in ImageStream tag '{ tag } '!" )
263
+ def check_sbom_content (software , python_deps , sbom ):
264
+ # TODO
265
+ return None
207
266
208
- python_deps = tag_annotations .get (python_dep_annotation )
209
- if not python_deps :
210
- raise_exception (f"Missing '{ python_dep_annotation } ' in ImageStream tag '{ tag } '!" )
267
+ def check_against_image (tag , tag_annotations , tag_name , image ):
268
+ # Check if the sbom for the image is available
269
+ if check_sbom_available :
270
+ log .info (f"SBOM for image '{ image } ' is available." )
271
+ platform = "--platform=linux/amd64"
272
+ output_file = "sbom.json"
273
+ download_sbom_with_retry (platform , image , output_file )
274
+ # TODO check the sbom against the expected data
275
+ # check_sbom_content()
276
+ else :
277
+ # SBOM not available -> gather data directly from the running image
278
+ container_id = run_podman_container (f"{ tag_name } -container" , image )
279
+ if not container_id :
280
+ raise_exception (f"Failed to start a container from image '{ image } ' for the '{ tag_name } ' tag!" )
211
281
212
- try :
213
- for item in parse_json_string (software ) or []:
214
- process_dependency_item (item , container_id , AnnotationType .SOFTWARE )
215
- except Exception as e :
216
- log .error (f"Failed check for the '{ tag_name } ' tag!" )
217
- errors .append (str (e ))
282
+ ntb_sw_annotation = "opendatahub.io/notebook-software"
283
+ python_dep_annotation = "opendatahub.io/notebook-python-dependencies"
218
284
285
+ errors = []
219
286
try :
220
- for item in parse_json_string (python_deps ) or []:
221
- process_dependency_item (item , container_id , AnnotationType .PYTHON_DEPS )
222
- except Exception as e :
223
- log .error (f"Failed check for the '{ tag_name } ' tag!" )
224
- errors .append (str (e ))
225
- finally :
226
- print_delimiter ()
227
- try :
228
- stop_and_remove_container (container_id )
229
- except Exception as e :
230
- log .error (f"Failed to stop/remove the container '{ container_id } ' for the '{ tag_name } ' tag!" )
231
- errors .append (str (e ))
232
-
233
- if errors :
234
- raise Exception (errors )
287
+ software = tag_annotations .get (ntb_sw_annotation )
288
+ if not software :
289
+ raise_exception (f"Missing '{ ntb_sw_annotation } ' in ImageStream tag '{ tag } '!" )
290
+
291
+ python_deps = tag_annotations .get (python_dep_annotation )
292
+ if not python_deps :
293
+ raise_exception (f"Missing '{ python_dep_annotation } ' in ImageStream tag '{ tag } '!" )
294
+
295
+ try :
296
+ for item in parse_json_string (software ) or []:
297
+ process_dependency_item (item , container_id , AnnotationType .SOFTWARE )
298
+ except Exception as e :
299
+ log .error (f"Failed check for the '{ tag_name } ' tag!" )
300
+ errors .append (str (e ))
301
+
302
+ try :
303
+ for item in parse_json_string (python_deps ) or []:
304
+ process_dependency_item (item , container_id , AnnotationType .PYTHON_DEPS )
305
+ except Exception as e :
306
+ log .error (f"Failed check for the '{ tag_name } ' tag!" )
307
+ errors .append (str (e ))
308
+ finally :
309
+ print_delimiter ()
310
+ try :
311
+ stop_and_remove_container (container_id )
312
+ except Exception as e :
313
+ log .error (f"Failed to stop/remove the container '{ container_id } ' for the '{ tag_name } ' tag!" )
314
+ errors .append (str (e ))
315
+
316
+ if errors :
317
+ raise Exception (errors )
235
318
236
319
237
320
def process_tag (tag , image ):
0 commit comments