Skip to content

Add trustify-cli as a subfolder with preserved history#2220

Open
ruromero wants to merge 14 commits intoguacsec:mainfrom
ruromero:add-trustify-cli-subtree
Open

Add trustify-cli as a subfolder with preserved history#2220
ruromero wants to merge 14 commits intoguacsec:mainfrom
ruromero:add-trustify-cli-subtree

Conversation

@ruromero
Copy link
Contributor

@ruromero ruromero commented Jan 28, 2026

Summary

  • Imports the trustify-cli repository (github.com/trustification/trustify-cli) as a subfolder
  • Preserves full git history from the source repository using git subtree
  • Adds CLI tools for authentication and SBOM management

Changes

  • Adds trustify-cli/ folder with complete source code
  • Includes 18 files with 3,898 line additions
  • Preserves 6 commits from original repository history

Context

This is a one-time import to merge the trustify-cli project into the main trustify repository. The git subtree approach was used to preserve the full commit history while placing all files in a dedicated subdirectory to avoid conflicts with existing files.

After merging, the separate trustify-cli repository can be archived or removed.

🤖 Generated with Claude Code

Summary by Sourcery

Import the trustify CLI as a new workspace member providing command-line interaction with the Trustify API for SBOM and auth management.

New Features:

  • Add the trustify CLI binary under etc/trustify-cli for managing SBOMs and authentication against the Trustify API.
  • Introduce SBOM duplicate detection and bulk deletion commands with progress reporting and concurrency controls.
  • Provide an auth command to obtain OAuth2 tokens via client-credentials flow using configurable SSO endpoints.

Enhancements:

  • Register etc/trustify-cli as a Cargo workspace member and share common workspace dependencies, including dotenv-based configuration loading.

Build:

  • Update Cargo workspace configuration and dependencies to include the new trustify-cli crate.

Documentation:

  • Add dedicated Trustify CLI README documenting installation, configuration, and usage of SBOM and auth commands.

Signed-off-by: Ruben Romero Montes <[email protected]>
Assisted by: Cursor
Signed-off-by: Ruben Romero Montes <[email protected]>
Assisted-by: Cursor
Signed-off-by: Ruben Romero Montes <[email protected]>
Assisted-by: Cursor
Signed-off-by: Ruben Romero Montes <[email protected]>
Signed-off-by: Ruben Romero Montes <[email protected]>
Assisted-by: Cursor
Signed-off-by: Ruben Romero Montes <[email protected]>
Assisted-by: Cursor
…57a2'

git-subtree-dir: trustify-cli
git-subtree-mainline: a93901a
git-subtree-split: b9d149b
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 28, 2026

Reviewer's Guide

Imports the standalone trustify-cli Rust project into the monorepo as a new etc/trustify-cli workspace member, wiring it into the Cargo workspace and exposing a trustify CLI binary for authenticated SBOM management and duplicate detection against the Trustify API.

Sequence diagram for CLI startup and authenticated API client initialization

sequenceDiagram
    actor User
    participant Shell
    participant trustify_bin as trustify
    participant Main as main_rs
    participant Cli as Cli_parser
    participant Config as Config
    participant AuthCreds as AuthCredentials
    participant AuthApi as auth_get_token
    participant ApiClient as ApiClient
    participant Command as Commands

    User->>Shell: run trustify sbom list ...
    Shell->>trustify_bin: start process
    trustify_bin->>Main: main()
    Main->>Main: dotenvy::dotenv()

    Main->>Cli: Cli::parse()
    Cli-->>Main: Cli { config, command }

    Main->>Config: config.auth_credentials()
    alt auth configured
        Config-->>Main: Some(sso_url, client_id, client_secret)
        Main->>AuthCreds: AuthCredentials::new(sso_url, client_id, client_secret)
        AuthCreds-->>Main: AuthCredentials
        Main->>AuthApi: get_token(token_url, client_id, client_secret)
        AuthApi-->>Main: access_token
        Main->>ApiClient: ApiClient::new(url, Some(access_token), Some(AuthCredentials))
    else no auth configured
        Config-->>Main: None
        Main->>ApiClient: ApiClient::new(url, None, None)
    end
    ApiClient-->>Main: ApiClient

    Main->>Command: command.run(Context)
    Command-->>User: command output
    Command-->>trustify_bin: exit code
    trustify_bin-->>Shell: process exit
