scanoss 1.28.3__tar.gz → 1.30.0__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.
Files changed (102) hide show
  1. {scanoss-1.28.3 → scanoss-1.30.0}/PACKAGE.md +1 -1
  2. {scanoss-1.28.3/src/scanoss.egg-info → scanoss-1.30.0}/PKG-INFO +4 -3
  3. {scanoss-1.28.3 → scanoss-1.30.0}/README.md +1 -1
  4. {scanoss-1.28.3 → scanoss-1.30.0}/pyproject.toml +2 -2
  5. {scanoss-1.28.3 → scanoss-1.30.0}/setup.cfg +2 -1
  6. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/__init__.py +1 -1
  7. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/cli.py +99 -21
  8. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/cyclonedx.py +45 -28
  9. scanoss-1.30.0/src/scanoss/data/build_date.txt +1 -0
  10. scanoss-1.30.0/src/scanoss/export/dependency_track.py +221 -0
  11. scanoss-1.30.0/src/scanoss/scanners/__init__.py +23 -0
  12. {scanoss-1.28.3 → scanoss-1.30.0/src/scanoss.egg-info}/PKG-INFO +4 -3
  13. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss.egg-info/SOURCES.txt +2 -0
  14. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss.egg-info/requires.txt +1 -0
  15. scanoss-1.28.3/src/scanoss/data/build_date.txt +0 -1
  16. {scanoss-1.28.3 → scanoss-1.30.0}/LICENSE +0 -0
  17. {scanoss-1.28.3 → scanoss-1.30.0}/src/protoc_gen_swagger/__init__.py +0 -0
  18. {scanoss-1.28.3 → scanoss-1.30.0}/src/protoc_gen_swagger/options/__init__.py +0 -0
  19. {scanoss-1.28.3 → scanoss-1.30.0}/src/protoc_gen_swagger/options/annotations_pb2.py +0 -0
  20. {scanoss-1.28.3 → scanoss-1.30.0}/src/protoc_gen_swagger/options/annotations_pb2_grpc.py +0 -0
  21. {scanoss-1.28.3 → scanoss-1.30.0}/src/protoc_gen_swagger/options/openapiv2_pb2.py +0 -0
  22. {scanoss-1.28.3 → scanoss-1.30.0}/src/protoc_gen_swagger/options/openapiv2_pb2_grpc.py +0 -0
  23. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/__init__.py +0 -0
  24. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/common/__init__.py +0 -0
  25. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/common/v2/__init__.py +0 -0
  26. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/common/v2/scanoss_common_pb2.py +0 -0
  27. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/common/v2/scanoss_common_pb2_grpc.py +0 -0
  28. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/components/__init__.py +0 -0
  29. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/components/v2/__init__.py +0 -0
  30. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/components/v2/scanoss_components_pb2.py +0 -0
  31. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/components/v2/scanoss_components_pb2_grpc.py +0 -0
  32. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +0 -0
  33. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +0 -0
  34. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/dependencies/__init__.py +0 -0
  35. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/dependencies/v2/__init__.py +0 -0
  36. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +0 -0
  37. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +0 -0
  38. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/geoprovenance/__init__.py +0 -0
  39. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/geoprovenance/v2/__init__.py +0 -0
  40. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +0 -0
  41. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +0 -0
  42. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/scanning/__init__.py +0 -0
  43. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/scanning/v2/__init__.py +0 -0
  44. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2.py +0 -0
  45. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +0 -0
  46. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/semgrep/__init__.py +0 -0
  47. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/semgrep/v2/__init__.py +0 -0
  48. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +0 -0
  49. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +0 -0
  50. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/vulnerabilities/__init__.py +0 -0
  51. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/vulnerabilities/v2/__init__.py +0 -0
  52. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +0 -0
  53. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +0 -0
  54. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/components.py +0 -0
  55. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/constants.py +0 -0
  56. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/cryptography.py +0 -0
  57. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/csvoutput.py +0 -0
  58. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/data/scanoss-settings-schema.json +0 -0
  59. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/data/spdx-exceptions.json +0 -0
  60. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/data/spdx-licenses.json +0 -0
  61. {scanoss-1.28.3/src/scanoss/inspection → scanoss-1.30.0/src/scanoss/export}/__init__.py +0 -0
  62. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/file_filters.py +0 -0
  63. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/filecount.py +0 -0
  64. {scanoss-1.28.3/src/scanoss/scanners → scanoss-1.30.0/src/scanoss/inspection}/__init__.py +0 -0
  65. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/inspection/component_summary.py +0 -0
  66. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/inspection/copyleft.py +0 -0
  67. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/inspection/inspect_base.py +0 -0
  68. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/inspection/license_summary.py +0 -0
  69. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/inspection/policy_check.py +0 -0
  70. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/inspection/undeclared_component.py +0 -0
  71. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/inspection/utils/license_utils.py +0 -0
  72. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/results.py +0 -0
  73. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/scancodedeps.py +0 -0
  74. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/scanner.py +0 -0
  75. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/scanners/container_scanner.py +0 -0
  76. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/scanners/folder_hasher.py +0 -0
  77. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/scanners/scanner_config.py +0 -0
  78. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/scanners/scanner_hfh.py +0 -0
  79. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/scanoss_settings.py +0 -0
  80. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/scanossapi.py +0 -0
  81. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/scanossbase.py +0 -0
  82. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/scanossgrpc.py +0 -0
  83. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/scanpostprocessor.py +0 -0
  84. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/scantype.py +0 -0
  85. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/spdxlite.py +0 -0
  86. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/threadeddependencies.py +0 -0
  87. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/threadedscanning.py +0 -0
  88. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/utils/__init__.py +0 -0
  89. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/utils/abstract_presenter.py +0 -0
  90. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/utils/crc64.py +0 -0
  91. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/utils/file.py +0 -0
  92. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/utils/simhash.py +0 -0
  93. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss/winnowing.py +0 -0
  94. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss.egg-info/dependency_links.txt +0 -0
  95. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss.egg-info/entry_points.txt +0 -0
  96. {scanoss-1.28.3 → scanoss-1.30.0}/src/scanoss.egg-info/top_level.txt +0 -0
  97. {scanoss-1.28.3 → scanoss-1.30.0}/tests/test_csv_output.py +0 -0
  98. {scanoss-1.28.3 → scanoss-1.30.0}/tests/test_file_filters.py +0 -0
  99. {scanoss-1.28.3 → scanoss-1.30.0}/tests/test_policy_inspect.py +0 -0
  100. {scanoss-1.28.3 → scanoss-1.30.0}/tests/test_scan_post_processor.py +0 -0
  101. {scanoss-1.28.3 → scanoss-1.30.0}/tests/test_spdxlite.py +0 -0
  102. {scanoss-1.28.3 → scanoss-1.30.0}/tests/test_winnowing.py +0 -0
