scanoss 1.30.0__py3-none-any.whl → 1.31.0__py3-none-any.whl

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.
scanoss/cli.py CHANGED
@@ -33,12 +33,10 @@ from typing import List
33
33
  import pypac
34
34
 
35
35
  from scanoss.cryptography import Cryptography, create_cryptography_config_from_args
36
- from scanoss.export.dependency_track import (
37
- DependencyTrackExporter,
38
- create_dependency_track_exporter_config_from_args,
39
- )
40
- from scanoss.inspection.component_summary import ComponentSummary
41
- from scanoss.inspection.license_summary import LicenseSummary
36
+ from scanoss.export.dependency_track import DependencyTrackExporter
37
+ from scanoss.inspection.dependency_track.project_violation import DependencyTrackProjectViolationPolicyCheck
38
+ from scanoss.inspection.raw.component_summary import ComponentSummary
39
+ from scanoss.inspection.raw.license_summary import LicenseSummary
42
40
  from scanoss.scanners.container_scanner import (
43
41
  DEFAULT_SYFT_COMMAND,
44
42
  DEFAULT_SYFT_TIMEOUT,
@@ -69,8 +67,8 @@ from .constants import (
69
67
  from .csvoutput import CsvOutput
70
68
  from .cyclonedx import CycloneDx
71
69
  from .filecount import FileCount
72
- from .inspection.copyleft import Copyleft
73
- from .inspection.undeclared_component import UndeclaredComponent
70
+ from .inspection.raw.copyleft import Copyleft
71
+ from .inspection.raw.undeclared_component import UndeclaredComponent
74
72
  from .results import Results
75
73
  from .scancodedeps import ScancodeDeps
76
74
  from .scanner import FAST_WINNOWING, Scanner
@@ -539,80 +537,293 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
539
537
  )
540
538
  p_results.set_defaults(func=results)
541
539
 
542
- ########################################### INSPECT SUBCOMMAND ###########################################
543
- # Sub-command: inspect
540
+ # =========================================================================
541
+ # INSPECT SUBCOMMAND - Analysis and validation of scan results
542
+ # =========================================================================
543
+
544
+ # Main inspect parser - provides tools for analyzing scan results
544
545
  p_inspect = subparsers.add_parser(
545
- 'inspect', aliases=['insp', 'ins'], description=f'Inspect results: {__version__}', help='Inspect results'
546
+ 'inspect',
547
+ aliases=['insp', 'ins'],
548
+ description=f'Inspect and analyse scan results: {__version__}',
549
+ help='Inspect and analyse scan results'
546
550
  )
547
- # Sub-parser: inspect
551
+
552
+ # Inspect sub-commands parser
548
553
  p_inspect_sub = p_inspect.add_subparsers(
549
- title='Inspect Commands', dest='subparsercmd', description='Inspect sub-commands', help='Inspect sub-commands'
554
+ title='Inspect Commands',
555
+ dest='subparsercmd',
556
+ description='Available inspection sub-commands',
557
+ help='Choose an inspection type'
558
+ )
559
+
560
+ # -------------------------------------------------------------------------
561
+ # RAW RESULTS INSPECTION - Analyse raw scan output
562
+ # -------------------------------------------------------------------------
563
+
564
+ # Raw results parser - handles inspection of unprocessed scan results
565
+ p_inspect_raw = p_inspect_sub.add_parser(
566
+ 'raw',
567
+ description='Inspect and analyse SCANOSS raw scan results',
568
+ help='Analyse raw scan results for various compliance issues'
569
+ )
570
+
571
+ # Raw results sub-commands parser
572
+ p_inspect_raw_sub = p_inspect_raw.add_subparsers(
573
+ title='Raw Results Inspection Commands',
574
+ dest='subparser_subcmd',
575
+ description='Tools for analyzing raw scan results',
576
+ help='Choose a raw results analysis type'
550
577
  )
551
578
 
552
- ####### INSPECT: Copyleft ######
553
- # Inspect Sub-command: inspect copyleft
554
- p_copyleft = p_inspect_sub.add_parser(
555
- 'copyleft', aliases=['cp'], description='Inspect for copyleft licenses', help='Inspect for copyleft licenses'
579
+ # Copyleft license inspection - identifies copyleft license violations
580
+ p_inspect_raw_copyleft = p_inspect_raw_sub.add_parser(
581
+ 'copyleft',
582
+ aliases=['cp'],
583
+ description='Identify components with copyleft licenses that may require compliance action',
584
+ help='Find copyleft license violations'
556
585
  )
557
586
 
558
- ####### INSPECT: License Summary ######
559
- # Inspect Sub-command: inspect license summary
560
- p_license_summary = p_inspect_sub.add_parser(
587
+ # License summary inspection - provides overview of all detected licenses
588
+ p_inspect_raw_license_summary = p_inspect_raw_sub.add_parser(
561
589
  'license-summary',
562
590
  aliases=['lic-summary', 'licsum'],
563
- description='Get license summary',
564
- help='Get detected license summary from scan results',
591
+ description='Generate comprehensive summary of all licenses found in scan results',
592
+ help='Generate license summary report'
565
593
  )
566
594
 
567
- p_component_summary = p_inspect_sub.add_parser(
595
+ # Component summary inspection - provides overview of all detected components
596
+ p_inspect_raw_component_summary = p_inspect_raw_sub.add_parser(
568
597
  'component-summary',
569
598
  aliases=['comp-summary', 'compsum'],
570
- description='Get component summary',
571
- help='Get detected component summary from scan results',
599
+ description='Generate comprehensive summary of all components found in scan results',
600
+ help='Generate component summary report'
601
+ )
602
+
603
+ # Undeclared components inspection - finds components not declared in SBOM
604
+ p_inspect_raw_undeclared = p_inspect_raw_sub.add_parser(
605
+ 'undeclared',
606
+ aliases=['un'],
607
+ description='Identify components present in code but not declared in SBOM files',
608
+ help='Find undeclared components'
609
+ )
610
+ # SBOM format option for undeclared components inspection
611
+ p_inspect_raw_undeclared.add_argument(
612
+ '--sbom-format',
613
+ required=False,
614
+ choices=['legacy', 'settings'],
615
+ default='settings',
616
+ help='SBOM format type for comparison: legacy or settings (default)'
572
617
  )
573
618
 
574
- ####### INSPECT: Undeclared components ######
575
- # Inspect Sub-command: inspect undeclared
576
- p_undeclared = p_inspect_sub.add_parser(
619
+ # -------------------------------------------------------------------------
620
+ # BACKWARD COMPATIBILITY - Support old inspect command format
621
+ # -------------------------------------------------------------------------
622
+
623
+ # Legacy copyleft inspection - backward compatibility for 'scanoss-py inspect copyleft'
624
+ p_inspect_legacy_copyleft = p_inspect_sub.add_parser(
625
+ 'copyleft',
626
+ aliases=['cp'],
627
+ description='Identify components with copyleft licenses that may require compliance action',
628
+ help='Find copyleft license violations (legacy format)'
629
+ )
630
+
631
+ # Legacy undeclared components inspection - backward compatibility for 'scanoss-py inspect undeclared'
632
+ p_inspect_legacy_undeclared = p_inspect_sub.add_parser(
577
633
  'undeclared',
578
634
  aliases=['un'],
579
- description='Inspect for undeclared components',
580
- help='Inspect for undeclared components',
635
+ description='Identify components present in code but not declared in SBOM files',
636
+ help='Find undeclared components (legacy format)'
581
637
  )
582
- p_undeclared.add_argument(
638
+
639
+ # SBOM format option for legacy undeclared components inspection
640
+ p_inspect_legacy_undeclared.add_argument(
583
641
  '--sbom-format',
584
642
  required=False,
585
643
  choices=['legacy', 'settings'],
586
644
  default='settings',
587
- help='Sbom format for status output',
645
+ help='SBOM format type for comparison: legacy or settings (default)'
646
+ )
647
+
648
+ # Legacy license summary inspection - backward compatibility for 'scanoss-py inspect license-summary'
649
+ p_inspect_legacy_license_summary = p_inspect_sub.add_parser(
650
+ 'license-summary',
651
+ aliases=['lic-summary', 'licsum'],
652
+ description='Generate comprehensive summary of all licenses found in scan results',
653
+ help='Generate license summary report (legacy format)'
654
+ )
655
+
656
+ # Legacy component summary inspection - backward compatibility for 'scanoss-py inspect component-summary'
657
+ p_inspect_legacy_component_summary = p_inspect_sub.add_parser(
658
+ 'component-summary',
659
+ aliases=['comp-summary', 'compsum'],
660
+ description='Generate comprehensive summary of all components found in scan results',
661
+ help='Generate component summary report (legacy format)'
588
662
  )
589
663
 
590
- # Add common commands for inspect copyleft and license summary
591
- for p in [p_copyleft, p_license_summary]:
664
+ # Applies the same configuration to both legacy and raw versions
665
+ # License filtering options - common to (legacy) copyleft and license summary commands
666
+ for p in [p_inspect_raw_copyleft, p_inspect_raw_license_summary,
667
+ p_inspect_legacy_copyleft, p_inspect_legacy_license_summary]:
592
668
  p.add_argument(
593
669
  '--include',
594
- help='List of Copyleft licenses to append to the default list. Provide licenses as a comma-separated list.',
670
+ help='Additional licenses to include in analysis (comma-separated list)'
595
671
  )
596
672
  p.add_argument(
597
673
  '--exclude',
598
- help='List of Copyleft licenses to remove from default list. Provide licenses as a comma-separated list.',
674
+ help='Licenses to exclude from analysis (comma-separated list)'
599
675
  )
600
676
  p.add_argument(
601
677
  '--explicit',
602
- help='Explicit list of Copyleft licenses to consider. Provide licenses as a comma-separated list.s',
678
+ help='Use only these specific licenses for analysis (comma-separated list)'
603
679
  )
604
680
 
605
- # Add common commands for inspect copyleft and license summary
606
- for p in [p_license_summary, p_component_summary]:
607
- p.add_argument('-i', '--input', nargs='?', help='Path to results file')
608
- p.add_argument('-o', '--output', type=str, help='Save summary into a file')
681
+ # Common options for (legacy) copyleft and undeclared component inspection
682
+ for p in [p_inspect_raw_copyleft, p_inspect_raw_undeclared, p_inspect_legacy_copyleft, p_inspect_legacy_undeclared]:
683
+ p.add_argument(
684
+ '-i', '--input',
685
+ nargs='?',
686
+ help='Path to scan results file to analyse'
687
+ )
688
+ p.add_argument(
689
+ '-f', '--format',
690
+ required=False,
691
+ choices=['json', 'md', 'jira_md'],
692
+ default='json',
693
+ help='Output format: json (default), md (Markdown), or jira_md (JIRA Markdown)'
694
+ )
695
+ p.add_argument(
696
+ '-o', '--output',
697
+ type=str,
698
+ help='Save detailed results to specified file'
699
+ )
700
+ p.add_argument(
701
+ '-s', '--status',
702
+ type=str,
703
+ help='Save summary status report to Markdown file'
704
+ )
609
705
 
610
- p_undeclared.set_defaults(func=inspect_undeclared)
611
- p_copyleft.set_defaults(func=inspect_copyleft)
612
- p_license_summary.set_defaults(func=inspect_license_summary)
613
- p_component_summary.set_defaults(func=inspect_component_summary)
706
+ # Common options for (legacy) license and component summary commands
707
+ for p in [p_inspect_raw_license_summary, p_inspect_raw_component_summary,
708
+ p_inspect_legacy_license_summary, p_inspect_legacy_component_summary]:
709
+ p.add_argument(
710
+ '-i', '--input',
711
+ nargs='?',
712
+ help='Path to scan results file to analyse'
713
+ )
714
+ p.add_argument(
715
+ '-o', '--output',
716
+ type=str,
717
+ help='Save summary report to specified file'
718
+ )
614
719
 
615
- ########################################### END INSPECT SUBCOMMAND ###########################################
720
+ # -------------------------------------------------------------------------
721
+ # DEPENDENCY TRACK INSPECTION - Analyse Dependency Track project data
722
+ # -------------------------------------------------------------------------
723
+
724
+ # Dependency Track parser - handles inspection of DT project status and violations
725
+ p_dep_track_sub = p_inspect_sub.add_parser(
726
+ 'dependency-track',
727
+ aliases=['dt'],
728
+ description='Inspect and analyse Dependency Track project status and policy violations',
729
+ help='Analyse Dependency Track projects'
730
+ )
731
+
732
+ # Dependency Track sub-commands parser
733
+ p_inspect_dep_track_sub = p_dep_track_sub.add_subparsers(
734
+ title='Dependency Track Inspection Commands',
735
+ dest='subparser_subcmd',
736
+ description='Tools for analysing Dependency Track project data',
737
+ help='Choose a Dependency Track analysis type'
738
+ )
739
+
740
+ # Project violations inspection - analyses policy violations in DT projects
741
+ p_inspect_dt_project_violation = p_inspect_dep_track_sub.add_parser(
742
+ 'project-violations',
743
+ aliases=['pv'],
744
+ description='Analyse policy violations and compliance issues in Dependency Track projects',
745
+ help='Inspect project policy violations'
746
+ )
747
+ # Dependency Track connection and authentication options
748
+ p_inspect_dt_project_violation.add_argument(
749
+ '--url',
750
+ required=True,
751
+ type=str,
752
+ help='Dependency Track server base URL (e.g., https://dtrack.example.com)'
753
+ )
754
+ p_inspect_dt_project_violation.add_argument(
755
+ '--upload-token', '-ut',
756
+ required=False,
757
+ type=str,
758
+ help='Project-specific upload token for accessing DT project data'
759
+ )
760
+ p_inspect_dt_project_violation.add_argument(
761
+ '--project-id', '-pid',
762
+ required=False,
763
+ type=str,
764
+ help='Dependency Track project UUID to inspect'
765
+ )
766
+ p_inspect_dt_project_violation.add_argument(
767
+ '--apikey', '-k',
768
+ required=True,
769
+ type=str,
770
+ help='Dependency Track API key for authentication'
771
+ )
772
+ p_inspect_dt_project_violation.add_argument(
773
+ '--project-name', '-pn',
774
+ required=False,
775
+ type=str,
776
+ help='Dependency Track project name'
777
+ )
778
+ p_inspect_dt_project_violation.add_argument(
779
+ '--project-version', '-pv',
780
+ required=False,
781
+ type=str,
782
+ help='Dependency Track project version'
783
+ )
784
+ p_inspect_dt_project_violation.add_argument(
785
+ '--output', '-o',
786
+ required=False,
787
+ type=str,
788
+ help='Save inspection results to specified file'
789
+ )
790
+ p_inspect_dt_project_violation.add_argument(
791
+ '--status',
792
+ required=False,
793
+ type=str,
794
+ help='Save summary status report to specified file'
795
+ )
796
+ p_inspect_dt_project_violation.add_argument(
797
+ '--format', '-f',
798
+ required=False,
799
+ choices=['json', 'md'],
800
+ default='json',
801
+ help='Output format: json (default) or md (Markdown)'
802
+ )
803
+ p_inspect_dt_project_violation.add_argument(
804
+ '--timeout', '-M',
805
+ required=False,
806
+ default='300',
807
+ help='Timeout (in seconds) for API communication (optional - default 300 sec)'
808
+ )
809
+
810
+ # TODO Move to the command call def location
811
+ # RAW results
812
+ p_inspect_raw_undeclared.set_defaults(func=inspect_undeclared)
813
+ p_inspect_raw_copyleft.set_defaults(func=inspect_copyleft)
814
+ p_inspect_raw_license_summary.set_defaults(func=inspect_license_summary)
815
+ p_inspect_raw_component_summary.set_defaults(func=inspect_component_summary)
816
+ # Legacy backward compatibility commands
817
+ p_inspect_legacy_copyleft.set_defaults(func=inspect_copyleft)
818
+ p_inspect_legacy_undeclared.set_defaults(func=inspect_undeclared)
819
+ p_inspect_legacy_license_summary.set_defaults(func=inspect_license_summary)
820
+ p_inspect_legacy_component_summary.set_defaults(func=inspect_component_summary)
821
+ # Dependency Track
822
+ p_inspect_dt_project_violation.set_defaults(func=inspect_dep_track_project_violations)
823
+
824
+ # =========================================================================
825
+ # END INSPECT SUBCOMMAND CONFIGURATION
826
+ # =========================================================================
616
827
 
617
828
  # Sub-command: export
618
829
  p_export = subparsers.add_parser(
@@ -637,11 +848,12 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
637
848
  help='Upload SBOM files to Dependency Track',
638
849
  )
639
850
  e_dt.add_argument('-i', '--input', type=str, required=True, help='Input SBOM file (CycloneDX JSON format)')
640
- e_dt.add_argument('--dt-url', type=str, required=True, help='Dependency Track base URL')
641
- e_dt.add_argument('--dt-apikey', type=str, required=True, help='Dependency Track API key')
642
- e_dt.add_argument('--dt-projectid', type=str, help='Dependency Track project UUID')
643
- e_dt.add_argument('--dt-projectname', type=str, help='Dependency Track project name')
644
- e_dt.add_argument('--dt-projectversion', type=str, help='Dependency Track project version')
851
+ e_dt.add_argument('--url', type=str, required=True, help='Dependency Track base URL')
852
+ e_dt.add_argument('--apikey', '-k', type=str, required=True, help='Dependency Track API key')
853
+ e_dt.add_argument('--output', '-o', type=str, help='File to save export token and uuid into')
854
+ e_dt.add_argument('--project-id', '-pid', type=str, help='Dependency Track project UUID')
855
+ e_dt.add_argument('--project-name', '-pn', type=str, help='Dependency Track project name')
856
+ e_dt.add_argument('--project-version', '-pv', type=str, help='Dependency Track project version')
645
857
  e_dt.set_defaults(func=export_dt)
646
858
 
647
859
  # Sub-command: folder-scan
@@ -746,19 +958,6 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
746
958
  help='Skip default settings file (scanoss.json) if it exists',
747
959
  )
748
960
 
749
- for p in [p_copyleft, p_undeclared]:
750
- p.add_argument('-i', '--input', nargs='?', help='Path to results file')
751
- p.add_argument(
752
- '-f',
753
- '--format',
754
- required=False,
755
- choices=['json', 'md', 'jira_md'],
756
- default='json',
757
- help='Output format (default: json)',
758
- )
759
- p.add_argument('-o', '--output', type=str, help='Save details into a file')
760
- p.add_argument('-s', '--status', type=str, help='Save summary data into Markdown file')
761
-
762
961
  # Global Scan command options
763
962
  for p in [p_scan, p_cs]:
764
963
  p.add_argument(
@@ -886,10 +1085,15 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
886
1085
  c_versions,
887
1086
  c_semgrep,
888
1087
  p_results,
889
- p_undeclared,
890
- p_copyleft,
891
- p_license_summary,
892
- p_component_summary,
1088
+ p_inspect_raw_undeclared,
1089
+ p_inspect_raw_copyleft,
1090
+ p_inspect_raw_license_summary,
1091
+ p_inspect_raw_component_summary,
1092
+ p_inspect_legacy_copyleft,
1093
+ p_inspect_legacy_undeclared,
1094
+ p_inspect_legacy_license_summary,
1095
+ p_inspect_legacy_component_summary,
1096
+ p_inspect_dt_project_violation,
893
1097
  c_provenance,
894
1098
  p_folder_scan,
895
1099
  p_folder_hash,
@@ -916,6 +1120,9 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
916
1120
  ) and not args.subparsercmd:
917
1121
  parser.parse_args([args.subparser, '--help']) # Force utils helps to be displayed
918
1122
  sys.exit(1)
1123
+ elif (args.subparser in 'inspect') and (args.subparsercmd in ('raw', 'dt')) and (args.subparser_subcmd is None):
1124
+ parser.parse_args([args.subparser, args.subparsercmd, '--help']) # Force utils helps to be displayed
1125
+ sys.exit(1)
919
1126
  args.func(parser, args) # Execute the function associated with the sub-command
920
1127
 
921
1128
 
@@ -949,16 +1156,14 @@ def file_count(parser, args):
949
1156
  print_stderr('Please specify a folder')
950
1157
  parser.parse_args([args.subparser, '-h'])
951
1158
  sys.exit(1)
952
- scan_output: str = None
953
1159
  if args.output:
954
- scan_output = args.output
955
- open(scan_output, 'w').close()
1160
+ initialise_empty_file(args.output)
956
1161
 
957
1162
  counter = FileCount(
958
1163
  debug=args.debug,
959
1164
  quiet=args.quiet,
960
1165
  trace=args.trace,
961
- scan_output=scan_output,
1166
+ scan_output=args.output,
962
1167
  hidden_files_folders=args.all_hidden,
963
1168
  )
964
1169
  if not os.path.exists(args.scan_dir):
@@ -987,10 +1192,8 @@ def wfp(parser, args):
987
1192
  sys.exit(1)
988
1193
  if args.strip_hpsm and not args.hpsm and not args.quiet:
989
1194
  print_stderr('Warning: --strip-hpsm option supplied without enabling HPSM (--hpsm). Ignoring.')
990
- scan_output: str = None
991
1195
  if args.output:
992
- scan_output = args.output
993
- open(scan_output, 'w').close()
1196
+ initialise_empty_file(args.output)
994
1197
 
995
1198
  # Load scan settings
996
1199
  scan_settings = None
@@ -1023,15 +1226,15 @@ def wfp(parser, args):
1023
1226
  )
1024
1227
  if args.stdin:
1025
1228
  contents = sys.stdin.buffer.read()
1026
- scanner.wfp_contents(args.stdin, contents, scan_output)
1229
+ scanner.wfp_contents(args.stdin, contents, args.output)
1027
1230
  elif args.scan_dir:
1028
1231
  if not os.path.exists(args.scan_dir):
1029
1232
  print_stderr(f'Error: File or folder specified does not exist: {args.scan_dir}.')
1030
1233
  sys.exit(1)
1031
1234
  if os.path.isdir(args.scan_dir):
1032
- scanner.wfp_folder(args.scan_dir, scan_output)
1235
+ scanner.wfp_folder(args.scan_dir, args.output)
1033
1236
  elif os.path.isfile(args.scan_dir):
1034
- scanner.wfp_file(args.scan_dir, scan_output)
1237
+ scanner.wfp_file(args.scan_dir, args.output)
1035
1238
  else:
1036
1239
  print_stderr(f'Error: Path specified is neither a file or a folder: {args.scan_dir}.')
1037
1240
  sys.exit(1)
@@ -1128,10 +1331,8 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
1128
1331
  if args.strip_hpsm and not args.hpsm and not args.quiet:
1129
1332
  print_stderr('Warning: --strip-hpsm option supplied without enabling HPSM (--hpsm). Ignoring.')
1130
1333
 
1131
- scan_output: str = None
1132
1334
  if args.output:
1133
- scan_output = args.output
1134
- open(scan_output, 'w').close()
1335
+ initialise_empty_file(args.output)
1135
1336
  output_format = args.format if args.format else 'plain'
1136
1337
  flags = args.flags if args.flags else None
1137
1338
  if args.debug and not args.quiet:
@@ -1186,7 +1387,7 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
1186
1387
  quiet=args.quiet,
1187
1388
  api_key=args.key,
1188
1389
  url=args.apiurl,
1189
- scan_output=scan_output,
1390
+ scan_output=args.output,
1190
1391
  output_format=output_format,
1191
1392
  flags=flags,
1192
1393
  nb_threads=args.threads,
@@ -1298,16 +1499,15 @@ def dependency(parser, args):
1298
1499
  if not os.path.exists(args.scan_loc):
1299
1500
  print_stderr(f'Error: File or folder specified does not exist: {args.scan_loc}.')
1300
1501
  sys.exit(1)
1301
- scan_output: str = None
1302
1502
  if args.output:
1303
- scan_output = args.output
1304
- open(scan_output, 'w').close()
1503
+ initialise_empty_file(args.output)
1305
1504
 
1306
1505
  sc_deps = ScancodeDeps(
1307
1506
  debug=args.debug, quiet=args.quiet, trace=args.trace, sc_command=args.sc_command, timeout=args.sc_timeout
1308
1507
  )
1309
- if not sc_deps.get_dependencies(what_to_scan=args.scan_loc, result_output=scan_output):
1508
+ if not sc_deps.get_dependencies(what_to_scan=args.scan_loc, result_output=args.output):
1310
1509
  sys.exit(1)
1510
+ return None
1311
1511
 
1312
1512
 
1313
1513
  def convert(parser, args):
@@ -1346,179 +1546,340 @@ def convert(parser, args):
1346
1546
  sys.exit(1)
1347
1547
 
1348
1548
 
1349
- ################################ INSPECT handlers ################################
1549
+ # =============================================================================
1550
+ # INSPECT COMMAND HANDLERS - Functions that execute inspection operations
1551
+ # =============================================================================
1552
+
1350
1553
  def inspect_copyleft(parser, args):
1351
1554
  """
1352
- Run the "inspect" sub-command
1555
+ Handle copyleft license inspection command.
1556
+
1557
+ Analyses scan results to identify components using copyleft licenses
1558
+ that may require compliance actions such as source code disclosure.
1559
+
1353
1560
  Parameters
1354
1561
  ----------
1355
- parser: ArgumentParser
1356
- command line parser object
1357
- args: Namespace
1358
- Parsed arguments
1359
- """
1562
+ parser : ArgumentParser
1563
+ Command line parser object for help display
1564
+ args : Namespace
1565
+ Parsed command line arguments containing:
1566
+ - input: Path to scan results file
1567
+ - output: Optional output file path
1568
+ - status: Optional status summary file path
1569
+ - format: Output format (json, md, jira_md)
1570
+ - include/exclude/explicit: License filter options
1571
+ """
1572
+ # Validate required input file parameter
1360
1573
  if args.input is None:
1361
- print_stderr('Please specify an input file to inspect')
1362
- parser.parse_args([args.subparser, args.subparsercmd, '-h'])
1574
+ print_stderr('ERROR: Input file is required for copyleft inspection')
1575
+ parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
1363
1576
  sys.exit(1)
1364
- output: str = None
1577
+ # Initialise output file if specified
1365
1578
  if args.output:
1366
- output = args.output
1367
- open(output, 'w').close()
1368
-
1369
- status_output: str = None
1579
+ initialise_empty_file(args.output)
1580
+ # Initialise status summary file if specified
1370
1581
  if args.status:
1371
- status_output = args.status
1372
- open(status_output, 'w').close()
1582
+ initialise_empty_file(args.status)
1583
+ try:
1584
+ # Create and configure copyleft inspector
1585
+ i_copyleft = Copyleft(
1586
+ debug=args.debug,
1587
+ trace=args.trace,
1588
+ quiet=args.quiet,
1589
+ filepath=args.input,
1590
+ format_type=args.format,
1591
+ status=args.status,
1592
+ output=args.output,
1593
+ include=args.include, # Additional licenses to check
1594
+ exclude=args.exclude, # Licenses to ignore
1595
+ explicit=args.explicit, # Explicit license list
1596
+ )
1373
1597
 
1374
- i_copyleft = Copyleft(
1375
- debug=args.debug,
1376
- trace=args.trace,
1377
- quiet=args.quiet,
1378
- filepath=args.input,
1379
- format_type=args.format,
1380
- status=status_output,
1381
- output=output,
1382
- include=args.include,
1383
- exclude=args.exclude,
1384
- explicit=args.explicit,
1385
- )
1386
- status, _ = i_copyleft.run()
1387
- sys.exit(status)
1598
+ # Execute inspection and exit with appropriate status code
1599
+ status, _ = i_copyleft.run()
1600
+ sys.exit(status)
1601
+ except Exception as e:
1602
+ print_stderr(e)
1603
+ if args.debug:
1604
+ traceback.print_exc()
1605
+ sys.exit(1)
1388
1606
 
1389
1607
 
1390
1608
  def inspect_undeclared(parser, args):
1391
1609
  """
1392
- Run the "inspect" sub-command
1610
+ Handle undeclared components inspection command.
1611
+
1612
+ Analyses scan results to identify components that are present in the
1613
+ codebase but not declared in SBOM or manifest files, which may indicate
1614
+ security or compliance risks.
1615
+
1393
1616
  Parameters
1394
1617
  ----------
1395
- parser: ArgumentParser
1396
- command line parser object
1397
- args: Namespace
1398
- Parsed arguments
1399
- """
1618
+ parser : ArgumentParser
1619
+ Command line parser object for help display
1620
+ args : Namespace
1621
+ Parsed command line arguments containing:
1622
+ - input: Path to scan results file
1623
+ - output: Optional output file path
1624
+ - status: Optional status summary file path
1625
+ - format: Output format (json, md, jira_md)
1626
+ - sbom_format: SBOM format type (legacy, settings)
1627
+ """
1628
+ # Validate required input file parameter
1400
1629
  if args.input is None:
1401
- print_stderr('Please specify an input file to inspect')
1402
- parser.parse_args([args.subparser, args.subparsercmd, '-h'])
1630
+ print_stderr('ERROR: Input file is required for undeclared component inspection')
1631
+ parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
1403
1632
  sys.exit(1)
1404
- output: str = None
1633
+
1634
+ # Initialise output file if specified
1405
1635
  if args.output:
1406
- output = args.output
1407
- open(output, 'w').close()
1636
+ initialise_empty_file(args.output)
1408
1637
 
1409
- status_output: str = None
1638
+ # Initialise status summary file if specified
1410
1639
  if args.status:
1411
- status_output = args.status
1412
- open(status_output, 'w').close()
1413
- i_undeclared = UndeclaredComponent(
1414
- debug=args.debug,
1415
- trace=args.trace,
1416
- quiet=args.quiet,
1417
- filepath=args.input,
1418
- format_type=args.format,
1419
- status=status_output,
1420
- output=output,
1421
- sbom_format=args.sbom_format,
1422
- )
1423
- status, _ = i_undeclared.run()
1424
- sys.exit(status)
1640
+ initialise_empty_file(args.status)
1641
+
1642
+ try:
1643
+ # Create and configure undeclared component inspector
1644
+ i_undeclared = UndeclaredComponent(
1645
+ debug=args.debug,
1646
+ trace=args.trace,
1647
+ quiet=args.quiet,
1648
+ filepath=args.input,
1649
+ format_type=args.format,
1650
+ status=args.status,
1651
+ output=args.output,
1652
+ sbom_format=args.sbom_format, # Format for SBOM comparison
1653
+ )
1654
+
1655
+ # Execute inspection and exit with appropriate status code
1656
+ status, _ = i_undeclared.run()
1657
+ sys.exit(status)
1658
+ except Exception as e:
1659
+ print_stderr(e)
1660
+ if args.debug:
1661
+ traceback.print_exc()
1662
+ sys.exit(1)
1425
1663
 
1426
1664
 
1427
1665
  def inspect_license_summary(parser, args):
1428
1666
  """
1429
- Run the "inspect" sub-command
1667
+ Handle license summary inspection command.
1668
+
1669
+ Generates comprehensive summary of all licenses detected in scan results,
1670
+ including license counts, risk levels, and compliance recommendations.
1671
+
1430
1672
  Parameters
1431
1673
  ----------
1432
- parser: ArgumentParser
1433
- command line parser object
1434
- args: Namespace
1435
- Parsed arguments
1436
- """
1674
+ parser : ArgumentParser
1675
+ Command line parser object for help display
1676
+ args : Namespace
1677
+ Parsed command line arguments containing:
1678
+ - input: Path to scan results file
1679
+ - output: Optional output file path
1680
+ - include/exclude/explicit: License filter options
1681
+ """
1682
+ # Validate required input file parameter
1437
1683
  if args.input is None:
1438
- print_stderr('Please specify an input file to inspect')
1439
- parser.parse_args([args.subparser, args.subparsercmd, '-h'])
1684
+ print_stderr('ERROR: Input file is required for license summary')
1685
+ parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
1440
1686
  sys.exit(1)
1441
- output: str = None
1687
+
1688
+ # Initialise output file if specified
1442
1689
  if args.output:
1443
- output = args.output
1444
- open(output, 'w').close()
1690
+ initialise_empty_file(args.output)
1445
1691
 
1692
+ # Create and configure license summary generator
1446
1693
  i_license_summary = LicenseSummary(
1447
1694
  debug=args.debug,
1448
1695
  trace=args.trace,
1449
1696
  quiet=args.quiet,
1450
1697
  filepath=args.input,
1451
- output=output,
1452
- include=args.include,
1453
- exclude=args.exclude,
1454
- explicit=args.explicit,
1698
+ output=args.output,
1699
+ include=args.include, # Additional licenses to include
1700
+ exclude=args.exclude, # Licenses to exclude from summary
1701
+ explicit=args.explicit, # Explicit license list to summarize
1455
1702
  )
1456
- i_license_summary.run()
1457
-
1703
+ try:
1704
+ # Execute summary generation
1705
+ i_license_summary.run()
1706
+ except Exception as e:
1707
+ print_stderr(e)
1708
+ if args.debug:
1709
+ traceback.print_exc()
1710
+ sys.exit(1)
1458
1711
 
1459
1712
  def inspect_component_summary(parser, args):
1460
1713
  """
1461
- Run the "inspect" sub-command
1714
+ Handle component summary inspection command.
1715
+
1716
+ Generates a comprehensive summary of all components detected in scan results,
1717
+ including component counts, versions, match types, and security information.
1718
+
1462
1719
  Parameters
1463
1720
  ----------
1464
- parser: ArgumentParser
1465
- command line parser object
1466
- args: Namespace
1467
- Parsed arguments
1468
- """
1721
+ parser : ArgumentParser
1722
+ Command line parser object for help display
1723
+ args : Namespace
1724
+ Parsed command line arguments containing:
1725
+ - input: Path to scan results file
1726
+ - output: Optional output file path
1727
+ """
1728
+ # Validate required input file parameter
1469
1729
  if args.input is None:
1470
- print_stderr('Please specify an input file to inspect')
1471
- parser.parse_args([args.subparser, args.subparsercmd, '-h'])
1730
+ print_stderr('ERROR: Input file is required for component summary')
1731
+ parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
1472
1732
  sys.exit(1)
1473
- output: str = None
1733
+
1734
+ # Initialise an output file if specified
1474
1735
  if args.output:
1475
- output = args.output
1476
- open(output, 'w').close()
1736
+ initialise_empty_file(args.output) # Create/clear output file
1477
1737
 
1738
+ # Create and configure component summary generator
1478
1739
  i_component_summary = ComponentSummary(
1479
1740
  debug=args.debug,
1480
1741
  trace=args.trace,
1481
1742
  quiet=args.quiet,
1482
1743
  filepath=args.input,
1483
- output=output,
1744
+ output=args.output,
1484
1745
  )
1485
- i_component_summary.run()
1486
-
1487
-
1488
- ################################ End inspect handlers ################################
1489
1746
 
1747
+ try:
1748
+ # Execute summary generation
1749
+ i_component_summary.run()
1750
+ except Exception as e:
1751
+ print_stderr(e)
1752
+ if args.debug:
1753
+ traceback.print_exc()
1754
+ sys.exit(1)
1490
1755
 
1491
- def export_dt(parser, args):
1756
+ def inspect_dep_track_project_violations(parser, args):
1492
1757
  """
1493
- Run the "export dt" sub-command
1758
+ Handle Dependency Track project inspection command.
1759
+
1760
+ Analyses Dependency Track projects for policy violations, security issues,
1761
+ and compliance status. Connects to DT API to retrieve project data and
1762
+ generate detailed violation reports.
1763
+
1494
1764
  Parameters
1495
1765
  ----------
1496
- parser: ArgumentParser
1497
- command line parser object
1498
- args: Namespace
1499
- Parsed arguments
1766
+ parser : ArgumentParser
1767
+ Command line parser object for help display
1768
+ args : Namespace
1769
+ Parsed command line arguments containing:
1770
+ - url: Dependency Track base URL
1771
+ - apikey: API key for authentication
1772
+ - project_id: Project UUID to inspect
1773
+ - project_name: Project name to inspect
1774
+ - project_version: Project version to inspect
1775
+ - upload_token: Upload token for project access
1776
+ - output: Optional output file path
1777
+ - format: Output format (json, md)
1778
+ - timeout: Optional timeout for API requests
1779
+
1780
+ """
1781
+ # Make sure we have project id/project name and version
1782
+ _dt_args_validator(parser, args)
1783
+ # Initialise the output file if specified
1784
+ if args.output:
1785
+ initialise_empty_file(args.output)
1786
+ # Create and configure Dependency Track inspector
1787
+ try:
1788
+ dt_proj_violations = DependencyTrackProjectViolationPolicyCheck(
1789
+ debug=args.debug,
1790
+ trace=args.trace,
1791
+ quiet=args.quiet,
1792
+ output=args.output,
1793
+ status= args.status,
1794
+ format_type=args.format,
1795
+ url=args.url, # DT server URL
1796
+ api_key=args.apikey, # Authentication key
1797
+ project_id=args.project_id, # Target project UUID
1798
+ upload_token=args.upload_token, # Upload access token
1799
+ project_name=args.project_name, # DT project name
1800
+ project_version=args.project_version, # DT project version
1801
+ timeout=args.timeout,
1802
+ )
1803
+
1804
+ # Execute inspection and exit with appropriate status code
1805
+ status, _ = dt_proj_violations.run() #TODO remove datastructure from return
1806
+ sys.exit(status)
1807
+ except Exception as e:
1808
+ print_stderr(e)
1809
+ if args.debug:
1810
+ traceback.print_exc()
1811
+ sys.exit(1)
1812
+
1813
+
1814
+ # =============================================================================
1815
+ # END INSPECT COMMAND HANDLERS
1816
+ # =============================================================================
1817
+
1818
+ def export_dt(parser, args):
1500
1819
  """
1820
+ Validates and exports a Software Bill of Materials (SBOM) to a Dependency-Track server.
1821
+
1822
+ Parameters:
1823
+ parser (argparse.ArgumentParser): The argument parser to validate input arguments.
1824
+ args (argparse.Namespace): Parsed arguments passed to the command.
1501
1825
 
1826
+ Raises:
1827
+ SystemExit: If argument validation fails or uploading the SBOM to the Dependency-Track server
1828
+ is unsuccessful.
1829
+ """
1830
+ # Make sure we have project id/project name and version
1831
+ _dt_args_validator(parser, args)
1832
+ if args.output:
1833
+ initialise_empty_file(args.output)
1834
+ if not args.quiet:
1835
+ print_stderr(f'Outputting export data result to: {args.output}')
1502
1836
  try:
1503
- config = create_dependency_track_exporter_config_from_args(args)
1504
1837
  dt_exporter = DependencyTrackExporter(
1505
- config=config,
1838
+ url=args.url,
1839
+ apikey=args.apikey,
1840
+ output=args.output,
1506
1841
  debug=args.debug,
1507
1842
  trace=args.trace,
1508
1843
  quiet=args.quiet,
1509
1844
  )
1510
-
1511
- success = dt_exporter.upload_sbom(args.input)
1512
-
1845
+ success = dt_exporter.upload_sbom_file(args.input, args.project_id, args.project_name,
1846
+ args.project_version, args.output)
1513
1847
  if not success:
1514
1848
  sys.exit(1)
1515
-
1516
1849
  except Exception as e:
1517
1850
  print_stderr(f'ERROR: {e}')
1518
1851
  if args.debug:
1519
1852
  traceback.print_exc()
1520
1853
  sys.exit(1)
1521
1854
 
1855
+ def _dt_args_validator(parser, args):
1856
+ """
1857
+ Validates command-line arguments related to project identification.
1858
+
1859
+ Parameters
1860
+ ----------
1861
+ parser : argparse.ArgumentParser
1862
+ An argument parser instance for handling command-line arguments.
1863
+ args : argparse.Namespace
1864
+ Parsed arguments from the command line containing project-related information.
1865
+
1866
+ Raises
1867
+ ------
1868
+ SystemExit
1869
+ If neither a project ID nor the required combination of project name and
1870
+ project version is provided, or if any of the compulsory arguments
1871
+ are missing.
1872
+ """
1873
+ if not args.project_id and not args.project_name and not args.project_version:
1874
+ print_stderr(
1875
+ 'Please specify either a project ID (--project-id) or a project name (--project-name) and '
1876
+ 'version (--project-version)'
1877
+ )
1878
+ parser.parse_args([args.subparser, '-h'])
1879
+ sys.exit(1)
1880
+ if not args.project_id and (not args.project_name or not args.project_version):
1881
+ print_stderr('Please supply a project name (--project-name) and version (--project-version)')
1882
+ sys.exit(1)
1522
1883
 
1523
1884
  def utils_certloc(*_):
1524
1885
  """
@@ -2123,7 +2484,31 @@ def get_scanoss_settings_from_args(args):
2123
2484
  except ScanossSettingsError as e:
2124
2485
  print_stderr(f'Error: {e}')
2125
2486
  sys.exit(1)
2126
- return scanoss_settings
2487
+ return scanoss_settings
2488
+
2489
+
2490
+ def initialise_empty_file(filename: str):
2491
+ """
2492
+ Initialises an empty file with the specified name. If the file already exists,
2493
+ it truncates its content. Ensures proper error handling in case of failure.
2494
+
2495
+ Args:
2496
+ filename (str): The name of the file to be initialised.
2497
+
2498
+ Raises:
2499
+ SystemExit: If the file cannot be created or written due to an exception,
2500
+ the function prints an error message and exits the program.
2501
+
2502
+ Note:
2503
+ This function writes an empty file and handles exceptions to ensure the
2504
+ program does not continue execution in case of an error.
2505
+ """
2506
+ if filename:
2507
+ try:
2508
+ open(filename, 'w').close()
2509
+ except Exception as e:
2510
+ print_stderr(f'Error: Unable to create output file {filename}: {e}')
2511
+ sys.exit(1)
2127
2512
 
2128
2513
 
2129
2514
  def main():