|
2 | 2 |
|
3 | 3 | import click |
4 | 4 |
|
| 5 | +from ...core.api.packages import list_packages |
5 | 6 | from ...core.api.vulnerabilities import ( |
6 | 7 | _print_vulnerabilities_assessment_table, |
7 | 8 | _print_vulnerabilities_summary_table, |
8 | 9 | get_package_scan_result, |
9 | 10 | ) |
10 | 11 | from .. import decorators, utils, validators |
11 | | -from ..exceptions import handle_api_exceptions |
12 | 12 | from .main import main |
13 | 13 |
|
14 | 14 |
|
| 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 | + |
15 | 24 | @main.command() |
16 | 25 | @decorators.common_cli_config_options |
17 | 26 | @decorators.common_cli_output_options |
18 | 27 | @decorators.common_api_auth_options |
19 | 28 | @decorators.initialise_api |
20 | 29 | @click.argument( |
21 | 30 | "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, |
24 | 33 | ) |
25 | 34 | @click.option( |
26 | 35 | "-A", |
@@ -75,67 +84,99 @@ def vulnerabilities( |
75 | 84 |
|
76 | 85 | """ |
77 | 86 | 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, |
94 | 125 | ) |
| 126 | + continue |
95 | 127 |
|
96 | | - click.secho("OK", fg="green", err=use_stderr) |
| 128 | + if not data: |
| 129 | + continue |
97 | 130 |
|
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 | + ) |
101 | 142 |
|
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 |
106 | 178 | ) |
107 | 179 |
|
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) |
0 commit comments