Skip to content

Commit cd36a3b

Browse files
committed
feat(components): Support for querying Development status for a component. (SP4197)
1 parent 72c17ac commit cd36a3b

5 files changed

Lines changed: 147 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
11+
## [1.52.0] - 2026-04-06
12+
### Added
13+
- Added `status` subcommand query to `component` command to retrieve development life-cycle staus:
14+
- Component and version specific for a single component
15+
- Component and version specific for a list of components
16+
1017
## [1.51.1] - 2026-04-01
1118
### Fixed
1219
- Fixed vulnerabilities not appearing in CycloneDX output for folder-scan (`fs`) command
@@ -869,3 +876,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
869876
[1.50.1]: https://github.com/scanoss/scanoss.py/compare/v1.50.0...v1.50.1
870877
[1.51.0]: https://github.com/scanoss/scanoss.py/compare/v1.50.1...v1.51.0
871878
[1.51.1]: https://github.com/scanoss/scanoss.py/compare/v1.51.0...v1.51.1
879+
[1.52.0]: https://github.com/scanoss/scanoss.py/compare/v1.51.1...v1.52.0

CLIENT_HELP.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,8 @@ The `component` command has a suite of sub-commands designed to operate on OSS c
423423
* Version Details (`versions`)
424424
* Cryptography (`crypto`)
425425
* Provenance (`provenance`)
426+
* Licenses (`licenses`)
427+
* Status (`status`)
426428

427429
For the latest list of sub-commands, please run:
428430
```bash
@@ -518,14 +520,38 @@ The licenses command also supports CycloneDX (CDX) input files. You can provide
518520
scanoss-py comp licenses -i cyclonedx-sbom.json -o component-licenses.json
519521
```
520522

523+
#### Component Status
524+
The following command provides the capability to search the SCANOSS KB for development status information of Open Source components:
525+
```bash
526+
scanoss-py comp status -p "pkg:npm/react@17.0.2"
527+
```
528+
It is possible to supply multiple PURLs by repeating the `-p pkg` option, or providing a purl input file `-i purl-input.json` ([for example](tests/data/purl-input.json)):
529+
```bash
530+
scanoss-py comp status -i purl-input.json -o component-status.json
531+
```
532+
533+
The status command also supports CycloneDX (CDX) input files. You can provide a CycloneDX SBOM file and retrieve status information for all components:
534+
```bash
535+
scanoss-py comp status -i cyclonedx-sbom.json -o component-status.json
536+
```
537+
538+
The component status provides information about:
539+
- **Component status**: Overall status of the component (active, inactive, deprecated)
540+
- **Repository status**: Current status of the component's repository
541+
- **First indexed date**: When the component was first indexed in SCANOSS KB
542+
- **Last indexed date**: Most recent indexing date
543+
- **Version status**: Status specific to the requested version
544+
- **Indexed date**: When the specific version was indexed
545+
521546
### CDX Input Support for Component Commands
522547
Several component commands now support CycloneDX (CDX) input files. This allows you to analyze components from existing SBOM files:
523548
524549
**Supported commands with CDX input:**
525550
- `comp vulns` - Analyze vulnerabilities from CDX file
526-
- `comp licenses` - Retrieve licenses from CDX file
551+
- `comp licenses` - Retrieve licenses from CDX file
527552
- `comp crypto` - Detect cryptographic algorithms from CDX file
528553
- `comp semgrep` - Find semgrep issues from CDX file
554+
- `comp status` - Retrieve development status from CDX file
529555
530556
**Example using CDX input:**
531557
```bash
@@ -535,6 +561,9 @@ scanoss-py comp vulns -i sbom.cdx.json -o vulnerabilities.json
535561
# Get licenses for all components in a CycloneDX SBOM
536562
scanoss-py comp licenses -i sbom.cdx.json -o licenses.json
537563
564+
# Get status information for all components in a CycloneDX SBOM
565+
scanoss-py comp status -i sbom.cdx.json -o status.json
566+
538567
# Detect cryptographic usage from CDX
539568
scanoss-py comp crypto -i sbom.cdx.json -o crypto-findings.json
540569
```