@@ -138,7 +138,7 @@ if __name__ == "__main__":
138
138
  ```
139
139
 
140
140
  ## Requirements
141
- Python 3.7 or higher.
141
+ Python 3.9 or higher.
142
142
 
143
143
  ## Source code
144
144
  The source for this package can be found [here](https://github.com/scanoss/scanoss.py).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scanoss
3
- Version: 1.28.3
3
+ Version: 1.30.0
4
4
  Summary: Simple Python library to leverage the SCANOSS APIs
5
5
  Home-page: https://scanoss.com
6
6
  Author: SCANOSS
@@ -13,7 +13,7 @@ Classifier: License :: OSI Approved :: MIT License
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Development Status :: 5 - Production/Stable
15
15
  Classifier: Programming Language :: Python :: 3
16
- Requires-Python: >=3.7
16
+ Requires-Python: >=3.9
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: requests
@@ -30,6 +30,7 @@ Requires-Dist: packageurl-python
30
30
  Requires-Dist: pathspec
31
31
  Requires-Dist: jsonschema
32
32
  Requires-Dist: crc
33
+ Requires-Dist: cyclonedx-python-lib[validation]
33
34
  Provides-Extra: fast-winnowing
34
35
  Requires-Dist: scanoss_winnowing>=0.5.0; extra == "fast-winnowing"
35
36
  Dynamic: license-file
@@ -174,7 +175,7 @@ if __name__ == "__main__":
174
175
  ```
175
176
 
176
177
  ## Requirements
177
- Python 3.7 or higher.
178
+ Python 3.9 or higher.
178
179
 
179
180
  ## Source code