Loading

Sequence diagram for SBOM duplicate detection and deletion workflow

sequenceDiagram
    actor User
    participant trustify as trustify
    participant Cli as SbomCommands
    participant DupCmd as DuplicatesCommands
    participant SbomApi as sbom_api
    participant ApiClient as ApiClient
    participant TrustifyAPI as Trustify_HTTP_API
    participant FS as FileSystem

    rect rgb(230,230,255)
        User->>trustify: sbom duplicates find
        trustify->>Cli: SbomCommands::run
        Cli->>DupCmd: DuplicatesCommands::run(Find)
        DupCmd->>FS: check_output_file(output)
        FS-->>DupCmd: final_output_path

        DupCmd->>SbomApi: find_duplicates(client, params, Some(final_output))
        SbomApi->>SbomApi: list first page to get total
        SbomApi->>ApiClient: get_with_query(/v2/sbom, ListParams(limit=1))
        ApiClient->>TrustifyAPI: HTTP GET /api/v2/sbom?limit=1
        TrustifyAPI-->>ApiClient: 200 items,total
        ApiClient-->>SbomApi: JSON body

        loop for each worker and page
            SbomApi->>ApiClient: get_with_query(/v2/sbom, ListParams(limit=batch,offset))
            ApiClient->>TrustifyAPI: HTTP GET /api/v2/sbom
            TrustifyAPI-->>ApiClient: 200 items
            ApiClient-->>SbomApi: JSON body
            SbomApi->>SbomApi: parse items into SbomEntry
        end

        SbomApi->>SbomApi: group by document_id and build DuplicateGroup list
        SbomApi->>FS: write duplicates.json
        FS-->>SbomApi: ok
        SbomApi-->>DupCmd: Vec DuplicateGroup
        DupCmd-->>User: summary of duplicates and output file
    end

    rect rgb(230,255,230)
        User->>trustify: sbom duplicates delete [--dry-run]
        trustify->>Cli: SbomCommands::run
        Cli->>DupCmd: DuplicatesCommands::run(Delete)
        DupCmd->>SbomApi: delete_duplicates(client, input_path, concurrency, dry_run)
        SbomApi->>FS: open duplicates.json
        FS-->>SbomApi: Vec DuplicateGroup

        alt dry_run
            SbomApi-->>DupCmd: DeleteDuplicatesResult(total, deleted=0,...)
            DupCmd-->>User: print planned deletions
        else real deletion
            loop for each duplicate id (concurrent)
                SbomApi->>ApiClient: delete(/v2/sbom/{id})
                ApiClient->>TrustifyAPI: HTTP DELETE /api/v2/sbom/{id}
                TrustifyAPI-->>ApiClient: 200 or 404 or error
                ApiClient-->>SbomApi: result
                SbomApi->>SbomApi: update deleted/skipped/failed counters
            end
            SbomApi-->>DupCmd: DeleteDuplicatesResult
            DupCmd-->>User: print deletion summary
        end
    end
Loading

Sequence diagram for automatic token refresh on expired access token

sequenceDiagram
    participant Command as Command_run
    participant ApiClient as ApiClient
    participant Exec as execute_with_retry
    participant HTTP as Trustify_HTTP_API
    participant AuthCreds as AuthCredentials
    participant AuthApi as get_token

    Command->>ApiClient: get(/v2/sbom)
    ApiClient->>Exec: execute_with_retry(closure)

    loop first attempt
        Exec->>ApiClient: closure()
        ApiClient->>HTTP: HTTP GET /api/v2/sbom with Authorization: Bearer old_token
        HTTP-->>ApiClient: 401 Unauthorized
        ApiClient-->>Exec: Err(TokenExpired)

        Exec->>ApiClient: refresh_token()
        ApiClient->>AuthCreds: get_token()
        AuthCreds->>AuthApi: get_token(token_url, client_id, client_secret)
        AuthApi-->>AuthCreds: new_access_token
        AuthCreds-->>ApiClient: new_access_token
        ApiClient->>ApiClient: store new token in RwLock
        ApiClient-->>Exec: Ok
    end

    loop retry with new token
        Exec->>ApiClient: closure()
        ApiClient->>HTTP: HTTP GET /api/v2/sbom with Authorization: Bearer new_access_token
        HTTP-->>ApiClient: 200 OK
        ApiClient-->>Exec: Ok(body)
    end

    Exec-->>Command: Ok(body)
    Command->>Command: process response
