Skip to content

Commit f7e348a

Browse files
committed
initial commit
1 parent 14849ec commit f7e348a

3 files changed

Lines changed: 114 additions & 61 deletions

File tree

cloudsmith_cli/cli/commands/vulnerabilities.py

Lines changed: 102 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,34 @@
22

33
import click
44

5+
from ...core.api.packages import list_packages
56
from ...core.api.vulnerabilities import (
67
_print_vulnerabilities_assessment_table,
78
_print_vulnerabilities_summary_table,
89
get_package_scan_result,
910
)
1011
from .. import decorators, utils, validators
11-
from ..exceptions import handle_api_exceptions
1212
from .main import main
1313

1414

15+
def get_packages_in_repo(opts, owner, repo):
16+
"""Get list of packages in a repository. Returns list of package identifiers."""
17+
packages, _ = list_packages(
18+
opts=opts, owner=owner, repo=repo, query=None, sort=None
19+
)
20+
21+
return [pkg["slug_perm"] for pkg in packages]
22+
23+
1524
@main.command()
1625
@decorators.common_cli_config_options
1726
@decorators.common_cli_output_options
1827
@decorators.common_api_auth_options
1928
@decorators.initialise_api
2029
@click.argument(
2130
"owner_repo_package",
22-
metavar="OWNER/REPO/PACKAGE",
23-
callback=validators.validate_owner_repo_package,
31+
metavar="OWNER/REPO or OWNER/REPO/PACKAGE",
32+
callback=validators.validate_required_owner_repo_optional_slug_perm,
2433
)
2534
@click.option(
2635
"-A",
@@ -75,67 +84,99 @@ def vulnerabilities(
7584
7685
"""
7786
use_stderr = utils.should_use_stderr(opts)
78-
79-
owner, repo, slug = owner_repo_package
80-
81-
total_filtered_vulns = 0
82-
83-
context_msg = "Failed to retrieve vulnerability report!"
84-
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
85-
with utils.maybe_spinner(opts):
86-
data = get_package_scan_result(
87-
opts=opts,
88-
owner=owner,
89-
repo=repo,
90-
package=slug,
91-
show_assessment=show_assessment,
92-
severity_filter=severity_filter,
93-
fixable=fixable,
87+
repo_summary = False
88+
89+
if len(owner_repo_package) == 3:
90+
owner, repo, slug = owner_repo_package
91+
else:
92+
owner, repo = owner_repo_package
93+
slug = None
94+
repo_summary = True
95+
96+
if repo_summary and show_assessment:
97+
click.echo("Show full assessment is not supported for the repo level summary.")
98+
99+
if slug is None:
100+
slugs = get_packages_in_repo(opts, owner, repo)
101+
else:
102+
slugs = [slug]
103+
104+
for slug in slugs:
105+
total_filtered_vulns = 0
106+
data = None
107+
108+
# Manually handle exceptions to skip packages (e.g. no scan found) instead of exiting
109+
try:
110+
with utils.maybe_spinner(opts):
111+
data = get_package_scan_result(
112+
opts=opts,
113+
owner=owner,
114+
repo=repo,
115+
package=slug,
116+
show_assessment=show_assessment,
117+
severity_filter=severity_filter,
118+
fixable=fixable,
119+
)
120+
except Exception as exc: # pylint: disable=broad-exception-caught
121+
click.secho(
122+
f"Warning: Failed to retrieve vulnerability report for {slug}: {exc}",
123+
fg="yellow",
124+
err=use_stderr,
94125
)
126+
continue
95127

96-
click.secho("OK", fg="green", err=use_stderr)
128+
if not data:
129+
continue
97130

98-
# Filter results if severity or fixable flags are active
99-
if severity_filter or fixable is not None:
100-
scans = getattr(data, "scans", [])
131+
click.secho("OK", fg="green", err=use_stderr)
132+
133+
# Filter results if severity or fixable flags are active
134+
if severity_filter or fixable is not None:
135+
scans = getattr(data, "scans", [])
136+
137+
allowed_severities = (
138+
[s.strip().lower() for s in severity_filter.split(",")]
139+
if severity_filter
140+
else None
141+
)
101142

102-
allowed_severities = (
103-
[s.strip().lower() for s in severity_filter.split(",")]
104-
if severity_filter
105-
else None
143+
for scan in scans:
144+
results = getattr(scan, "results", [])
145+
146+
# 1. Filter by Severity
147+
if allowed_severities:
148+
results = [
149+
res
150+
for res in results
151+
if getattr(res, "severity", "unknown").lower()
152+
in allowed_severities
153+
]
154+
155+
# 2. Filter by Fixable Status
156+
# fixable=True: Keep only if has fix_version
157+
# fixable=False: Keep only if NO fix_version
158+
if fixable is not None:
159+
results = [
160+
res
161+
for res in results
162+
if bool(
163+
getattr(
164+
res, "fix_version", getattr(res, "fixed_version", None)
165+
)
166+
)
167+
is fixable
168+
]
169+
170+
scan.results = results
171+
total_filtered_vulns += len(results)
172+
173+
if utils.maybe_print_as_json(opts, data):
174+
return
175+
176+
_print_vulnerabilities_summary_table(
177+
data, severity_filter, total_filtered_vulns
106178
)
107179

108-
for scan in scans:
109-
results = getattr(scan, "results", [])
110-
111-
# 1. Filter by Severity
112-
if allowed_severities:
113-
results = [
114-
res
115-
for res in results
116-
if getattr(res, "severity", "unknown").lower() in allowed_severities
117-
]
118-
119-
# 2. Filter by Fixable Status
120-
# fixable=True: Keep only if has fix_version
121-
# fixable=False: Keep only if NO fix_version
122-
if fixable is not None:
123-
results = [
124-
res
125-
for res in results
126-
if bool(
127-
getattr(res, "fix_version", getattr(res, "fixed_version", None))
128-
)
129-
is fixable
130-
]
131-
132-
scan.results = results
133-
total_filtered_vulns += len(results)
134-
135-
if utils.maybe_print_as_json(opts, data):
136-
return
137-
138-
_print_vulnerabilities_summary_table(data, severity_filter, total_filtered_vulns)
139-
140-
if show_assessment:
141-
_print_vulnerabilities_assessment_table(data, severity_filter)
180+
if show_assessment:
181+
if not repo_summary:
182+
_print_vulnerabilities_assessment_table(data, severity_filter)

cloudsmith_cli/cli/validators.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ def validate_required_owner_optional_repo(ctx, param, value):
126126
return validate_slashes(param, value, minimum=1, maximum=2, form=form)
127127

128128

129+
def validate_required_owner_repo_optional_slug_perm(ctx, param, value):
130+
"""Ensure that owner/repo is formatted correctly, where owner/repo is required and slug_perm is optional."""
131+
form = "OWNER/REPO[/SLUG_PERM]"
132+
return validate_slashes(param, value, minimum=2, maximum=3, form=form)
133+
134+
129135
def validate_owner(ctx, param, value):
130136
"""Ensure that owner is formatted correctly."""
131137
# pylint: disable=unused-argument

cloudsmith_cli/core/api/vulnerabilities.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,12 @@ def get_package_scan_identifier(owner, repo, package):
206206

207207
ratelimits.maybe_rate_limit(client, headers)
208208

209+
if not data:
210+
# click.echo(
211+
# f"No vulnerability scan results found for package: {package}", err=True
212+
# )
213+
return None
214+
209215
return data[0].identifier
210216

211217

0 commit comments

Comments
 (0)