180
181
  The source for this package can be found [here](https://github.com/scanoss/scanoss.py).
@@ -24,7 +24,7 @@ To leverage the CLI from within a container, please look at [GHCR.md](GHCR.md).
24
24
  Before starting with development of this project, please read our [CONTRIBUTING](CONTRIBUTING.md) and [CODE OF CONDUCT](CODE_OF_CONDUCT.md).
25
25
 
26
26
  ### Requirements
27
- Python 3.7 or higher.
27
+ Python 3.9 or higher.
28
28
 
29
29
  The dependencies can be found in the [requirements.txt](requirements.txt) and [requirements-dev.txt](requirements-dev.txt) files.
30
30
 
@@ -6,8 +6,8 @@ build-backend = "setuptools.build_meta"
6
6
  # Enable pycodestyle (E), pyflakes (F), isort (I), pylint (PL)
7
7
  select = ["E", "F", "I", "PL"]
8
8
  line-length = 120
9
- # Assume Python 3.7+
10
- target-version = "py37"
9
+ # Assume Python 3.9+
10
+ target-version = "py39"
11
11
  exclude = [
12
12
  "tests/*",
13
13
  "test_*.py",
@@ -23,7 +23,7 @@ packages = find_namespace:
23
23
  package_dir =
24
24
  = src
25
25
  include_package_data = True
26
- python_requires = >=3.7
26
+ python_requires = >=3.9
27
27
  install_requires =
28
28
  requests
29
29
  crc32c>=2.2
@@ -39,6 +39,7 @@ install_requires =
39
39
  pathspec
40
40
  jsonschema
41
41
  crc
42
+ cyclonedx-python-lib[validation]
42
43
 
43
44
  [options.extras_require]
44
45
  fast_winnowing =
@@ -22,4 +22,4 @@ SPDX-License-Identifier: MIT
22
22
  THE SOFTWARE.
23
23
  """
24
24
 
25
- __version__ = '1.28.3'
25
+ __version__ = '1.30.0'
@@ -25,6 +25,7 @@ SPDX-License-Identifier: MIT
25
25
  import argparse
26
26
  import os
27
27
  import sys
28
+ import traceback
28
29
  from dataclasses import asdict
29
30
  from pathlib import Path
30
31
  from typing import List
@@ -32,6 +33,10 @@ from typing import List
32
33
  import pypac
33
34
 
34
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
+ )
35
40
  from scanoss.inspection.component_summary import ComponentSummary
36
41
  from scanoss.inspection.license_summary import LicenseSummary
37
42
  from scanoss.scanners.container_scanner import (
@@ -553,13 +558,17 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
553
558
  ####### INSPECT: License Summary ######
554
559
  # Inspect Sub-command: inspect license summary
555
560
  p_license_summary = p_inspect_sub.add_parser(
556
- 'license-summary', aliases=['lic-summary', 'licsum'], description='Get license summary',
557
- help='Get detected license summary from scan results'
561
+ 'license-summary',
562
+ aliases=['lic-summary', 'licsum'],
563
+ description='Get license summary',
564
+ help='Get detected license summary from scan results',
558
565
  )
559
566
 
560
567
  p_component_summary = p_inspect_sub.add_parser(
561
- 'component-summary', aliases=['comp-summary', 'compsum'], description='Get component summary',
562
- help='Get detected component summary from scan results'
568
+ 'component-summary',
569
+ aliases=['comp-summary', 'compsum'],
570
+ description='Get component summary',
571
+ help='Get detected component summary from scan results',
563
572
  )
564
573
 
565
574
  ####### INSPECT: Undeclared components ######
@@ -605,6 +614,36 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
605
614
 
606
615
  ########################################### END INSPECT SUBCOMMAND ###########################################
607
616
 
617
+ # Sub-command: export
618
+ p_export = subparsers.add_parser(
619
+ 'export',
620
+ aliases=['exp'],
621
+ description=f'Export SBOM files to external platforms: {__version__}',
622
+ help='Export SBOM files to external platforms',
623
+ )
624
+
625
+ export_sub = p_export.add_subparsers(
626
+ title='Export Commands',
627
+ dest='subparsercmd',
628
+ description='export sub-commands',
629
+ help='export sub-commands',
630
+ )
631
+
632
+ # Export Sub-command: export dt (Dependency Track)
633
+ e_dt = export_sub.add_parser(
634
+ 'dt',
635
+ aliases=['dependency-track'],
636
+ description='Export SBOM to Dependency Track',
637
+ help='Upload SBOM files to Dependency Track',
638
+ )
639
+ 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')
645
+ e_dt.set_defaults(func=export_dt)
646
+
608
647
  # Sub-command: folder-scan
609
648
  p_folder_scan = subparsers.add_parser(
610
649
  'folder-scan',
@@ -858,6 +897,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
858
897
  p_crypto_algorithms,
859
898
  p_crypto_hints,
860
899
  p_crypto_versions_in_range,
900
+ e_dt,
861
901
  ]:
862
902
  p.add_argument('--debug', '-d', action='store_true', help='Enable debug messages')
863
903
  p.add_argument('--trace', '-t', action='store_true', help='Enable trace messages, including API posts')
@@ -871,7 +911,8 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
871
911
  parser.print_help() # No sub command subcommand, print general help
872
912
  sys.exit(1)
873
913
  elif (
874
- args.subparser in ('utils', 'ut', 'component', 'comp', 'inspect', 'insp', 'ins', 'crypto', 'cr')
914
+ args.subparser
915
+ in ('utils', 'ut', 'component', 'comp', 'inspect', 'insp', 'ins', 'crypto', 'cr', 'export', 'exp')
875
916
  ) and not args.subparsercmd:
876
917
  parser.parse_args([args.subparser, '--help']) # Force utils helps to be displayed
877
918
  sys.exit(1)
@@ -1304,6 +1345,7 @@ def convert(parser, args):
1304
1345
  if not success:
1305
1346
  sys.exit(1)
1306
1347
 
1348
+
1307
1349
  ################################ INSPECT handlers ################################
1308
1350
  def inspect_copyleft(parser, args):
1309
1351
  """
@@ -1381,16 +1423,17 @@ def inspect_undeclared(parser, args):
1381
1423
  status, _ = i_undeclared.run()
1382
1424
  sys.exit(status)
1383
1425
 
1426
+
1384
1427
  def inspect_license_summary(parser, args):
1385
1428
  """
1386
- Run the "inspect" sub-command
1387
- Parameters
1388
- ----------
1389
- parser: ArgumentParser
1390
- command line parser object
1391
- args: Namespace
1392
- Parsed arguments
1393
- """
1429
+ Run the "inspect" sub-command
1430
+ Parameters
1431
+ ----------
1432
+ parser: ArgumentParser
1433
+ command line parser object
1434
+ args: Namespace
1435
+ Parsed arguments
1436
+ """
1394
1437
  if args.input is None:
1395
1438
  print_stderr('Please specify an input file to inspect')
1396
1439
  parser.parse_args([args.subparser, args.subparsercmd, '-h'])
@@ -1412,16 +1455,17 @@ def inspect_license_summary(parser, args):
1412
1455
  )
1413
1456
  i_license_summary.run()
1414
1457
 
1458
+
1415
1459
  def inspect_component_summary(parser, args):
1416
1460
  """
1417
- Run the "inspect" sub-command
1418
- Parameters
1419
- ----------
1420
- parser: ArgumentParser
1421
- command line parser object
1422
- args: Namespace
1423
- Parsed arguments
1424
- """
1461
+ Run the "inspect" sub-command
1462
+ Parameters
1463
+ ----------
1464
+ parser: ArgumentParser
1465
+ command line parser object
1466
+ args: Namespace
1467
+ Parsed arguments
1468
+ """
1425
1469
  if args.input is None:
1426
1470
  print_stderr('Please specify an input file to inspect')
1427
1471
  parser.parse_args([args.subparser, args.subparsercmd, '-h'])
@@ -1440,8 +1484,42 @@ def inspect_component_summary(parser, args):
1440
1484
  )
1441
1485
  i_component_summary.run()
1442
1486
 
1487
+
1443
1488
  ################################ End inspect handlers ################################
1444
1489
 
1490
+
1491
+ def export_dt(parser, args):
1492
+ """
1493
+ Run the "export dt" sub-command
1494
+ Parameters
1495
+ ----------
1496
+ parser: ArgumentParser
1497
+ command line parser object
1498
+ args: Namespace
1499
+ Parsed arguments
1500
+ """
1501
+
1502
+ try:
1503
+ config = create_dependency_track_exporter_config_from_args(args)
1504
+ dt_exporter = DependencyTrackExporter(
1505
+ config=config,
1506
+ debug=args.debug,
1507
+ trace=args.trace,
1508
+ quiet=args.quiet,
1509
+ )
1510
+
1511
+ success = dt_exporter.upload_sbom(args.input)
1512
+
1513
+ if not success:
1514
+ sys.exit(1)
1515
+
1516
+ except Exception as e:
1517
+ print_stderr(f'ERROR: {e}')
1518
+ if args.debug:
1519
+ traceback.print_exc()
1520
+ sys.exit(1)
1521
+
1522
+
1445
1523
  def utils_certloc(*_):
1446
1524
  """
1447
1525
  Run the "utils certloc" sub-command
@@ -28,6 +28,9 @@ import os.path
28
28
  import sys
29
29
  import uuid
30
30
 
31
+ from cyclonedx.schema import SchemaVersion
32
+ from cyclonedx.validation.json import JsonValidator
33
+
31
34
  from . import __version__
32
35
  from .scanossbase import ScanossBase
33
36
  from .spdxlite import SpdxLite
@@ -48,10 +51,10 @@ class CycloneDx(ScanossBase):
48
51
  self.debug = debug
49
52
  self._spdx = SpdxLite(debug=debug)
50
53
 
51
- def parse(self, data: json): # noqa: PLR0912, PLR0915
54
+ def parse(self, data: dict): # noqa: PLR0912, PLR0915
52
55
  """
53
56
  Parse the given input (raw/plain) JSON string and return CycloneDX summary
54
- :param data: json - JSON object
57
+ :param data: dict - JSON object
55
58
  :return: CycloneDX dictionary, and vulnerability dictionary
56
59
  """
57
60
  if not data:
@@ -170,12 +173,12 @@ class CycloneDx(ScanossBase):
170
173
  success = self.produce_from_str(f.read(), output_file)
171
174
  return success
172
175
 
173
- def produce_from_json(self, data: json, output_file: str = None) -> tuple[bool, json]: # noqa: PLR0912
176
+ def produce_from_json(self, data: dict, output_file: str = None) -> tuple[bool, dict]: # noqa: PLR0912
174
177
  """
175
178
  Produce the CycloneDX output from the raw scan results input data
176
179
 
177
180
  Args:
178
- data (json): JSON object
181
+ data (dict): JSON object
179
182
  output_file (str, optional): Output file (optional). Defaults to None.
180
183
 
181
184
  Returns:
@@ -296,13 +299,13 @@ class CycloneDx(ScanossBase):
296
299
  """
297
300
  vuln_id = vuln.get('ID', '') or vuln.get('id', '')
298
301
  vuln_cve = vuln.get('CVE', '') or vuln.get('cve', '')
299
-
302
+
300
303
  # Skip CPE entries, use CVE if available
301
304
  if vuln_id.upper().startswith('CPE:') and vuln_cve:
302
305
  vuln_id = vuln_cve
303
-
306
+
304
307
  return vuln_id, vuln_cve
305
-
308
+
306
309
  def _create_vulnerability_entry(self, vuln_id: str, vuln: dict, vuln_cve: str, purl: str) -> dict:
307
310
  """
308
311
  Create a new vulnerability entry for CycloneDX format.
@@ -313,61 +316,56 @@ class CycloneDx(ScanossBase):
313
316
  'source': {
314
317
  'name': 'NVD' if vuln_source == 'nvd' else 'GitHub Advisories',
315
318
  'url': f'https://nvd.nist.gov/vuln/detail/{vuln_cve}'
316
- if vuln_source == 'nvd'
317
- else f'https://github.com/advisories/{vuln_id}'
319
+ if vuln_source == 'nvd'
320
+ else f'https://github.com/advisories/{vuln_id}',
318
321
  },
319
322
  'ratings': [{'severity': self._sev_lookup(vuln.get('severity', 'unknown').lower())}],
320
- 'affects': [{'ref': purl}]
323
+ 'affects': [{'ref': purl}],
321
324
  }
