boman-cli 2.5.2__tar.gz → 2.5.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boman-cli
3
- Version: 2.5.2
3
+ Version: 2.5.4
4
4
  Summary: CLI tool of boman.ai
5
5
  Home-page: https://boman.ai
6
6
  Author: Sumeru Software Solutions Pvt. Ltd.
@@ -105,11 +105,19 @@ Example: boman-cli -a run -zap_session_script ./session.js
105
105
  2 : Auth error
106
106
  3 : Docker/System error
107
107
  4 : Misconfig error
108
-
108
+ 5 : Failing based on SLA
109
109
 
110
110
 
111
111
 
112
112
  ### Release Note:
113
+
114
+ ### 2.5.4
115
+ - **CLOC** - New feature. CLI will now generate lines of code of the repo and push it to SaaS.
116
+ - **Dev Attribution** - New feature, Dev names for fetched for the findings and shared with SaaS.
117
+
118
+ ### 2.5.3
119
+ - **SLA** - new feature. Build fail SLA has been introduced it can be configured in SaaS platform. Build fails if the condition is met menioned in SaaS. Check Build SLA in SLA menu for more info.
120
+
113
121
  ### 2.5.1
114
122
  - **SBOM** Added New fields as per certin
115
123
 
@@ -74,11 +74,19 @@ Example: boman-cli -a run -zap_session_script ./session.js
74
74
  2 : Auth error
75
75
  3 : Docker/System error
76
76
  4 : Misconfig error
77
-
77
+ 5 : Failing based on SLA
78
78
 
79
79
 
80
80
 
81
81
  ### Release Note:
82
+
83
+ ### 2.5.4
84
+ - **CLOC** - New feature. CLI will now generate lines of code of the repo and push it to SaaS.
85
+ - **Dev Attribution** - New feature, Dev names for fetched for the findings and shared with SaaS.
86
+
87
+ ### 2.5.3
88
+ - **SLA** - new feature. Build fail SLA has been introduced it can be configured in SaaS platform. Build fails if the condition is met menioned in SaaS. Check Build SLA in SLA menu for more info.
89
+
82
90
  ### 2.5.1
83
91
  - **SBOM** Added New fields as per certin
84
92
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boman-cli
3
- Version: 2.5.2
3
+ Version: 2.5.4
4
4
  Summary: CLI tool of boman.ai
5
5
  Home-page: https://boman.ai
6
6
  Author: Sumeru Software Solutions Pvt. Ltd.
@@ -105,11 +105,19 @@ Example: boman-cli -a run -zap_session_script ./session.js
105
105
  2 : Auth error
106
106
  3 : Docker/System error
107
107
  4 : Misconfig error
108
-
108
+ 5 : Failing based on SLA
109
109
 
110
110
 
111
111
 
112
112
  ### Release Note:
113
+
114
+ ### 2.5.4
115
+ - **CLOC** - New feature. CLI will now generate lines of code of the repo and push it to SaaS.
116
+ - **Dev Attribution** - New feature, Dev names for fetched for the findings and shared with SaaS.
117
+
118
+ ### 2.5.3
119
+ - **SLA** - new feature. Build fail SLA has been introduced it can be configured in SaaS platform. Build fails if the condition is met menioned in SaaS. Check Build SLA in SLA menu for more info.
120
+
113
121
  ### 2.5.1
114
122
  - **SBOM** Added New fields as per certin
115
123
 
@@ -11,6 +11,7 @@ bomancli/Config.py
11
11
  bomancli/_init_.py
12
12
  bomancli/auth.py
13
13
  bomancli/base_logger.py
14
+ bomancli/dev_attribution.py
14
15
  bomancli/loc_finder.py
15
16
  bomancli/main.py
16
17
  bomancli/sbom_enricher.py
@@ -121,7 +121,7 @@ class Config:
121
121
 
122
122
  log_level = "INFO"
123
123
 
124
- version = 'v2.5.2'
124
+ version = 'v2.5.4'
125
125
 
126
126
  boman_config_file = 'boman.yaml'