src/scanoss/cli.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,15 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
420420
c_versions.add_argument('--limit', '-l', type=int, help='Generic component search')
421421
c_versions.set_defaults(func=comp_versions)
422422

423+
# Component Sub-command: component status
424+
c_status = comp_sub.add_parser(
425+
'status',
426+
aliases=['stat'],
427+
description=f'Show Component Status details: {__version__}',
428+
help='Retrieve development status for the given components',
429+
)
430+
c_status.set_defaults(func=comp_status)
431+
423432
# Sub-command: crypto
424433
p_crypto = subparsers.add_parser(
425434
'crypto',
@@ -478,6 +487,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
478487
p_crypto_hints,
479488
p_crypto_versions_in_range,
480489
c_licenses,
490+
c_status,
481491
]:
482492
p.add_argument('--purl', '-p', type=str, nargs='*', help='Package URL - PURL to process.')
483493
p.add_argument('--input', '-i', type=str, help='Input file name')
@@ -493,6 +503,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
493503
p_crypto_hints,
494504
p_crypto_versions_in_range,
495505
c_licenses,
506+
c_status,
496507
]:
497508
p.add_argument(
498509
'--timeout',
@@ -510,6 +521,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
510521
c_semgrep,
511522
c_provenance,
512523
c_licenses,
524+
c_status,
513525
]:
514526
p.add_argument(
515527
'--apiurl', type=str, help='SCANOSS API base URL (optional - default: https://api.osskb.org)'
@@ -1090,6 +1102,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
10901102
p_crypto_hints,
10911103
p_crypto_versions_in_range,
10921104
c_licenses,
1105+
c_status,
10931106
p_copy,
10941107
]:
10951108
p.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
@@ -1178,6 +1191,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
11781191
p_crypto_hints,
11791192
p_crypto_versions_in_range,
11801193
c_licenses,
1194+
c_status,
11811195
]:
11821196
p.add_argument(
11831197
'--key', '-k', type=str, help='SCANOSS API Key token (optional - not required for default OSSKB URL)'
@@ -1215,6 +1229,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
12151229
p_crypto_hints,
12161230
p_crypto_versions_in_range,
12171231
c_licenses,
1232+
c_status,
12181233
]:
12191234
p.add_argument(
12201235
'--api2url', type=str,
@@ -1261,6 +1276,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
12611276
c_search,
12621277
c_versions,
12631278
c_licenses,
1279+
c_status,
12641280
p_folder_scan,
12651281
]:
12661282
p.add_argument(
@@ -1305,6 +1321,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
13051321
p_crypto_hints,
13061322
p_crypto_versions_in_range,
13071323
c_licenses,
1324+
c_status,
13081325
e_dt,
13091326
p_copy,
13101327
]:
@@ -2652,6 +2669,41 @@ def comp_licenses(parser, args):
26522669
sys.exit(1)
26532670

26542671

