Skip to content

Commit 5a6b30b

Browse files
committed
support download option for upload command
Signed-off-by: Vivek Kumar Sahu <[email protected]>
1 parent f79dfb9 commit 5a6b30b

File tree

2 files changed

+134
-18
lines changed

2 files changed

+134
-18
lines changed

lynkctx.py

+98-13
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ def resolve_env(self):
226226

227227
def resolve_ver(self):
228228
env = self.env or 'default'
229+
print("self.ver_id: ", self.ver_id)
229230
if not self.ver_id:
230231
for product in self.data.get('data', {}).get('organization', {}).get('productNodes', {}).get('products', []):
231232
if product['id'] == self.prod_id:
@@ -252,6 +253,46 @@ def resolve_ver(self):
252253

253254
return (empty_ver or self.ver) and self.ver_id
254255

256+
def resolve_latest_ver(self):
257+
"""
258+
Resolve and set the version ID for the latest uploaded SBOM version
259+
based on the most recent updatedAt timestamp.
260+
"""
261+
# fetch the context data
262+
self.data = self._fetch_context()
263+
if not self.data:
264+
print("Failed to refresh context data")
265+
return False
266+
267+
print("Attempting to resolve the latest version information for Product ID:", self.prod_id, "Environment ID:", self.env_id)
268+
269+
# retry to handle propagation delays
270+
for attempt in range(5):
271+
print(f"Attempt {attempt + 1}: Resolving the latest version ID based on updatedAt")
272+
273+
for product in self.data.get('data', {}).get('organization', {}).get('productNodes', {}).get('products', []):
274+
if product['id'] == self.prod_id:
275+
for env in product['environments']:
276+
if env['id'] == self.env_id:
277+
278+
# sort the versions by updatedAt in descending order
279+
versions_sorted = sorted(env['versions'], key=lambda v: v['updatedAt'], reverse=True)
280+
latest_version = versions_sorted[0] if versions_sorted else None
281+
282+
if latest_version:
283+
self.ver_id = latest_version['id']
284+
self.ver = latest_version['primaryComponent']['version']
285+
self.ver_status = self.vuln_status_to_status(latest_version['vulnRunStatus'])
286+
print(f"Resolved version ID: {self.ver_id}")
287+
return True
288+
289+
print("Latest version ID not found. Retrying...")
290+
time.sleep(2)
291+
292+
print("Error: Unable to resolve the latest SBOM version information after multiple attempts.")
293+
return False
294+
295+
255296
def __repr__(self):
256297
from pprint import pformat
257298
return pformat(vars(self), indent=1, width=1)
@@ -286,6 +327,7 @@ def versions(self):
286327

287328
def status(self):
288329
self.resolve_ver()
330+
print("self.ver_status: ", self.ver_status)
289331
return self.ver_status
290332

291333
def download(self):
@@ -334,17 +376,17 @@ def download(self):
334376
response.status_code)
335377

336378
def upload(self, sbom_file):
337-
if os.path.isfile(sbom_file) is False:
379+
if not os.path.isfile(sbom_file):
338380
print(f"SBOM File not found: {sbom_file}")
339-
return
381+
return None
340382

341383
if not self.prod_id:
342384
print(f"Product not found: {self.prod}")
343-
return
385+
return None
344386

345387
if not self.env_id:
346388
print(f"Environment not found: {self.env}")
347-
return
389+
return None
348390

349391
logging.debug("Uploading SBOM to product ID %s", self.prod_id)
350392

@@ -367,26 +409,66 @@ def upload(self, sbom_file):
367409
with open(sbom_file, 'rb') as sbom:
368410
files_map = {'0': sbom}
369411
response = requests.post(self.api_url,
370-
headers=headers,
371-
data=form_data,
372-
files=files_map,
373-
timeout=INTERLYNK_API_TIMEOUT)
412+
headers=headers,
413+
data=form_data,
414+
files=files_map,
415+
timeout=INTERLYNK_API_TIMEOUT)
374416
if response.status_code == 200:
375417
resp_json = response.json()
376-
errors = resp_json.get('data', {}).get(
377-
'sbomUpload', {}).get('errors')
418+
errors = resp_json.get('data', {}).get('sbomUpload', {}).get('errors')
378419
if errors:
379420
print(f"Error uploading sbom: {errors}")
380-
return 1
421+
return None
422+
381423
print('Uploaded successfully')
382424
logging.debug("SBOM Uploading response: %s", response.text)
383-
return 0
425+
return True # Return True to indicate a successful upload
426+
384427
logging.error("Error uploading sbom: %d", response.status_code)
385428
except requests.exceptions.RequestException as ex:
386429
logging.error("RequestException: %s", ex)
387430
except FileNotFoundError as ex:
388431
logging.error("FileNotFoundError: %s", ex)
389-
return 1
432+
433+
return None
434+
435+
436+
def _fetch_latest_version_id(self):
437+
"""
438+
Fetch the latest version ID for the current product and environment.
439+
"""
440+
headers = {
441+
"Authorization": "Bearer " + self.token
442+
}
443+
444+
request_data = {
445+
"query": QUERY_PRODUCTS_LIST,
446+
"variables": {"first": 1}, # Fetch the latest
447+
}
448+
449+
try:
450+
response = requests.post(self.api_url,
451+
headers=headers,
452+
json=request_data,
453+
timeout=INTERLYNK_API_TIMEOUT)
454+
if response.status_code == 200:
455+
response_data = response.json()
456+
products = response_data.get('data', {}).get('organization', {}).get('productNodes', {}).get('products', [])
457+
for product in products:
458+
if product['id'] == self.prod_id:
459+
for env in product['environments']:
460+
if env['id'] == self.env_id:
461+
# Assuming the latest version is at the end of the list
462+
latest_version = env['versions'][-1]
463+
return latest_version['id'] if latest_version else None
464+
465+
logging.error("Error: Version ID not found for the specified product and environment.")
466+
else:
467+
logging.error("Failed to retrieve latest version ID. Status code: %s", response.status_code)
468+
except requests.exceptions.RequestException as ex:
469+
logging.error("RequestException: %s", ex)
470+
return None
471+
390472