127
127
 
@@ -218,7 +218,12 @@ class Config:
218
218
  reachability_language=None
219
219
 
220
220
  fail_build = False
221
+ sla_fail_build = False
221
222
  polling_time = 60
222
223
  polling_frequency = 10
223
224
 
224
- ml_success = False
225
+ ml_success = False
226
+
227
+ reason_sla_build_fail = []
228
+
229
+ cloc_total_data = None
@@ -0,0 +1,302 @@
1
+ import subprocess
2
+ import json
3
+ import argparse
4
+ import os
5
+ import datetime
6
+ import re
7
+ from docker import errors
8
+ from bomancli.base_logger import logging
9
+ from bomancli.Config import Config
10
+ # --- Git Helper Functions ---
11
+
12
+ def ensure_full_git_history():
13
+ try:
14
+ is_shallow = subprocess.check_output(
15
+ ["git", "rev-parse", "--is-shallow-repository"],
16
+ text=True
17
+ ).strip()
18
+
19
+ if is_shallow == "true":
20
+ logging.info("[INFO] Shallow repository detected. Fetching full history...")
21
+ subprocess.run(
22
+ ["git", "fetch", "--prune", "--unshallow"],
23
+ check=False
24
+ )
25
+ else:
26
+ logging.info("[INFO] Repository already has full history.")
27
+
28
+ except Exception as e:
29
+ logging.error(f"[WARN] Could not determine git history depth: {e}", file=sys.stderr)
30
+
31
+
32
+
33
+
34
+
35
+ def run_git_command(cmd, cwd=None):
36
+ """Run a git command and return output."""
37
+ try:
38
+ if cwd and not os.path.exists(cwd):
39
+ return None, "cwd_not_found"
40
+ result = subprocess.run(
41
+ cmd,
42
+ stdout=subprocess.PIPE,
43
+ stderr=subprocess.PIPE,
44
+ text=True,
45
+ check=False,
46
+ cwd=cwd,
47
+ encoding='utf-8',
48
+ errors='replace'
49
+ )
50
+ if result.returncode != 0:
51
+ return None, result.stderr.strip()
52
+ return result.stdout.strip(), None
53
+ except Exception as e:
54
+ logging.error(f"Error running git command {' '.join(cmd)}: {e}")
55
+ return None, str(e)
56
+
57
+ def get_branch_info(repo_root="."):
58
+ """Fetch current branch name, commit, and remote."""
59
+ branch, _ = run_git_command(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=repo_root)
60
+ commit, _ = run_git_command(["git", "rev-parse", "HEAD"], cwd=repo_root)
61
+ remote, _ = run_git_command(["git", "config", "--get", "remote.origin.url"], cwd=repo_root)
62
+
63
+ return {
64
+ "name": branch,
65
+ "head_commit": commit,
66
+ "remote_url": remote
67
+ }
68
+
69
+ # --- Blame Logic ---
70
+
71
+ def get_owner_from_lines(file_path, start_line, end_line):
72
+ # Ensure file exists
73
+ if not os.path.isfile(file_path):
74
+ return None, "file_not_found_on_disk"
75
+
76
+ cmd = [
77
+ "git", "blame",
78
+ "-w", "-M", "-C",
79
+ "-L", f"{start_line},{end_line}",
80
+ "--line-porcelain",
81
+ "--", file_path
82
+ ]
83
+
84
+ result, error = run_git_command(cmd)
85
+
86
+ if error:
87
+ return None, error
88
+
89
+ line_authors = []
90
+
91
+ line_authors = []
92
+ current = {}
93
+
94
+ for line in result.splitlines():
95
+ # Check for new block start (Hash Line)
96
+ # Format: <40-hex-sha> <orig_line> <final_line> [<group_lines>]
97
+ if re.match(r'^[a-f0-9]{40}\s', line):
98
+ # If we have a previous block with sufficient data, save it
99
+ if "author" in current:
100
+ line_authors.append(current)
101
+ # Start new block
102
+ current = {}
103
+ parts = line.split(" ")
104
+ current["commit"] = parts[0]
105
+ continue
106
+
107
+ if line.startswith("author "):
108
+ current["author"] = line.replace("author ", "")
109
+ elif line.startswith("author-mail "):
110
+ current["email"] = line.replace("author-mail ", "").strip("<>")
111
+ elif line.startswith("author-time "):
112
+ current["author_time"] = int(line.replace("author-time ", ""))
113
+ elif line.startswith("committer-time "):
114
+ pass
115
+ elif line.startswith("summary "):
116
+ current["summary"] = line.replace("summary ", "")
117
+ elif line.startswith("\t"): # actual code line
118
+ current["code"] = line.strip()
119
+
120
+ # Capture the last block
121
+ if "author" in current:
122
+ line_authors.append(current)
123
+
124
+ if not line_authors:
125
+ return None, f"no_authors_parsed_from_blame. Raw output start: {result[:200]!r}"
126
+
127
+ # Aggregate stats for this specific block
128
+ unique_authors = {}
129
+
130
+ for entry in line_authors:
131
+ email = entry.get("email")
132
+ if email not in unique_authors:
133
+ unique_authors[email] = {
134
+ "name": entry.get("author"),
135
+ "email": email,
136
+ "commit": entry.get("commit"),
137
+ "line_count": 0,
138
+ "timestamps": []
139
+ }
140
+ unique_authors[email]["line_count"] += 1
141
+ unique_authors[email]["timestamps"].append(entry.get("author_time"))
142
+
143
+ # Sort by line count
144
+ sorted_authors = sorted(unique_authors.values(), key=lambda x: x["line_count"], reverse=True)
145
+
146
+ if not sorted_authors:
147
+ return None, "no_sorted_authors"
148
+
149
+ # Determine primary owner for this finding
150
+ primary = sorted_authors[0]
151
+
152
+ # Calculate most recent time
153
+ all_timestamps = [t for a in unique_authors.values() for t in a["timestamps"]]
154
+ most_recent_ts = max(all_timestamps) if all_timestamps else 0
155
+ most_recent_str = datetime.datetime.fromtimestamp(most_recent_ts).strftime("%Y-%m-%d %H:%M:%S")
156
+
157
+ return {
158
+ "file": file_path,
159
+ "lines": f"{start_line}-{end_line}",
160
+ "authors": sorted_authors,
161
+ "primary_owner": primary["name"],
162
+ "primary_email": primary["email"],
163
+ "primary_commit": primary["commit"],
164
+ "updated_at": most_recent_str
165
+ }, None
166
+
167
+ # --- Main Enrichment Logic ---
168
+
169
+ def normalize_path(path):
170
+ # Remove leading prefixes often found in container scans
171
+ if path.startswith("/src/"):
172
+ return path[5:]
173
+ elif path.startswith("src/"):
174
+ return path[4:]
175
+ return path.lstrip("/")
176
+
177
+ def enrich_files(input_path, output_path):
178
+ # 1. Load Input
179
+
180
+ logging.info(f"Loading {input_path}...")
181
+
182
+ #logging.info('checking git swallow')
183
+
184
+ ensure_full_git_history()
185
+
186
+ try:
187
+ with open(input_path, "r", encoding="utf-8") as f:
188
+ data = json.load(f)
189
+ except Exception as e:
190
+ logging.error(f"Error reading input file: {e}")
191
+ return
192
+
193
+ # Handle if root is list
194
+ results = data.get("results", []) if isinstance(data, dict) else data
195
+ if isinstance(data, list):
196
+ # normalize structure
197
+ data = {"results": results}
198
+
199
+ # 2. Prepare Globals
200
+ git_users_registry = {} # key: email, value: stats
201
+
202
+ logging.info(f"Processing {len(results)} findings...")
203
+
204
+ # 3. Process Findings
205
+ for idx, result in enumerate(results):
206
+ path = normalize_path(result.get("path", ""))
207
+
208
+ # Determine lines
209
+ start_line = None
210
+ if isinstance(result.get("start"), dict):
211
+ start_line = result.get("start", {}).get("line")
212
+ else:
213
+ start_line = result.get("start_line") or result.get("start") # fallback
214
+
215
+ end_line = None
216
+ if isinstance(result.get("end"), dict):
217
+ end_line = result.get("end", {}).get("line")
218
+ else:
219
+ end_line = result.get("end_line") or result.get("end") or start_line
220
+
221
+ if not path or not start_line:
222
+ result["attribution"] = {"status": "skipped_invalid_path_or_line"}
223
+ continue
224
+
225
+ if not os.path.exists(path):
226
+ result["attribution"] = {"status": "file_not_found", "original_path": result.get("path")}
227
+ continue
228
+
229
+ # Get Blame
230
+ blame_info, blame_error = get_owner_from_lines(path, int(start_line), int(end_line))
231
+
232
+ if blame_info:
233
+ # Enriched Attribution Field
234
+ result["attribution"] = {
235
+ "developer": blame_info["primary_owner"],
236
+ "email": blame_info["primary_email"],
237
+ "commit": blame_info["primary_commit"],
238
+ "updated_at": blame_info["updated_at"],
239
+ "confidence": "high",
240
+ "status": "attributed",
241
+ "all_authors": blame_info["authors"] # optional detail
242
+ }
243
+
244
+ # Update Global Registry
245
+ for author in blame_info["authors"]:
246
+ email = author["email"]
247
+ if email not in git_users_registry:
248
+ git_users_registry[email] = {
249
+ "name": author["name"],
250
+ "email": email,
251
+ "total_attributed_findings": 0,
252
+ "last_active_ts": 0
253
+ }
254
+
255
+ # Update stats
256
+ reg = git_users_registry[email]
257
+ if author["name"] == blame_info["primary_owner"]:
258
+ # Only count as 'attributed finding' if they are the primary owner?
259
+ # Or count participation? Let's count if they are primary owner.
260
+ reg["total_attributed_findings"] += 1
261
+
262
+ # Update activity time
263
+ # author["timestamps"] is a list of ts
264
+ if author["timestamps"]:
265
+ max_ts = max(author["timestamps"])
266
+ if max_ts > reg["last_active_ts"]:
267
+ reg["last_active_ts"] = max_ts
268
+
269
+ else:
270
+ result["attribution"] = {"status": "blame_failed", "error": blame_error}
271
+
272
+ # 4. Finalize Globals
273
+
274
+ # Valid Git Users
275
+ final_users = []
276
+ for email, stats in git_users_registry.items():
277
+ stats["last_active"] = datetime.datetime.fromtimestamp(stats["last_active_ts"]).strftime("%Y-%m-%d %H:%M:%S")
278
+ del stats["last_active_ts"]
279
+ final_users.append(stats)
280
+
281
+ data["git_users"] = final_users
282
+
283
+ # Branch Info
284
+ logging.info("Fetching branch info...")
285
+ data["branch_info"] = get_branch_info()
286
+
287
+ # 5. Write Output
288
+ logging.info(f"Writing results to {output_path}...")
289
+ with open(output_path, "w", encoding="utf-8") as f:
290
+ json.dump(data, f, indent=2)
291
+ logging.info("Done.")
292
+
293
+ def main():
294
+ parser = argparse.ArgumentParser(description="Enrich OpenGrep findings with Git Blame information.")
295
+ parser.add_argument("--input", "-i", required=True, help="Input OpenGrep JSON file")
296
+ parser.add_argument("--output", "-o", default="opengrep_enriched.json", help="Output JSON file")
297
+
298
+ args = parser.parse_args()
299
+ enrich_files(args.input, args.output)
300
+
301
+ if __name__ == "__main__":
302
+ main()
@@ -1,4 +1,8 @@
1
1
  import os
