Skip to content

Commit 4ec138f

Browse files
Merge pull request #25 from interlynk-io/24-add-option-to-save-sbom-to-disk
Add save to file and pretty print
2 parents c30c25f + 1702d89 commit 4ec138f

File tree

2 files changed

+86
-6
lines changed

2 files changed

+86
-6
lines changed

lynkctx.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import requests
1+
import os
22
import json
33
import logging
4-
import os
54
import base64
5+
import requests
66

77
INTERLYNK_API_URL = 'https://api.interlynk.io/lynkapi'
88

@@ -74,7 +74,7 @@
7474

7575

7676
class LynkContext:
77-
def __init__(self, api_url, token, prod_id, prod, env_id, env, ver_id, ver):
77+
def __init__(self, api_url, token, prod_id, prod, env_id, env, ver_id, ver, output_file):
7878
self.api_url = api_url or INTERLYNK_API_URL
7979
self.token = token
8080
self.prod_id = prod_id
@@ -83,6 +83,7 @@ def __init__(self, api_url, token, prod_id, prod, env_id, env, ver_id, ver):
8383
self.env = env
8484
self.ver_id = ver_id
8585
self.ver = ver
86+
self.output_file = output_file
8687

8788
def validate(self):
8889
if not self.token:

pylynk.py

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,39 @@
1313
# limitations under the License.
1414

1515
import os
16+
import sys
17+
import json
1618
import argparse
1719
import logging
1820
import datetime
1921
import pytz
2022
import tzlocal
21-
import sys
2223
from lynkctx import LynkContext
2324

2425

2526
def user_time(utc_time):
27+
"""
28+
Convert UTC time to local time and format it as a string.
29+
30+
Args:
31+
utc_time (str): The UTC time in ISO format.
32+
33+
Returns:
34+
str: The local time formatted as a string.
35+
"""
2636
timestamp = datetime.datetime.fromisoformat(utc_time[:-1])
2737
local_timezone = tzlocal.get_localzone()
2838
local_time = timestamp.replace(tzinfo=pytz.UTC).astimezone(local_timezone)
2939
return local_time.strftime('%Y-%m-%d %H:%M:%S %Z')
3040

3141

3242
def print_products(lynk_ctx):
43+
"""
44+
Print the products of the Lynk context.
45+
46+
Args:
47+
lynk_ctx (LynkContext): The Lynk context object.
48+
"""
3349
products = lynk_ctx.prods()
3450

3551
# Calculate dynamic column widths
@@ -67,6 +83,12 @@ def print_products(lynk_ctx):
6783

6884

6985
def print_versions(lynk_ctx):
86+
"""
87+
Print the versions of the Lynk context.
88+
89+
Args:
90+
lynk_ctx (LynkContext): The Lynk context object.
91+
"""
7092
versions = lynk_ctx.versions()
7193
if not versions:
7294
print('No versions found')
@@ -122,20 +144,49 @@ def print_versions(lynk_ctx):
122144

123145

124146
def download_sbom(lynk_ctx):
147+
"""
148+
Download SBOM from the lynk_ctx and save it to a file or print it to stdout.
149+
150+
Args:
151+
lynk_ctx: The lynk context object.
152+
153+
Returns:
154+
int: 0 if successful, 1 if failed to fetch SBOM.
155+
"""
125156
sbom = lynk_ctx.download()
126157
if sbom is None:
127158
print('Failed to fetch SBOM')
128159
return 1
129160

130-
sys.stdout.buffer.write(sbom.encode("utf-8", errors='ignore'))
161+
sbom_data = json.loads(sbom.encode('utf-8'))
162+
163+
if lynk_ctx.output_file:
164+
with open(lynk_ctx.output_file, 'w', encoding='utf-8') as f:
165+
json.dump(sbom_data, f, indent=4, ensure_ascii=False)
166+
else:
167+
json.dump(sbom_data, sys.stdout, indent=4, ensure_ascii=False)
168+
131169
return 0
132170

133171

134172
def upload_sbom(lynk_ctx, sbom_file):
173+
"""
174+
Upload SBOM to the lynk_ctx.
175+
176+
Args:
177+
lynk_ctx: The lynk context object.
178+
sbom_file: The path to the SBOM file.
179+
180+
Returns:
181+
The result of the upload operation.
182+
"""
135183
return lynk_ctx.upload(sbom_file)
136184

137185

138186
def setup_args():
187+
"""
188+
Set up command line arguments for the script.
189+
"""
139190
parser = argparse.ArgumentParser(description='Interlynk command line tool')
140191
parser.add_argument('--verbose', '-v', action='count', default=0)
141192

@@ -183,17 +234,38 @@ def setup_args():
183234
required=False,
184235
help="Security token")
185236

237+
download_parser.add_argument(
238+
"--output", help="Output file", required=False)
239+
186240
args = parser.parse_args()
187241
return args
188242

189243

190244
def setup_log_level(args):
245+
"""
246+
Set up the log level based on the command line arguments.
247+
248+
Args:
249+
args: The command line arguments.
250+
251+
Returns:
252+
None.
253+
"""
191254
if args.verbose == 0:
192255
logging.basicConfig(level=logging.ERROR)
193256
logging.basicConfig(level=logging.DEBUG)
194257

195258

196259
def setup_lynk_context(args):
260+
"""
261+
Set up the LynkContext object based on the command line arguments.
262+
263+
Args:
264+
args: The command line arguments.
265+
266+
Returns:
267+
LynkContext: The LynkContext object.
268+
"""
197269
return LynkContext(
198270
os.environ.get('INTERLYNK_API_URL'),
199271
getattr(args, 'token', None) or os.environ.get(
@@ -203,11 +275,18 @@ def setup_lynk_context(args):
203275
getattr(args, 'envId', None),
204276
getattr(args, 'env', None),
205277
getattr(args, 'verId', None),
206-
getattr(args, 'ver', None)
278+
getattr(args, 'ver', None),
279+
getattr(args, 'output', None)
207280
)
208281

209282

210283
def main() -> int:
284+
"""
285+
Main function that serves as the entry point of the program.
286+
287+
Returns:
288+
int: The exit code of the program.
289+
"""
211290
args = setup_args()
212291
setup_log_level(args)
213292
lynk_ctx = setup_lynk_context(args)

0 commit comments

Comments
 (0)