391473
def vuln_status_to_status(self, status):
392474
result_dict = dict()
@@ -396,18 +478,21 @@ def vuln_status_to_status(self, status):
396478
result_dict['automationStatus'] = 'UNKNOWN'
397479
result_dict['vulnScanStatus'] = 'UNKNOWN'
398480
if status == 'NOT_STARTED':
481+
print("NOT_STARTED")
399482
result_dict['vulnScanStatus'] = 'NOT_STARTED'
400483
result_dict['checksStatus'] = 'NOT_STARTED'
401484
result_dict['policyStatus'] = 'NOT_STARTED'
402485
result_dict['labelingStatus'] = 'NOT_STARTED'
403486
result_dict['automationStatus'] = 'NOT_STARTED'
404487
elif status == 'IN_PROGRESS':
488+
print("IN_PROGRESS")
405489
result_dict['vulnScanStatus'] = 'IN_PROGRESS'
406490
result_dict['checksStatus'] = 'COMPLETED'
407491
result_dict['policyStatus'] = 'COMPLETED'
408492
result_dict['labelingStatus'] = 'COMPLETED'
409493
result_dict['automationStatus'] = 'COMPLETED'
410494
elif status == 'FINISHED':
495+
print("FINISHED")
411496
result_dict['vulnScanStatus'] = 'COMPLETED'
412497
result_dict['checksStatus'] = 'COMPLETED'
413498
result_dict['policyStatus'] = 'COMPLETED'

pylynk.py

+36-5
Original file line numberDiff line numberDiff line change
@@ -212,19 +212,48 @@ def download_sbom(lynk_ctx):
212212

213213
return 0
214214

215-
216-
def upload_sbom(lynk_ctx, sbom_file):
215+
def upload_sbom(lynk_ctx, sbom_file, download=False):
217216
"""
218-
Upload SBOM to the lynk_ctx.
217+
Upload SBOM to the lynk_ctx and optionally download it if specified.
219218
220219
Args:
221220
lynk_ctx: The lynk context object.
222221
sbom_file: The path to the SBOM file.
222+
download: Boolean to indicate if download is requested after upload.
223223
224224
Returns:
225225
The result of the upload operation.
226226
"""
227-
return lynk_ctx.upload(sbom_file)
227+
upload_result = lynk_ctx.upload(sbom_file)
228+
if not upload_result:
229+
print("SBOM upload failed.")
230+
return 1
231+
232+
print("SBOM uploaded successfully.")
233+
print(f"Product ID: {lynk_ctx.prod_id}, Environment ID: {lynk_ctx.env_id}")
234+
235+
# get the version ID after upload
236+
if not lynk_ctx.resolve_latest_ver():
237+
print("Error: Unable to resolve SBOM version information.")
238+
return 1
239+
240+
if download:
241+
while True:
242+
print("Checking SBOM status for automation completion...")
243+
244+
# retrieve status for specific version ID
245+
status = lynk_ctx.status()
246+
print("Current Status:", status)
247+
248+
if status.get('automationStatus') == "COMPLETED":
249+
print("Automation status completed. Initiating SBOM download.")
250+
download_sbom(lynk_ctx)
251+
break
252+
else:
253+
print("Waiting for automation status to complete...")
254+
time.sleep(5)
255+
256+
return 0
228257

229258

230259
def add_output_format_group(parser):
@@ -290,6 +319,8 @@ def setup_args():
290319
upload_parser.add_argument("--token",
291320
required=False,
292321
help="Security token")
322+
upload_parser.add_argument("--download", required=False, help="Download SBOM after upload")
323+
293324

294325
download_parser = subparsers.add_parser("download", help="Download SBOM")
295326
download_group = download_parser.add_mutually_exclusive_group(
@@ -374,7 +405,7 @@ def main() -> int:
374405
elif args.subcommand == "status":
375406
print_status(lynk_ctx, fmt_json)
376407
elif args.subcommand == "upload":
377-
upload_sbom(lynk_ctx, args.sbom)
408+
upload_sbom(lynk_ctx, args.sbom, download=args.download)
378409
elif args.subcommand == "download":
379410
download_sbom(lynk_ctx)
380411
else:

0 commit comments

Comments
 (0)