2
+ from docker import errors
3
+ from bomancli.base_logger import logging
4
+ from bomancli.Config import Config
5
+ docker = Config.docker_client
2
6
 
3
7
 
4
8
  ###line counts based on given file extension -- MM
@@ -59,4 +63,31 @@ def countlines(start,lines=0, header=False, begin_start=None,extentsion='.py'):
59
63
 
60
64
 
61
65
 
62
- #print(countlines('/home/boxuser/box/Vuln-code/',extentsion='.rb'))
66
+ #print(countlines('/home/boxuser/box/Vuln-code/',extentsion='.rb'))
67
+
68
+
69
+
70
+
71
+ def run_cloc_docker():
72
+
73
+ docker_image="bomanai/cloc:latest"
74
+ userid= Config.userid
75
+ detach=False
76
+ command_line = f"{Config.build_dir} --json --out=/{Config.build_dir}/cloc.json"
77
+ tool_name ='cloc'
78
+ logging.info('Generating loc')
79
+ try:
80
+
81
+ logging.info('Cloc Docker: Preparing %s scan for jenkins env with %s user ', tool_name , userid)
82
+
83
+
84
+ container_output = docker.containers.run(docker_image, command_line, volumes={Config.sast_build_dir: {
85
+ 'bind': Config.sast_build_dir}}, user=userid,detach=detach, remove=True)
86
+ logging.info('Cloc Docker: SUCCESS !!!. Message: %s Scan Completed',tool_name)
87
+
88
+ except errors.ContainerError as exc:
89
+ msg='\n The following error has been recorded while scanning SAST'
90
+ Utils.logError(msg,str(exc))
91
+ logging.error(f'SAST Docker: Failed !!!, Message: Some Error recorded while scanning {str(exc)}')
92
+ Config.sast_scan_status = 'Failed'
93
+ Config.sast_errors = f'Exit Status {exc.exit_status}:{str(exc)}]'
@@ -31,7 +31,8 @@ from bomancli.sbom_enricher import SBOMEnricher
31
31
  from bomancli import validation as Validation