Loading

Class diagram for the new trustify CLI structure

classDiagram
    class Cli {
        +Config config
        +Commands command
    }

    class Config {
        +String url
        +Option_String sso_url
        +Option_String client_id
        +Option_String client_secret
        +bool has_auth()
        +Option_tuple_auth_credentials auth_credentials()
    }

    class Context {
        +Config config
        +ApiClient client
    }

    class Commands {
        <<enum>>
        +Sbom(SbomCommands)
        +Auth(AuthCommands)
        +run(ctx: Context)
    }

    class AuthCommands {
        <<enum>>
        +Token
        +run(ctx: Context)
    }

    class SbomCommands {
        <<enum>>
        +Get(id: String)
        +List(query: Option_String, limit: Option_u32, offset: Option_u32, sort: Option_String, format: ListFormat)
        +Delete(id: Option_String, query: Option_String, dry_run: bool)
        +Duplicates(command: DuplicatesCommands)
        +run(ctx: Context)
    }

    class DuplicatesCommands {
        <<enum>>
        +Find(batch_size: u32, concurrency: usize, output: Option_String)
        +Delete(input: Option_String, concurrency: usize, dry_run: bool)
        +run(ctx: Context)
    }

    class ListFormat {
        <<enum>>
        +Id
        +Name
        +Short
        +Full
    }

    class ApiClient {
        +Client client
        +String base_url
        +RwLock_Option_String token
        +Option_AuthCredentials auth_credentials
        +new(base_url: String, token: Option_String, auth_credentials: Option_AuthCredentials) ApiClient
        +url(path: String) String
        +get(path: String) String
        +get_with_query(path: String, query: Serializable) String
        +delete(path: String) String
        +authorize(request: RequestBuilder) RequestBuilder
        +refresh_token() Result_void_ApiError
        +execute_with_retry(f: FutureFn) Result_String_ApiError
        +handle_response(response: Response) Result_String_ApiError
    }

    class ApiError {
        <<enum>>
        +NetworkError(message: String)
        +HttpError(status: u16, body: String)
        +NotFound(message: String)
        +Unauthorized
        +TokenExpired
        +Timeout(status: u16)
        +ServerError(status: u16, body: String)
        +InternalError(message: String)
        +TemplateError(message: String)
    }

    class AuthCredentials {
        +String token_url
        +String client_id
        +String client_secret
        +new(sso_url: String, client_id: String, client_secret: String) AuthCredentials
        +get_token() Result_String_AuthError
    }

    class AuthError {
        <<enum>>
        +ConnectionError(error: ReqwestError)
        +AuthenticationFailed
        +ServerError(message: String)
    }

    class ListParams {
        +Option_String q
        +Option_u32 limit
        +Option_u32 offset
        +Option_String sort
    }

    class FindDuplicatesParams {
        +u32 batch_size
        +usize concurrency
    }

    class DuplicateGroup {
        +String document_id
        +Option_String published
        +String id
        +Vec_String duplicates
    }

    class DeleteDuplicatesResult {
        +u32 deleted
        +u32 skipped
        +u32 failed
        +u32 total
    }

    class SbomEntry {
        +String id
        +String document_id
        +Option_String published
    }

    class DeleteEntry {
        +String id
        +String document_id
    }

    %% Relationships
    Cli --> Config
    Cli --> Commands

    Context --> Config
    Context --> ApiClient

    Commands --> SbomCommands
    Commands --> AuthCommands

    AuthCommands --> Context
    SbomCommands --> Context
    DuplicatesCommands --> Context

    ApiClient --> AuthCredentials
    ApiClient --> ApiError

    AuthCredentials --> AuthError

    SbomCommands --> ListFormat
    SbomCommands --> ListParams
    SbomCommands --> DuplicatesCommands

    DuplicatesCommands --> FindDuplicatesParams
    DuplicatesCommands --> DuplicateGroup
    DuplicatesCommands --> DeleteDuplicatesResult

    ListParams --> ApiClient
    FindDuplicatesParams --> ApiClient

    DuplicateGroup --> SbomEntry
    DeleteDuplicatesResult --> DeleteEntry
