-
Notifications
You must be signed in to change notification settings - Fork 59
CM-54797 add SBOM import support to cli #358
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
gotbadger
merged 1 commit into
cycodehq:main
from
omerr-cycode:cm-54797-add-sbom-import-support-to-cli
Nov 20, 2025
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,8 +56,9 @@ This guide walks you through both installation and usage. | |
| 6. [Ignoring via a config file](#ignoring-via-a-config-file) | ||
| 6. [Report command](#report-command) | ||
| 1. [Generating SBOM Report](#generating-sbom-report) | ||
| 7. [Scan logs](#scan-logs) | ||
| 8. [Syntax Help](#syntax-help) | ||
| 7. [Import command](#import-command) | ||
| 8. [Scan logs](#scan-logs) | ||
| 9. [Syntax Help](#syntax-help) | ||
|
|
||
| # Prerequisites | ||
|
|
||
|
|
@@ -1295,6 +1296,26 @@ To create an SBOM report for a path:\ | |
| For example:\ | ||
| `cycode report sbom --format spdx-2.3 --include-vulnerabilities --include-dev-dependencies path /path/to/local/project` | ||
|
|
||
| # Import Command | ||
|
|
||
| ## Importing SBOM | ||
|
|
||
| A software bill of materials (SBOM) is an inventory of all constituent components and software dependencies involved in the development and delivery of an application. | ||
| Using this command, you can import an SBOM file from your file system into Cycode. | ||
|
|
||
| The following options are available for use with this command: | ||
|
|
||
| | Option | Description | Required | Default | | ||
| |----------------------------------------------------|--------------------------------------------|----------|-------------------------------------------------------| | ||
| | `-n, --name TEXT` | Display name of the SBOM | Yes | | | ||
| | `-v, --vendor TEXT` | Name of the entity that provided the SBOM | Yes | | | ||
| | `-l, --label TEXT` | Attach label to the SBOM | No | | | ||
| | `-o, --owner TEXT` | Email address of the Cycode user that serves as point of contact for this SBOM | No | | | ||
| | `-b, --business-impact [High \| Medium \| Low]` | Business Impact | No | Medium | | ||
|
|
||
| For example:\ | ||
| `cycode import sbom --name example-sbom --vendor cycode -label tag1 -label tag2 --owner [email protected] /path/to/local/project` | ||
|
|
||
| # Scan Logs | ||
|
|
||
| All CLI scans are logged in Cycode. The logs can be found under Settings > CLI Logs. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import typer | ||
|
|
||
| from cycode.cli.apps.report_import.report_import_command import report_import_command | ||
| from cycode.cli.apps.report_import.sbom import sbom_command | ||
|
|
||
| app = typer.Typer(name='import', no_args_is_help=True) | ||
| app.callback(short_help='Import report. You`ll need to specify which report type to import.')(report_import_command) | ||
| app.command(name='sbom', short_help='Import SBOM report from a local path.')(sbom_command) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import typer | ||
|
|
||
| from cycode.cli.utils.sentry import add_breadcrumb | ||
|
|
||
|
|
||
| def report_import_command(ctx: typer.Context) -> int: | ||
| """:bar_chart: [bold cyan]Import security reports.[/] | ||
|
|
||
| Example usage: | ||
| * `cycode import sbom`: Import SBOM report | ||
| """ | ||
| add_breadcrumb('import') | ||
| return 1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import typer | ||
|
|
||
| from cycode.cli.apps.report_import.sbom.sbom_command import sbom_command | ||
|
|
||
| app = typer.Typer(name='sbom') | ||
| app.command(name='path', short_help='Import SBOM report from a local path.')(sbom_command) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| from pathlib import Path | ||
| from typing import Annotated, Optional | ||
|
|
||
| import typer | ||
|
|
||
| from cycode.cli.cli_types import BusinessImpactOption | ||
| from cycode.cli.exceptions.handle_report_sbom_errors import handle_report_exception | ||
| from cycode.cli.utils.get_api_client import get_import_sbom_cycode_client | ||
| from cycode.cli.utils.sentry import add_breadcrumb | ||
| from cycode.cyclient.import_sbom_client import ImportSbomParameters | ||
|
|
||
|
|
||
| def sbom_command( | ||
| ctx: typer.Context, | ||
| path: Annotated[ | ||
| Path, | ||
| typer.Argument( | ||
| exists=True, resolve_path=True, dir_okay=False, readable=True, help='Path to SBOM file.', show_default=False | ||
| ), | ||
| ], | ||
| sbom_name: Annotated[ | ||
| str, typer.Option('--name', '-n', help='SBOM Name.', case_sensitive=False, show_default=False) | ||
| ], | ||
| vendor: Annotated[ | ||
| str, typer.Option('--vendor', '-v', help='Vendor Name.', case_sensitive=False, show_default=False) | ||
| ], | ||
| labels: Annotated[ | ||
| Optional[list[str]], | ||
| typer.Option( | ||
| '--label', '-l', help='Label, can be specified multiple times.', case_sensitive=False, show_default=False | ||
| ), | ||
| ] = None, | ||
| owners: Annotated[ | ||
| Optional[list[str]], | ||
| typer.Option( | ||
| '--owner', | ||
| '-o', | ||
| help='Email address of a user in Cycode platform, can be specified multiple times.', | ||
| case_sensitive=True, | ||
| show_default=False, | ||
| ), | ||
| ] = None, | ||
| business_impact: Annotated[ | ||
| BusinessImpactOption, | ||
| typer.Option( | ||
| '--business-impact', | ||
| '-b', | ||
| help='Business Impact.', | ||
| case_sensitive=True, | ||
| show_default=True, | ||
| ), | ||
| ] = BusinessImpactOption.MEDIUM, | ||
| ) -> None: | ||
| """Import SBOM.""" | ||
| add_breadcrumb('sbom') | ||
|
|
||
| client = get_import_sbom_cycode_client(ctx) | ||
|
|
||
| import_parameters = ImportSbomParameters( | ||
| Name=sbom_name, | ||
| Vendor=vendor, | ||
| BusinessImpact=business_impact, | ||
| Labels=labels, | ||
| Owners=owners, | ||
| ) | ||
|
|
||
| try: | ||
| if not path.exists(): | ||
| from errno import ENOENT | ||
| from os import strerror | ||
|
|
||
| raise FileNotFoundError(ENOENT, strerror(ENOENT), path.absolute()) | ||
|
|
||
| client.request_sbom_import_execution(import_parameters, path) | ||
| except Exception as e: | ||
| handle_report_exception(ctx, e) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| import dataclasses | ||
| from pathlib import Path | ||
| from typing import Optional | ||
|
|
||
| from requests import Response | ||
|
|
||
| from cycode.cli.cli_types import BusinessImpactOption | ||
| from cycode.cli.exceptions.custom_exceptions import RequestHttpError | ||
| from cycode.cyclient import models | ||
| from cycode.cyclient.cycode_client_base import CycodeClientBase | ||
|
|
||
|
|
||
| @dataclasses.dataclass | ||
| class ImportSbomParameters: | ||
| Name: str | ||
| Vendor: str | ||
| BusinessImpact: BusinessImpactOption | ||
| Labels: Optional[list[str]] | ||
| Owners: Optional[list[str]] | ||
|
|
||
| def _owners_to_ids(self) -> list[str]: | ||
| return [] | ||
|
|
||
| def to_request_form(self) -> dict: | ||
| form_data = {} | ||
| for field in dataclasses.fields(self): | ||
| key = field.name | ||
| val = getattr(self, key) | ||
| if val is None or len(val) == 0: | ||
| continue | ||
| if isinstance(val, list): | ||
| form_data[f'{key}[]'] = val | ||
| else: | ||
| form_data[key] = val | ||
| return form_data | ||
|
|
||
|
|
||
| class ImportSbomClient: | ||
| IMPORT_SBOM_REQUEST_PATH: str = 'v4/sbom/import' | ||
| GET_USER_ID_REQUEST_PATH: str = 'v4/members' | ||
|
|
||
| def __init__(self, client: CycodeClientBase) -> None: | ||
| self.client = client | ||
|
|
||
| def request_sbom_import_execution(self, params: ImportSbomParameters, file_path: Path) -> None: | ||
| if params.Owners: | ||
| owners_ids = self.get_owners_user_ids(params.Owners) | ||
| params.Owners = owners_ids | ||
|
|
||
| form_data = params.to_request_form() | ||
|
|
||
| with open(file_path.absolute(), 'rb') as f: | ||
| request_args = { | ||
| 'url_path': self.IMPORT_SBOM_REQUEST_PATH, | ||
| 'data': form_data, | ||
| 'files': {'File': f}, | ||
| } | ||
|
|
||
| response = self.client.post(**request_args) | ||
|
|
||
| if response.status_code != 201: | ||
| raise RequestHttpError(response.status_code, response.text, response) | ||
|
|
||
| def get_owners_user_ids(self, owners: list[str]) -> list[str]: | ||
| return [self._get_user_id_by_email(owner) for owner in owners] | ||
|
|
||
| def _get_user_id_by_email(self, email: str) -> str: | ||
| request_args = {'url_path': self.GET_USER_ID_REQUEST_PATH, 'params': {'email': email}} | ||
|
|
||
| response = self.client.get(**request_args) | ||
| member_details = self.parse_requested_member_details_response(response) | ||
|
|
||
| if not member_details.items: | ||
| raise Exception( | ||
| f"Failed to find user with email '{email}'. Verify this email is registered to Cycode platform" | ||
| ) | ||
| return member_details.items.pop(0).external_id | ||
|
|
||
| @staticmethod | ||
| def parse_requested_member_details_response(response: Response) -> models.MemberDetails: | ||
| return models.RequestedMemberDetailsResultSchema().load(response.json()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.