Add repository-level vulnerability summary to vulnerabilities command#282
Add repository-level vulnerability summary to vulnerabilities command#282colinmoynes wants to merge 10 commits intomasterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR expands the CLI’s vulnerability reporting to support repository-level aggregation, and also introduces download-command enhancements (filename filtering and multi-package downloads), along with associated version/dependency bumps.
Changes:
- Add
cloudsmith vulnerabilities OWNER/REPOmode to aggregate vulnerability counts across all packages in a repository (with filtering and JSON output). - Enhance
cloudsmith downloadwith--filenamefiltering (including glob patterns) and--download-all, plus improved multi-match display. - Bump CLI version metadata to
1.16.0and updatecloudsmith-apidependency to2.0.25.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
cloudsmith_cli/cli/commands/vulnerabilities.py |
Adds repo-level summary mode, aggregation/filtering, and rich table output. |
cloudsmith_cli/core/api/vulnerabilities.py |
Updates summary table rendering and adjusts scan identifier handling. |
cloudsmith_cli/cli/validators.py |
Adds validator to accept OWNER/REPO[/SLUG_PERM] for vulnerabilities command arg. |
cloudsmith_cli/core/download.py |
Adds filename filtering (server/client side) and refactors multi-match output via rich table; adds resolve-all helper. |
cloudsmith_cli/cli/commands/download.py |
Adds --filename and --download-all, refactors download flow, and adds safe path joining. |
cloudsmith_cli/core/tests/test_download.py |
Adds unit tests for filename filtering, resolve-all behavior, and multi-match display. |
cloudsmith_cli/cli/tests/commands/test_download.py |
Adds integration-style CLI tests for new download flags/behavior. |
cloudsmith_cli/cli/commands/upstream.py |
Adds alpine to supported upstream formats list. |
README.md |
Documents new download flags (--filename, --download-all). |
CHANGELOG.md |
Adds entries for new features/releases (but currently with duplicated 1.14.0 sections). |
setup.py / requirements.txt |
Bumps cloudsmith-api dependency from 2.0.24 to 2.0.25. |
cloudsmith_cli/data/VERSION / .bumpversion.cfg |
Bumps version to 1.16.0. |
Comments suppressed due to low confidence (2)
CHANGELOG.md:60
- The changelog now contains two separate
## [1.14.0]sections (with different dates), which makes it ambiguous which entry is authoritative. Consider consolidating these into a single1.14.0section (or correcting the version/date as appropriate).
## [1.14.0] - 2026-03-11
### Added
- Added `vulnerabilities` command to retrieve security scan results for a package
- Summary View (Default): Displays a high-level count of vulnerabilities broken down by severity (Critical, High, Medium, Low, Unknown).
- Assessment View `--show-assessment` (`-A`): Provides a detailed breakdown where vulnerabilities are:
- Grouped by the specific affected upstream package / dependency.
- Sorted by severity (Critical first).
- Richly formatted tables.
- Filtering Capabilities:
- By Severity: `--severity` Show only specific levels (e.g., just Critical and High).
- By Status: `--fixable | --non-fixable` Filter to show only "Fixable" vulnerabilities (where a patch exists) or "Non-Fixable" ones.
- Supports `--output-format json | pretty_json` for programmatic usage
## [1.14.0] - 2026-03-13
### Added
- Added `vulnerabilities` command to retrieve security scan results for a package
- Summary View (Default): Displays a high-level count of vulnerabilities broken down by severity (Critical, High, Medium, Low, Unknown).
- Assessment View `--show-assessment` (`-A`): Provides a detailed breakdown where vulnerabilities are:
- Grouped by the specific affected upstream package / dependency.
- Sorted by severity (Critical first).
- Richly formatted tables.
- Filtering Capabilities:
CHANGELOG.md:23
cloudsmith_cli/data/VERSIONand.bumpversion.cfgare bumped to1.16.0, but the new vulnerability summary entry is still under[Unreleased]rather than the1.16.0release section. If this change is part of 1.16.0, consider moving it into the## [1.16.0]section; otherwise consider not bumping the version yet.
## [Unreleased]
### Added
- Added repository-level vulnerability summary (`cloudsmith vulnerabilities OWNER/REPO`)
- Aggregates scan results across all packages into a single color-coded table
- Packages sorted by total vulnerability count (descending)
- Packages without scan results are silently omitted
- Supports `--severity` and `--fixable/--non-fixable` filters
## [1.16.0] - 2026-03-24
### Added
- Added Alpine Upstream support for managing upstream configurations.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try: | ||
| packages, _ = list_packages( | ||
| opts=opts, owner=owner, repo=repo, query=None, sort=None | ||
| ) | ||
| except Exception as exc: | ||
| raise click.ClickException( | ||
| f"Failed to list packages for '{owner}/{repo}'. " | ||
| f"Please check the owner and repository names are correct. " | ||
| f"Detail: {exc}" | ||
| ) from exc |
There was a problem hiding this comment.
Catching a broad Exception here and re-raising click.ClickException bypasses handle_api_exceptions, which is responsible for structured JSON errors when --output-format json|pretty_json is used. To keep error output consistent across the CLI, consider either wrapping the API call in handle_api_exceptions(...) (pass ctx into this helper) or letting ApiException propagate to a higher-level handler instead of converting everything into a ClickException.
| try: | |
| packages, _ = list_packages( | |
| opts=opts, owner=owner, repo=repo, query=None, sort=None | |
| ) | |
| except Exception as exc: | |
| raise click.ClickException( | |
| f"Failed to list packages for '{owner}/{repo}'. " | |
| f"Please check the owner and repository names are correct. " | |
| f"Detail: {exc}" | |
| ) from exc | |
| packages, _ = list_packages( | |
| opts=opts, | |
| owner=owner, | |
| repo=repo, | |
| query=None, | |
| sort=None, | |
| ) |
| # Repo summary mode: collect with progress bar, then output once | ||
| if repo_summary: | ||
| slugs = get_packages_in_repo(opts, owner, repo) | ||
|
|
||
| repo_summary_rows = _collect_repo_scan_data( | ||
| opts, owner, repo, slugs, severity_filter, fixable | ||
| ) | ||
|
|
||
| if not repo_summary_rows: | ||
| click.secho( | ||
| f"No vulnerability scan results found for any packages " | ||
| f"in '{owner}/{repo}'.", | ||
| fg="yellow", | ||
| err=use_stderr, | ||
| ) | ||
| return | ||
|
|
||
| json_output = { | ||
| "owner": owner, | ||
| "repository": repo, | ||
| "packages": [ | ||
| { | ||
| "slug_perm": slug_perm, | ||
| "package": label, | ||
| "vulnerabilities": counts, | ||
| } | ||
| for slug_perm, label, counts in repo_summary_rows | ||
| ], | ||
| } | ||
|
|
||
| if utils.maybe_print_as_json(opts, json_output): | ||
| return | ||
|
|
||
| # Table only needs label and counts | ||
| _print_repo_summary_table( | ||
| [(label, counts) for _, label, counts in repo_summary_rows], | ||
| severity_filter, | ||
| ) | ||
| return |
There was a problem hiding this comment.
The repo-level summary path (cloudsmith vulnerabilities OWNER/REPO) introduces new behavior (listing packages, aggregating counts, filtering, JSON shape), but the existing CLI tests only cover the single-package invocation. Consider adding tests for the OWNER/REPO mode (including pagination/multiple packages, empty repo, and --show-assessment rejection).
There was a problem hiding this comment.
Will add this shortly.
| if not data: | ||
| # click.echo( | ||
| # f"No vulnerability scan results found for package: {package}", err=True | ||
| # ) | ||
| return None | ||
|
|
||
| return data[0].identifier |
There was a problem hiding this comment.
get_package_scan_identifier() now returns None when no scan results exist, but get_package_scan_result() still uses the returned value as the identifier parameter for vulnerabilities_read_with_http_info(...). Unless None is explicitly supported by the API, this will likely turn a "no scan" situation into an API error. Consider handling the None return in get_package_scan_result() (e.g., return None early) so the CLI can display the intended "No scan results found" message.
| @@ -92,45 +382,43 @@ def vulnerabilities( | |||
| severity_filter=severity_filter, | |||
| fixable=fixable, | |||
| ) | |||
| except Exception as exc: # pylint: disable=broad-exception-caught | |||
| raise click.ClickException( | |||
| f"Failed to retrieve vulnerability report for " | |||
| f"'{owner}/{repo}/{slug}': {exc}" | |||
| ) from exc | |||
There was a problem hiding this comment.
This broad except Exception + ClickException re-wrap can break the CLI’s standard API error handling (notably JSON-formatted error responses via handle_api_exceptions) and also makes it harder to distinguish API failures from "no scan results". Consider using handle_api_exceptions(ctx, ...) around get_package_scan_result() (as other commands do) and only handling the "no scan" case via return values.
| slugs = [slug] | ||
| data = None | ||
|
|
There was a problem hiding this comment.
slugs = [slug] is assigned but never used in single-package mode. Removing it would avoid dead code and keep the command logic clearer.
| slugs = [slug] | |
| data = None |
842b05e to
df3070c
Compare
…now for single packages
df3070c to
0f5ee19
Compare
This pull request introduces improvements to the vulnerability command. It adds a repository-level vulnerability summary capability, allowing users to aggregate and filter vulnerability data across all packages in a repository.
Type of Change
Additional Notes