Loading

File-Level Changes

Change Details Files
Add trustify-cli as a new workspace crate and wire shared dependencies
  • Register etc/trustify-cli as a workspace member in the root Cargo.toml
  • Add dotenvy as a shared workspace dependency for environment/.env loading
  • Include a dedicated Cargo.toml and Cargo.lock for the trustify-cli crate
Cargo.toml
Cargo.lock
etc/trustify-cli/Cargo.toml
etc/trustify-cli/Cargo.lock
Implement API access layer with OAuth2-based authentication and resilient HTTP handling
  • Introduce AuthCredentials and token retrieval via OAuth2 client credentials against a configurable SSO/token endpoint
  • Provide an ApiClient wrapper over reqwest with bearer auth, centralized error typing, retry/backoff, and automatic token refresh on 401s
  • Standardize API error handling via ApiError with variants for HTTP, network, timeout, template, and internal errors
etc/trustify-cli/src/api/auth.rs
etc/trustify-cli/src/api/client.rs
etc/trustify-cli/src/api/mod.rs
Add SBOM-specific API helpers and high-level duplicate management workflows
  • Implement list/get/delete SBOM endpoints and query parameter support for the /v2/sbom API
  • Add a parallelized duplicate discovery routine that pages through SBOMs, groups by document_id, picks the most recent entry, and writes duplicate groups to JSON
  • Add a concurrent deletion routine that reads duplicate groups from disk, deletes SBOMs with progress bars, and tracks deleted/skipped/failed counts
etc/trustify-cli/src/api/sbom.rs
Expose CLI commands for SBOM operations and authentication
  • Define a Config struct sourced from CLI flags, environment variables, and .env for Trustify URL and OAuth2 credentials
  • Add CLI wiring (Cli, Commands) with sbom and auth top-level commands using clap
  • Implement sbom subcommands for get/list/delete and nested duplicates find/delete with formatting, progress, and user prompts for output file handling
  • Implement auth token command to print an access token using configured credentials
etc/trustify-cli/src/cli.rs
etc/trustify-cli/src/config.rs
etc/trustify-cli/src/commands/mod.rs
etc/trustify-cli/src/commands/auth.rs
etc/trustify-cli/src/commands/sbom.rs
etc/trustify-cli/src/main.rs
Document the Trustify CLI usage and licensing
  • Add README with installation options, configuration via .env, and detailed command examples for SBOM and auth workflows
  • Include LICENSE file copied from the original trustify-cli repository
etc/trustify-cli/README.md
etc/trustify-cli/LICENSE

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@ruromero ruromero requested a review from bxf12315 January 28, 2026 09:18
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The sbom delete CLI subcommand currently only prints a success message and does not invoke the underlying API delete logic, which is likely surprising to users; consider wiring this to sbom_api::delete (and/or a query-based bulk delete) or clearly marking it as unimplemented.
  • In api::client::ApiError, the NotFound(String) variant always gets constructed with the constant "Resource not found" message, so the String payload is redundant; either drop the payload or pass through the response body for more context.
  • The reqwest dependency is pinned to 0.13.1, which is quite old compared to the current ecosystem and may lack bug/security fixes; consider updating to a more recent 0.11+ version that aligns with your other services' HTTP stack.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `sbom delete` CLI subcommand currently only prints a success message and does not invoke the underlying API delete logic, which is likely surprising to users; consider wiring this to `sbom_api::delete` (and/or a query-based bulk delete) or clearly marking it as unimplemented.