322
-
325
+
323
326
  def append_vulnerabilities(self, cdx_dict: dict, vulnerabilities_data: dict, purl: str) -> dict:
324
327
  """
325
328
  Append vulnerabilities to an existing CycloneDX dictionary
326
-
329
+
327
330
  Args:
328
331
  cdx_dict (dict): The existing CycloneDX dictionary
329
332
  vulnerabilities_data (dict): The vulnerabilities data from get_vulnerabilities_json
330
333
  purl (str): The PURL of the component these vulnerabilities affect
331
-
334
+
332
335
  Returns:
333
336
  dict: The updated CycloneDX dictionary with vulnerabilities appended
334
337
  """
335
338
  if not cdx_dict or not vulnerabilities_data:
336
339
  return cdx_dict
337
-
340
+
338
341
  if 'vulnerabilities' not in cdx_dict:
339
342
  cdx_dict['vulnerabilities'] = []
340
-
343
+
341
344
  # Extract vulnerabilities from the response
342
345
  vulns_list = vulnerabilities_data.get('purls', [])
343
346
  if not vulns_list:
344
347
  return cdx_dict
345
-
348
+
346
349
  vuln_items = vulns_list[0].get('vulnerabilities', [])
347
-
350
+
348
351
  for vuln in vuln_items:
