Skip to content

Commit 2e96ca0

Browse files
committed
Improved _collect_repo_scan_data() performance. 8-10x improvement.
1 parent a20fe3e commit 2e96ca0

1 file changed

Lines changed: 56 additions & 53 deletions

File tree

cloudsmith_cli/cli/commands/vulnerabilities.py

Lines changed: 56 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
# Copyright 2026 Cloudsmith Ltd
44

5+
from concurrent.futures import ThreadPoolExecutor, as_completed
6+
57
import click
68
from rich.console import Console
79
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
@@ -170,13 +172,60 @@ def _print_repo_summary_table(package_rows, severity_filter=None):
170172
console.print(f"\nTotal Vulnerabilities: [bold]{grand_total}[/bold]\n")
171173

172174

175+
_SCAN_WORKERS = 10
176+
177+
173178
def _collect_repo_scan_data(opts, owner, repo, slugs, severity_filter, fixable):
174179
"""Silently collect scan data for all packages with a progress bar.
175180
176181
Returns list of (slug, label, counts, status) tuples where status is one of
177182
"vulnerable", "safe", or "no_scan". Sorted: vulnerable (by count desc),
178183
then safe, then no_scan.
179184
"""
185+
186+
def _scan_one(slug, pkg_name_fallback, pkg_version_fallback):
187+
fallback_label = (
188+
f"{pkg_name_fallback}:{pkg_version_fallback}"
189+
if pkg_version_fallback
190+
else pkg_name_fallback
191+
)
192+
try:
193+
data = get_package_scan_result(
194+
opts=opts,
195+
owner=owner,
196+
repo=repo,
197+
package=slug,
198+
show_assessment=False,
199+
severity_filter=severity_filter,
200+
fixable=fixable,
201+
)
202+
except Exception: # pylint: disable=broad-exception-caught
203+
return (slug, fallback_label, {}, "no_scan")
204+
205+
# Build label from scan response metadata, fall back to list_packages data
206+
pkg_data = getattr(data, "package", None) if data else None
207+
pkg_name = (
208+
getattr(pkg_data, "name", pkg_name_fallback)
209+
if pkg_data
210+
else pkg_name_fallback
211+
)
212+
pkg_version = (
213+
getattr(pkg_data, "version", pkg_version_fallback)
214+
if pkg_data
215+
else pkg_version_fallback
216+
)
217+
label = f"{pkg_name}:{pkg_version}" if pkg_version else pkg_name
218+
219+
if not data or not _has_scan_results(data):
220+
return (slug, label, {}, "no_scan")
221+
222+
if severity_filter or fixable is not None:
223+
_apply_filters(data, severity_filter, fixable)
224+
225+
counts = _aggregate_severity_counts(data, severity_filter)
226+
status = "vulnerable" if sum(counts.values()) > 0 else "no_issues_found"
227+
return (slug, label, counts, status)
228+
180229
rows = []
181230
console = Console(stderr=True)
182231

@@ -191,60 +240,14 @@ def _collect_repo_scan_data(opts, owner, repo, slugs, severity_filter, fixable):
191240
) as progress:
192241
task = progress.add_task("Scanning packages...", total=len(slugs))
193242

194-
for slug, pkg_name_fallback, pkg_version_fallback in slugs:
195-
progress.update(task, description=f"Processing {slug}...")
196-
fallback_label = (
197-
f"{pkg_name_fallback}:{pkg_version_fallback}"
198-
if pkg_version_fallback
199-
else pkg_name_fallback
200-
)
201-
202-
try:
203-
data = get_package_scan_result(
204-
opts=opts,
205-
owner=owner,
206-
repo=repo,
207-
package=slug,
208-
show_assessment=False,
209-
severity_filter=severity_filter,
210-
fixable=fixable,
211-
)
212-
except Exception: # pylint: disable=broad-exception-caught
213-
rows.append((slug, fallback_label, {}, "no_scan"))
214-
progress.advance(task)
215-
continue
216-
217-
# Build label from scan response metadata, fall back to list_packages data
218-
pkg_data = getattr(data, "package", None) if data else None
219-
pkg_name = (
220-
getattr(pkg_data, "name", pkg_name_fallback)
221-
if pkg_data
222-
else pkg_name_fallback
223-
)
224-
pkg_version = (
225-
getattr(pkg_data, "version", pkg_version_fallback)
226-
if pkg_data
227-
else pkg_version_fallback
228-
)
229-
label = f"{pkg_name}:{pkg_version}" if pkg_version else pkg_name
230-
231-
if not data or not _has_scan_results(data):
232-
rows.append((slug, label, {}, "no_scan"))
243+
with ThreadPoolExecutor(max_workers=_SCAN_WORKERS) as executor:
244+
futures = {
245+
executor.submit(_scan_one, slug, name, version): slug
246+
for slug, name, version in slugs
247+
}
248+
for future in as_completed(futures):
249+
rows.append(future.result())
233250
progress.advance(task)
234-
continue
235-
236-
# Apply filters if active
237-
if severity_filter or fixable is not None:
238-
_apply_filters(data, severity_filter, fixable)
239-
240-
counts = _aggregate_severity_counts(data, severity_filter)
241-
242-
if sum(counts.values()) > 0:
243-
rows.append((slug, label, counts, "vulnerable"))
244-
else:
245-
rows.append((slug, label, counts, "no_issues_found"))
246-
247-
progress.advance(task)
248251

249252
# Sort: vulnerable first (by total desc), then safe, then no_scan
250253
# When filters are active, only return packages with matching vulnerabilities

0 commit comments

Comments
 (0)