2672+
def comp_status(parser, args):
2673+
"""
2674+
Run the "component status" sub-command
2675+
Parameters
2676+
----------
2677+
parser: ArgumentParser
2678+
command line parser object
2679+
args: Namespace
2680+
Parsed arguments
2681+
"""
2682+
if (not args.purl and not args.input) or (args.purl and args.input):
2683+
print_stderr('ERROR: Please specify an input file or purl to decorate (--purl or --input)')
2684+
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
2685+
sys.exit(1)
2686+
if args.ca_cert and not os.path.exists(args.ca_cert):
2687+
print_stderr(f'ERROR: Certificate file does not exist: {args.ca_cert}.')
2688+
sys.exit(1)
2689+
pac_file = get_pac_file(args.pac)
2690+
comps = Components(
2691+
debug=args.debug,
2692+
trace=args.trace,
2693+
quiet=args.quiet,
2694+
grpc_url=args.apiurl, # Legacy param name; accepts the REST API base URL. TODO: rename to url
2695+
api_key=args.key,
2696+
ca_cert=args.ca_cert,
2697+
proxy=args.proxy,
2698+
pac=pac_file,
2699+
timeout=args.timeout,
2700+
req_headers=process_req_headers(args.header),
2701+
ignore_cert_errors=args.ignore_cert_errors,
2702+
)
2703+
if not comps.get_status(args.input, args.purl, args.output):
2704+
sys.exit(1)
2705+
2706+
26552707
def results(parser, args):
26562708
"""
26572709
Run the "results" sub-command

src/scanoss/components.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,36 @@ def get_licenses(self, json_file: str = None, purls: [] = None, output_file: str
395395
self.print_msg(f'Results written to: {output_file}')
396396
self._close_file(output_file, file)
397397
return success
398+
399+
def get_status(self, json_file: str = None, purls: [] = None, output_file: str = None) -> bool:
400+
"""
401+
Retrieve the development status details for the supplied PURLs
402+
403+
Args:
404+
json_file (str, optional): Input JSON file. Defaults to None.
405+
purls (None, optional): PURLs to retrieve status details for. Defaults to None.
406+
output_file (str, optional): Output file. Defaults to None.
407+
408+
Returns:
409+
bool: True on success, False otherwise
410+
"""
411+
success = False
412+
413+
purls_request = self.load_purls(json_file, purls)
414+
if not purls_request:
415+
return False
416+
file = self._open_file_or_sdtout(output_file)
417+
if file is None:
418+
return False
419+
420+
# Use ComponentBatchRequest format for the status api
421+
component_batch_request = {'components': purls_request.get('purls')}
422+
self.print_msg('Sending PURLs to Component Status API...')
423+
response = self.grpc_api.get_component_status(component_batch_request, use_grpc=self.use_grpc)
424+
if response:
425+
print(json.dumps(response, indent=2, sort_keys=True), file=file)
426+
success = True
427+
if output_file:
428+
self.print_msg(f'Results written to: {output_file}')
429+
self._close_file(output_file, file)
430+
return success

src/scanoss/scanossgrpc.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
},
9696
'components.SearchComponents': {'path': '/components/search', 'method': 'GET'},
9797
'components.GetComponentVersions': {'path': '/components/versions', 'method': 'GET'},
98+
'components.GetComponentsStatus': {'path': '/components/status/components', 'method': 'POST'},
9899
'geoprovenance.GetCountryContributorsByComponents': {
99100
'path': '/geoprovenance/countries/components',
100101
'method': 'POST',
@@ -765,6 +766,29 @@ def get_licenses(self, request: Dict, use_grpc: Optional[bool] = None) -> Option
765766
use_grpc=use_grpc,
766767
)
767768

769+
def get_component_status(self, request: Dict, use_grpc: Optional[bool] = None) -> Optional[Dict]:
770+
"""
771+
Client function to call the API for Components GetComponentsStatus
772+
Only REST API is supported for this endpoint (gRPC not yet available)
773+
774+
Args:
775+
request (Dict): ComponentsRequest
776+
Returns:
777+
Optional[Dict]: ComponentsStatusResponse, or None if the request was not successful
778+
"""
779+
# Force REST API since gRPC is not available for this endpoint
780+
if use_grpc:
781+
self.print_stderr('WARNING: gRPC is not supported for component status. Using REST API instead.')
782+
783+
return self._call_api(
784+
'components.GetComponentsStatus',
785+
None, # No gRPC method available
786+
request,
787+
ComponentsRequest,
788+
'Sending data for component status retrieval (rqId: {rqId})...',
789+
use_grpc=False, # Force REST
790+
)
791+
768792
def load_generic_headers(self, url: Optional[str] = None):
769793
"""
770794
Adds custom headers from req_headers to metadata.

0 commit comments

Comments
 (0)