- In `api::client::ApiError`, the `NotFound(String)` variant always gets constructed with the constant "Resource not found" message, so the `String` payload is redundant; either drop the payload or pass through the response body for more context.
- The `reqwest` dependency is pinned to `0.13.1`, which is quite old compared to the current ecosystem and may lack bug/security fixes; consider updating to a more recent 0.11+ version that aligns with your other services' HTTP stack.

## Individual Comments

### Comment 1
<location> `trustify-cli/Dockerfile:2` </location>
<code_context>
+# Build stage
+FROM rust:1.83-alpine AS builder
+
+# Install musl-dev for static linking
</code_context>

<issue_to_address>
**issue (bug_risk):** Container build/runtime are likely to fail due to TLS/openssl and CA certificate assumptions with reqwest.

With `reqwest` using the default `native-tls` stack, this Alpine-based build and a distroless runtime are likely to miss required TLS libs and CA certs:

- Builder (Alpine): `native-tls` typically needs `openssl-dev` (and related libs) to compile.
- Runtime (distroless): you need CA certificates available, or HTTPS calls from `reqwest` will fail.

You can either (1) keep `native-tls` and add the needed OpenSSL libs + CA bundle to the images, or (2) switch `reqwest` to `rustls-tls` to avoid system OpenSSL and better support static/distroless images.
</issue_to_address>

### Comment 2
<location> `trustify-cli/src/api/sbom.rs:237-238` </location>
<code_context>
+        )));
+    }
+
+    // Wait for all workers to complete
+    join_all(handles).await;
+
+    let all_entries = Arc::try_unwrap(results)
</code_context>

<issue_to_address>
**issue (bug_risk):** Worker join errors are ignored, which can hide panics and lead to silently incomplete results.

Because the `join_all(handles).await` result is ignored, any worker panic or cancellation is silently dropped and `find_duplicates` continues as if all workers succeeded, potentially yielding incomplete `duplicate_groups`.

Consider checking each `JoinHandle` and returning an `ApiError` on failure, e.g.:

```rust
let join_results = join_all(handles).await;
for res in join_results {
    if let Err(e) = res {
        return Err(ApiError::InternalError(format!(
            "Worker task failed: {}",
            e
        )));
    }
}
```

This ensures worker failures are surfaced instead of producing corrupted output.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +237 to +238
// Wait for all workers to complete
join_all(handles).await;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Worker join errors are ignored, which can hide panics and lead to silently incomplete results.

Because the join_all(handles).await result is ignored, any worker panic or cancellation is silently dropped and find_duplicates continues as if all workers succeeded, potentially yielding incomplete duplicate_groups.

Consider checking each JoinHandle and returning an ApiError on failure, e.g.:

let join_results = join_all(handles).await;
for res in join_results {
    if let Err(e) = res {
        return Err(ApiError::InternalError(format!(
            "Worker task failed: {}",
            e
        )));
    }
}

This ensures worker failures are surfaced instead of producing corrupted output.

@codecov
Copy link

codecov bot commented Jan 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 68.73%. Comparing base (a93901a) to head (3eeece6).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2220      +/-   ##
==========================================
- Coverage   68.74%   68.73%   -0.01%     
==========================================
  Files         397      397              
  Lines       22322    22322              
  Branches    22322    22322              
==========================================
- Hits        15345    15343       -2     
+ Misses       6072     6068       -4     
- Partials      905      911       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ruromero ruromero marked this pull request as draft January 28, 2026 12:48
@@ -0,0 +1,31 @@
# Build stage
FROM rust:1.83-alpine AS builder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be consistent with the root repo’s version, 1.91?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RUN touch src/main.rs && cargo build --release

# Runtime stage - use minimal distroless image
FROM gcr.io/distroless/static-debian12:nonroot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use the company’s ubi9 here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would maybe remove docker stuff for now. We can add it later if needed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@bxf12315
Copy link
Contributor

There are a few other minor issues.
The path requested in the issue is tools/trustify-cli. The second point should be to add trustify-cli to the members list in the root Cargo.toml, so that it can be built via cargo build -p trustify-cli.