349
352
  vuln_id, vuln_cve = self._normalize_vulnerability_id(vuln)
350
-
353
+
351
354
  # Skip empty IDs or CPE-only entries
352
355
  if not vuln_id or vuln_id.upper().startswith('CPE:'):
353
356
  continue
354
-
357
+
355
358
  # Check if vulnerability already exists
356
- existing_vuln = next(
357
- (v for v in cdx_dict['vulnerabilities'] if v.get('id') == vuln_id),
358
- None
359
- )
360
-
359
+ existing_vuln = next((v for v in cdx_dict['vulnerabilities'] if v.get('id') == vuln_id), None)
360
+
361
361
  if existing_vuln:
362
362
  # Add this PURL to the affects list if not already present
363
363
  if not any(ref.get('ref') == purl for ref in existing_vuln.get('affects', [])):
364
364
  existing_vuln['affects'].append({'ref': purl})
365
365
  else:
366
366
  # Create new vulnerability entry
367
- cdx_dict['vulnerabilities'].append(
368
- self._create_vulnerability_entry(vuln_id, vuln, vuln_cve, purl)
369
- )
370
-
367
+ cdx_dict['vulnerabilities'].append(self._create_vulnerability_entry(vuln_id, vuln, vuln_cve, purl))
368
+
371
369
  return cdx_dict