32
32
  from bomancli import utils as Utils
33
33
  from bomancli import auth as Auth
34
-
34
+ from bomancli.dev_attribution import enrich_files
35
+ from bomancli import loc_finder as Loc_finder
35
36
 
36
37
 
37
38
  parser = argparse.ArgumentParser(
@@ -205,11 +206,23 @@ def runImage(data=None,type=None):
205
206
 
206
207
 
207
208
 
209
+ ## check whether the tool is opengrep, if yes run gitblame enricher script
210
+
211
+
212
+ if tool_name == 'Opengrep':
213
+ logging.info("Running Dev attribution on this script")
214
+ enrich_files(output_file,output_file)
215
+ else:
216
+ logging.info("no dev attribution has been run for this")
217
+
218
+
219
+
208
220
  try:
209
221
  if Config.sast_ignore:
210
222
  os.remove('.semgrepignore')
211
223
  if will_generate_output == 1:
212
- #logging.info('WILL GENERATE OUTPUT')
224
+ logging.info('WILL GENERATE OUTPUT')
225
+ #output_file ="blame_enriched_report.json"
213
226
  if uploadReport(output_file,tool_name,tool_id,scan_details_id,'SAST'):
214
227
  Config.sast_scan_status = 'Completed'
215
228
  Config.sast_upload_status ='Completed'
@@ -778,7 +791,8 @@ def uploadReport(filename,toolname,tool_id,scan_details_id,type):
778
791
  try:
779
792
  logging.info('Removing the result file')
780
793
  if type != "sbom" and type != "reachability":
781
- os.remove(path)
794
+ logging.info("remvoing file here")
795
+ #os.remove(path)
782
796
  elif Config.reachability_present is not True and type == 'sbom':
783
797
  os.remove(path)
784
798
  elif type == "reachability":
@@ -808,7 +822,7 @@ def uploadReport(filename,toolname,tool_id,scan_details_id,type):
808
822
  else:
809
823
  logging.info('[COMPLETED]: %s Report uploaded Successfully! Report Name: %s',toolname,filename)
810
824
  logging.info('Removing the result file')
811
- #os.remove(path)
825
+ os.remove(path)
812
826
  return 1
813
827
  elif r.status_code == 401 :
814
828
  logging.error('Unauthorized Access while uploading the results. Please check the app/customer tokens')
@@ -914,10 +928,10 @@ def exitFunction():
914
928
  x = requests.post(url,json=values)
915
929
  response = x.json()
916
930
 
917
- if not Config.fail_build:
918
- logging.info("Fail Build was not configured")
919
- Utils.get_summary_of_vulns(response)
920
- break
931
+ # if not Config.fail_build:
932
+ # logging.info("Fail Build was not configured")
933
+ # Utils.get_summary_of_vulns(response)
934
+ # break
921
935
 
922
936
  if "ml_status" in response.keys():
923
937
  if response["ml_status"] == True:
@@ -1363,43 +1377,53 @@ def default():
1363
1377
  if main():
1364
1378
  logging.info('################################ BOMAN Scanning Done ################################')
1365
1379
  logging.info('#####################################################################################')
1380
+ Loc_finder.run_cloc_docker()
1366
1381
  Utils.showSummary()
1382
+ #Loc_finder.run_cloc_docker()
1367
1383
  Utils.uploadLogs()
1384
+
1385
+ # Sla build failing
1386
+ if Config.sla_fail_build == True:
1387
+ logging.warning("Failing the build for the below reason(s).")
1388
+ for reason in Config.reason_sla_build_fail:
1389
+ logging.warning(f"- {reason}")
1390
+ logging.warning("Please check SLA menu in Boman platform for more details.")
1391
+ exit(5)
1368
1392
 
1369
1393
  ## checking the failbuild argument
1370
1394
  if args.failBuild == 'fail':
1371
1395
  total_vuln = Config.high_count + Config.medium_count + Config.low_count + Config.critical_count
1372
1396
  if total_vuln > 0:
1373
1397
  logging.warning(f"Failing the build as {args.failBuild} is configured in failBuild argument. Boman has found {total_vuln} vulnerabilities")
1374
- exit(-1)
1398
+ exit(5)
1375
1399
  else:
1376
1400
  exit(0)
1377
1401
 
1378
1402
  elif args.failBuild == 'high':
1379
1403
  if Config.high_count > 0 or Config.critical_count > 0:
1380
1404
  logging.warning(f"Failing the build as {args.failBuild} is configured in failBuild argument. Boman has found {Config.high_count} {args.failBuild} vulnerabilities")
1381
- exit(-1)
1405
+ exit(5)
1382
1406
  else:
1383
1407
  exit(0)
1384
1408
 
1385
1409
  elif args.failBuild == 'medium':
1386
1410
  if Config.medium_count > 0 or Config.high_count > 0:
1387
1411
  logging.warning(f"Failing the build as {args.failBuild} is configured in failBuild argument. Boman has found {Config.medium_count} {args.failBuild} vulnerabilities")
1388
- exit(-1)
1412
+ exit(5)
1389
1413
  else:
1390
1414
  exit(0)
1391
1415
 
1392
1416
  elif args.failBuild == 'low':
1393
1417
  if Config.low_count > 0 or Config.medium_count > 0 or Config.high_count > 0:
1394
1418
  logging.warning(f"Failing the build as {args.failBuild} is configured in failBuild argument. Boman has found {Config.low_count} {args.failBuild} vulnerabilities")
1395
- exit(-1)
1419
+ exit(5)
1396
1420
  else:
1397
1421
  exit(0)
1398
1422
 
1399
1423
  elif args.failBuild == 'critical':
1400
1424
  if Config.critical_count > 0:
1401
1425
  logging.warning(f"Failing the build as {args.failBuild} is configured in failBuild argument. Boman has found {Config.critical_count} {args.failBuild} vulnerabilities")
1402
- exit(-1)
1426
+ exit(5)
1403
1427
  else:
1404
1428
  exit(0)
1405
1429
 
@@ -6,6 +6,8 @@ import asyncio
6
6
  import aiohttp
7
7
  from abc import ABC, abstractmethod
8
8
  from datetime import datetime
9
+ from bomancli.base_logger import logging
10
+ from bomancli.Config import Config
9
11
  # -----------------------------------------------------
10
12
  # BASE FETCHERS (Modular Structure)
11
13
  # -----------------------------------------------------
@@ -99,7 +101,7 @@ class MavenFetcher(BaseFetcher):
99
101
  # --- FIX: Ensure the API response is a dictionary ---
100
102
  if not isinstance(j, dict):
101
103
  # If j is a string (non-JSON response), we skip processing and return empty data
102
- print(f"[WARN] Maven API for {name} returned non-JSON data or failed parsing.")
104
+ logging.error(f"[WARN] Maven API for {name} returned non-JSON data or failed parsing.")
103
105
  return data
104
106
  # ----------------------------------------------------
105
107
 
@@ -334,7 +336,7 @@ class SBOMEnricher:
334
336
 
335
337
  # Store the map for quick lookup later
336
338
  self._dependency_map = dependency_map
337
- print(f"Preprocessed dependency map containing {len(dependency_map)} components.")
339
+ logging.info(f"Preprocessed dependency map containing {len(dependency_map)} components.")
338
340
 
339
341
 
340
342
 
@@ -354,7 +356,7 @@ class SBOMEnricher:
354
356
  fetcher_instance = Fetcher(session)
355
357
  return await fetcher_instance.fetch_metadata(package_name, version)
356
358
  except Exception as e:
357
- print(f"[ERROR] Fetcher failed for {package_type} ({package_name}): {e}")
359
+ logging.error(f"[ERROR] Fetcher failed for {package_type} ({package_name}): {e}")
358
360
  return {"description": "", "release_date": ""}
359
361
  else:
360
362
  # Fallback for unknown types (e.g., github, generic files)
@@ -480,7 +482,7 @@ class SBOMEnricher:
480
482
 
481
483
  # Check for components before running async
482
484
  if not self.get_components():
483
- print("No components found in SBOM to enrich.")
485
+ logging.info("No components found in SBOM to enrich.")
484
486
  return self.sbom_data
485
487
 
486
488
  # --- NEW STEP: PREPROCESS DEPENDENCIES ---
@@ -508,5 +510,5 @@ class SBOMEnricher:
508
510
  """Save enriched SBOM"""
509
511
  with open(output_path, "w", encoding="utf-8") as f:
510
512
  json.dump(self.sbom_data, f, indent=2)
511
- print(f"Enriched SBOM saved to {output_path}")
513
+ logging.info(f"Enriched SBOM saved to {output_path}")
512
514
 
@@ -19,7 +19,11 @@ import json
19
19
  import xmltodict
20
20
  import yaml
21
21
  import time
22
-
22
+ from json import JSONDecodeError
23
+ from pathlib import Path
24
+
25
+
26
+
23
27
  logging.basicConfig(format='%(asctime)s — %(name)s — %(levelname)s — %(funcName)s:%(lineno)d — %(message)s')
24
28
 
25
29
  docker = Config.docker_client
@@ -263,7 +267,7 @@ def showSummary():
263
267
  logging.info('--------------------------- Scan Token: %s --------------------------------------------------------',Config.scan_token)
264
268
  if Config.git_present:
265
269
  logging.info('--------------------------- Git Details:[Repo: %s, Branch: %s ]--------------------------------------------------------',Config.git_repo,Config.git_branch)
266
- logging.info('--------------------------- Language : Files_Size : Percentage --------------------------------------------------------')
270
+ #logging.info('--------------------------- Total Lines of Code Scanned: %s --------------------------------------------------------',Config.cloc_total_data)
267
271
  # Step 3: Loop through each item in the JSON object
268
272
  for file_type, details in Config.lingu_details.items():
269
273
  logging.info('--------------------------- %s : %s : %s%%--------------------------------------------------------', file_type, details['size'], details['percentage'])
@@ -947,13 +951,71 @@ def fetch_zap_advance_config():
947
951
  exit(1) #server/saas error
948
952
 
949
953
 
954
+ ### read cloc data
955
+
956
+ def load_cloc_json_safe(path="cloc.json"):
957
+ empty_data = {}
958
+
959
+ try:
960
+ file_path = Path(path)
961
+
962
+ if not file_path.exists():
963
+ return empty_data
964
+
965
+ if file_path.stat().st_size == 0:
966
+ return empty_data
967
+
968
+ with open(file_path, "r", encoding="utf-8") as f:
969
+ data = json.load(f)
970
+
971
+ # Validate expected structure
972
+ if not isinstance(data, dict):
973
+ return empty_data
974
+
975
+ # Optional: cloc usually contains SUM
976
+ if "SUM" not in data:
977
+ return empty_data
978
+
979
+ Config.cloc_total_data = data['SUM']['code']
980
+ return data
981
+
982
+ except (JSONDecodeError, OSError, ValueError):
983
+ # Any parsing or IO error → empty result
984
+ return empty_data
985
+
986
+
987
+
988
+
989
+
950
990
  def uploadLogs():
951
991
 
952
992
  # Get the captured log messages
953
993
  all_logs = Config.log_stream.logs
954
994
 
955
- #parameter setup
995
+ # fetching the cloc results
996
+
997
+ # Usage
998
+ cloc_data = load_cloc_json_safe("cloc.json")
999
+ #print(type(cloc_data))
1000
+ # cloc_data is a STRING, not dict
1001
+ #cloc_data = cloc_data.strip()
1002
+
1003
+ # try:
1004
+ # cloc_data = json.loads(cloc_data) #
1005
+ # except json.JSONDecodeError:
1006
+ # cloc_data = {} #
1007
+
1008
+
1009
+ if not cloc_data:
1010
+ logging.error("Invalid or empty cloc JSON, using empty data")
1011
+ else:
1012
+ logging.info("Valid cloc JSON loaded")
956
1013
 
1014
+
1015
+
1016
+
1017
+
1018
+ #parameter setup
957
1019
  body = {
958
1020
  'auth_token': Config.app_token,
959
1021
  'customer_token': Config.customer_token,
@@ -962,7 +1024,8 @@ def uploadLogs():
962
1024
  'git_repo_name':Config.git_repo,
963
1025
  'git_branch_name':Config.git_branch,
964
1026
  'full_logs':all_logs,
965
- 'lingu_details':Config.lingu_details
1027
+ 'lingu_details':Config.lingu_details,
1028
+ 'loc_details':cloc_data
966
1029
  # Add more parameters if needed
967
1030
  }
968
1031
 
@@ -972,7 +1035,7 @@ def uploadLogs():
972
1035
 
973
1036
  url = Config.boman_url+"/api/scan/logs/upload"
974
1037
 
975
- response = requests.post(url, data=body)
1038
+ response = requests.post(url, json=body)
976
1039
 
977
1040
  if response.status_code == 200:
978
1041
  #logging.info('Uploading logs for the scan token %s',str(Config.scan_token))
@@ -1141,6 +1204,9 @@ def get_summary_of_vulns(response):
1141
1204
  Config.medium_count = vuln['MEDIUM']
1142
1205
  Config.high_count = vuln['HIGH']
1143
1206
  Config.critical_count = vuln['CRITICAL']
1207
+
1208
+ Config.sla_fail_build= response["build_fail"]
1209
+ Config.reason_sla_build_fail = response["reason_for_failing"]
1144
1210
 
1145
1211
  logging.info('Summary: Analyzing Vulnerabitlites Done')
1146
1212
 
@@ -1174,3 +1240,5 @@ def ml_start(app_token, customer_token, scan_token):
1174
1240
  logging.error(f"Connection Error: Can't connect to the Server, Please check your Internet connection. Error: {e}")
1175
1241
  else:
1176
1242
  logging.warning("ML Start API has been already called....")
1243
+
1244
+
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  environment = prod
3
- version = 2.5.2
3
+ version = 2.5.4
4
4
  name = boman-cli
5
5
  saas_base_url = https://dashboard.boman.ai/
6
6
 
File without changes
File without changes
File without changes