@dejanb
Copy link
Contributor

dejanb commented Jan 29, 2026

This is going in the right direction. @ruromero can give access to @bxf12315 to the branch, so he can push things forward

[package]
name = "trustify-cli"
version = "0.1.0"
edition = "2021"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok Thanks

@jcrossley3
Copy link
Contributor

I'm curious what's the motivation for this? I thought we liked consuming the CLI as a crate, no?

Downgrade reqwest to 0.12, remove the form and query features, format…
@jcrossley3
Copy link
Contributor

I'm curious what's the motivation for this? I thought we liked consuming the CLI as a crate, no?

Let me rephrase, as I'm obviously confusing the CLI with the UX... 😄

Should we consume the CLI as a crate, like we do for the UX?

@dejanb
Copy link
Contributor

dejanb commented Jan 30, 2026

@jcrossley3 We discussed this offline and the majority decision was not to create an official CLI that we will release and maintain (at least not at this point), but to provide an example script/tool that people can use to do these kind of tasks. The thought process is that most of the organization will have somewhat different requirements and would like to modify the logic, but having a good, well-documented starting point should be helpful.

So we should come up with a good documentation of how to use this tool, what it can and can not do and how to adapt it. Perhaps in the future it can graduate to be an official CLI that we will release as a separate binary.

@jcrossley3
Copy link
Contributor

Thanks for the clarification @dejanb. How about we keep it beneath etc/ for now then, similar to the etc/gensbom tool we have? No need for the extra tools/ at this point, I think.

@dejanb
Copy link
Contributor

dejanb commented Jan 30, 2026

@jcrossley3 Yeah, that sounds reasonable to me. I was unaware of gensbom tool.

@bxf12315 @ruromero wdyt?

@bxf12315
Copy link
Contributor

bxf12315 commented Feb 1, 2026

@jcrossley3 Yeah, that sounds reasonable to me. I was unaware of gensbom tool.

@bxf12315 @ruromero wdyt?

OK

@bxf12315 bxf12315 marked this pull request as ready for review February 3, 2026 15:53
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • Since this crate is part of the workspace, consider removing the nested etc/trustify-cli/Cargo.lock file and relying solely on the top-level Cargo.lock to avoid divergence and dependency resolution confusion.
  • Many of the command handlers (SbomCommands, DuplicatesCommands, AuthCommands) call process::exit directly; if you anticipate reusing this logic (e.g. from tests or other binaries), it may be cleaner to return Result values and centralize exit handling in main.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Since this crate is part of the workspace, consider removing the nested `etc/trustify-cli/Cargo.lock` file and relying solely on the top-level `Cargo.lock` to avoid divergence and dependency resolution confusion.
- Many of the command handlers (`SbomCommands`, `DuplicatesCommands`, `AuthCommands`) call `process::exit` directly; if you anticipate reusing this logic (e.g. from tests or other binaries), it may be cleaner to return `Result` values and centralize exit handling in `main`.

## Individual Comments

### Comment 1
<location> `etc/trustify-cli/src/api/client.rs:24-25` </location>
<code_context>
+    #[error("HTTP 404: Resource not found")]
+    NotFound(String),
+
+    #[error("HTTP 401: Please check your authentication credentials")]
+    Unauthorized,
+
+    #[error("HTTP 401: Token expired")]
</code_context>

<issue_to_address>
**issue (bug_risk):** Using `Unauthorized` for 403 responses makes the error message misleading.

In `handle_response`, 403 (`StatusCode::FORBIDDEN`) is mapped to `ApiError::Unauthorized`, whose message is hard‑coded as `HTTP 401`. This will mislead users receiving a 403 (e.g. insufficient permissions) but seeing a 401 error text. Please either add a separate `Forbidden` variant or adjust the messages so they don’t embed `401` and remain consistent with the actual status code.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

@dejanb dejanb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR needs rebasing. Hopefully that would resolve build problem, but if not that test failure in examples should be examined.

}
}
}
SbomCommands::Delete { id, query, dry_run } => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delete command is just a stub at the moment. Let's implement it before merging

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

5 participants