372
370
 
373
371
  @staticmethod
@@ -388,6 +386,25 @@ class CycloneDx(ScanossBase):
388
386
  'unknown': 'unknown',
389
387
  }.get(value, 'unknown')
390
388
 
389
+ def is_cyclonedx_json(self, json_string: str) -> bool:
390
+ """
391
+ Validate if the given JSON string is a valid CycloneDX JSON string
392
+ Args:
393
+ json_string (str): JSON string to validate
394
+ Returns:
395
+ bool: True if the JSON string is valid, False otherwise
396
+ """
397
+ try:
398
+ cdx_json_validator = JsonValidator(SchemaVersion.V1_6)
399
+ json_validation_errors = cdx_json_validator.validate_str(json_string)
400
+ if json_validation_errors:
401
+ self.print_stderr(f'ERROR: Problem parsing input JSON: {json_validation_errors}')
402
+ return False
403
+ return True
404
+ except Exception as e:
405
+ self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
406
+ return False
407
+
391
408
 
392
409
  #
393
410
  # End of CycloneDX Class
@@ -0,0 +1 @@
1
+ date: 20250722103216, utime: 1753180336
@@ -0,0 +1,221 @@
1
+ """
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2025, SCANOSS
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
+ """
24
+
25
+ import base64
26
+ import json
27
+ import traceback
28
+ from dataclasses import dataclass
29
+ from typing import Optional
30
+
31
+ import requests
32
+
33
+ from scanoss.cyclonedx import CycloneDx
34
+
35
+ from ..scanossbase import ScanossBase
36
+ from ..utils.file import validate_json_file
37
+
38
+
39
+ @dataclass
40
+ class DependencyTrackExporterConfig:
41
+ debug: bool = False
42
+ trace: bool = False
43
+ quiet: bool = False
44
+ dt_url: str = None
45
+ dt_apikey: str = None
46
+ dt_projectid: Optional[str] = None
47
+ dt_projectname: Optional[str] = None
48
+ dt_projectversion: Optional[str] = None
49
+
50
+
51
+ def create_dependency_track_exporter_config_from_args(args) -> DependencyTrackExporterConfig:
52
+ return DependencyTrackExporterConfig(
53
+ debug=getattr(args, 'debug', False),
54
+ trace=getattr(args, 'trace', False),
55
+ quiet=getattr(args, 'quiet', False),
56
+ dt_url=getattr(args, 'dt_url', None),
57
+ dt_apikey=getattr(args, 'dt_apikey', None),
58
+ dt_projectid=getattr(args, 'dt_projectid', None),
59
+ dt_projectname=getattr(args, 'dt_projectname', None),
60
+ dt_projectversion=getattr(args, 'dt_projectversion', None),
61
+ )
62
+
63
+
64
+ class DependencyTrackExporter(ScanossBase):
65
+ """
66
+ Class for exporting SBOM files to Dependency Track
67
+ """
68
+
69
+ def __init__(
70
+ self,
71
+ config: DependencyTrackExporterConfig,
72
+ debug: bool = False,
73
+ trace: bool = False,
74
+ quiet: bool = False,
75
+ ):
76
+ """
77
+ Initialize DependencyTrackExporter
78
+
79
+ Args:
80
+ config: Configuration parameters for the dependency track exporter
81
+ debug: Enable debug output
82
+ trace: Enable trace output
83
+ quiet: Enable quiet mode
84
+ """
85
+ super().__init__(debug=debug, trace=trace, quiet=quiet)
86
+
87
+ self.dt_url = config.dt_url.rstrip('/')
88
+ self.dt_apikey = config.dt_apikey
89
+ self.dt_projectid = config.dt_projectid
90
+ self.dt_projectname = config.dt_projectname
91
+ self.dt_projectversion = config.dt_projectversion
92
+
93
+ self._validate_config()
94
+
95
+ def _validate_config(self):
96
+ """
97
+ Validate that the configuration is valid.
98
+ """
99
+ has_id = bool(self.dt_projectid)
100
+ has_name_version = bool(self.dt_projectname and self.dt_projectversion)
101
+
102
+ if not (has_id or has_name_version):
103
+ raise ValueError('Either --dt-projectid OR (--dt-projectname and --dt-projectversion) must be provided')
104
+
105
+ if has_id and has_name_version:
106
+ self.print_debug('Both DT project ID and name/version provided. Using project ID.')
107
+
108
+ def _read_and_validate_sbom(self, input_file: str) -> dict:
109
+ """
110
+ Read and validate the SBOM file
111
+
112
+ Args:
113
+ input_file: Path to the SBOM file
114
+
115
+ Returns:
116
+ Parsed SBOM content as dictionary
117
+
118
+ Raises:
119
+ ValueError: If file doesn't exist or is invalid or not a valid CycloneDX SBOM
120
+ """
121
+ result = validate_json_file(input_file)
122
+ if not result.is_valid:
123
+ raise ValueError(f'Invalid JSON file: {result.error}')
124
+
125
+ cdx = CycloneDx(debug=self.debug)
126
+ if not cdx.is_cyclonedx_json(json.dumps(result.data)):
127
+ raise ValueError(f'Input file is not a valid CycloneDX SBOM: {input_file}')
128
+
129
+ return result.data
130
+
131
+ def _encode_sbom(self, sbom_content: dict) -> str:
132
+ """
133
+ Encode SBOM content to base64
134
+
135
+ Args:
136
+ sbom_content: SBOM dictionary
137
+
138
+ Returns:
139
+ Base64 encoded string
140
+ """
141
+ json_str = json.dumps(sbom_content, separators=(',', ':'))
142
+ encoded = base64.b64encode(json_str.encode('utf-8')).decode('utf-8')
143
+ return encoded
144
+
145
+ def _build_payload(self, encoded_sbom: str) -> dict:
146
+ """
147
+ Build the API payload
148
+
149
+ Args:
150
+ encoded_sbom: Base64 encoded SBOM
151
+
152
+ Returns:
153
+ API payload dictionary
154
+ """
155
+ if self.dt_projectid:
156
+ return {'project': self.dt_projectid, 'bom': encoded_sbom}
157
+ else:
158
+ return {
159
+ 'projectName': self.dt_projectname,
160
+ 'projectVersion': self.dt_projectversion,
161
+ 'autoCreate': True,
162
+ 'bom': encoded_sbom,
163
+ }
164
+
165
+ def upload_sbom(self, input_file: str) -> bool:
166
+ """
167
+ Upload SBOM file to Dependency Track
168
+
169
+ Args:
170
+ input_file: Path to the SBOM file
171
+
172
+ Returns:
173
+ True if successful, False otherwise
174
+ """
175
+ try:
176
+ self.print_stderr(f'Reading SBOM file: {input_file}')
177
+ sbom_content = self._read_and_validate_sbom(input_file)
178
+
179
+ self.print_debug('Encoding SBOM to base64')
180
+ encoded_sbom = self._encode_sbom(sbom_content)
181
+
182
+ payload = self._build_payload(encoded_sbom)
183
+
184
+ url = f'{self.dt_url}/api/v1/bom'
185
+ headers = {'Content-Type': 'application/json', 'X-Api-Key': self.dt_apikey}
186
+
187
+ if self.trace:
188
+ self.print_trace(f'URL: {url}')
189
+ self.print_trace(f'Headers: {headers}')
190
+ self.print_trace(f'Payload keys: {list(payload.keys())}')
191
+
192
+ self.print_msg('Uploading SBOM to Dependency Track...')
193
+ response = requests.put(url, json=payload, headers=headers)
194
+
195
+ if response.status_code in [200, 201]:
196
+ self.print_stderr('SBOM uploaded successfully')
197
+
198
+ try:
199
+ response_data = response.json()
200
+ if 'token' in response_data:
201
+ self.print_stderr(f'Upload token: {response_data["token"]}')
202
+ except json.JSONDecodeError:
203
+ pass
204
+
205
+ return True
206
+ else:
207
+ self.print_stderr(f'Upload failed with status code: {response.status_code}')
208
+ self.print_stderr(f'Response: {response.text}')
209
+ return False
210
+
211
+ except ValueError as e:
212
+ self.print_stderr(f'Validation error: {e}')
213
+ return False
214
+ except requests.exceptions.RequestException as e:
215
+ self.print_stderr(f'Request error: {e}')
216
+ return False
217
+ except Exception as e:
218
+ self.print_stderr(f'Unexpected error: {e}')
219
+ if self.debug:
220
+ traceback.print_exc()
221
+ return False
@@ -0,0 +1,23 @@
1
+ """
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2025, SCANOSS
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
+ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scanoss
3
- Version: 1.28.3
3
+ Version: 1.30.0
4
4
  Summary: Simple Python library to leverage the SCANOSS APIs
5
5
  Home-page: https://scanoss.com
6
6
  Author: SCANOSS
@@ -13,7 +13,7 @@ Classifier: License :: OSI Approved :: MIT License
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Development Status :: 5 - Production/Stable
15
15
  Classifier: Programming Language :: Python :: 3
16
- Requires-Python: >=3.7
16
+ Requires-Python: >=3.9
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: requests
@@ -30,6 +30,7 @@ Requires-Dist: packageurl-python
30
30
  Requires-Dist: pathspec
31
31
  Requires-Dist: jsonschema
32
32
  Requires-Dist: crc
33
+ Requires-Dist: cyclonedx-python-lib[validation]
33
34
  Provides-Extra: fast-winnowing
34
35
  Requires-Dist: scanoss_winnowing>=0.5.0; extra == "fast-winnowing"
35
36
  Dynamic: license-file
@@ -174,7 +175,7 @@ if __name__ == "__main__":
174
175
  ```
175
176
 
176
177
  ## Requirements
177
- Python 3.7 or higher.
178
+ Python 3.9 or higher.
178
179
 
179
180
  ## Source code
180
181
  The source for this package can be found [here](https://github.com/scanoss/scanoss.py).
@@ -72,6 +72,8 @@ src/scanoss/data/build_date.txt
72
72
  src/scanoss/data/scanoss-settings-schema.json
73
73
  src/scanoss/data/spdx-exceptions.json
74
74
  src/scanoss/data/spdx-licenses.json
75
+ src/scanoss/export/__init__.py
76
+ src/scanoss/export/dependency_track.py
75
77
  src/scanoss/inspection/__init__.py
76
78
  src/scanoss/inspection/component_summary.py
77
79
  src/scanoss/inspection/copyleft.py
@@ -12,6 +12,7 @@ packageurl-python
12
12
  pathspec
13
13
  jsonschema
14
14
  crc
15
+ cyclonedx-python-lib[validation]
15
16
 
16
17
  [fast_winnowing]
17
18
  scanoss_winnowing>=0.5.0
@@ -1 +0,0 @@
1
- date: 20250714165622, utime: 1752512182
File without changes