cloudsmith-cli 1.20.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cloudsmith_cli/__init__.py +10 -0
- cloudsmith_cli/__main__.py +8 -0
- cloudsmith_cli/cli/__init__.py +1 -0
- cloudsmith_cli/cli/command.py +160 -0
- cloudsmith_cli/cli/commands/__init__.py +33 -0
- cloudsmith_cli/cli/commands/auth.py +173 -0
- cloudsmith_cli/cli/commands/check.py +129 -0
- cloudsmith_cli/cli/commands/copy.py +98 -0
- cloudsmith_cli/cli/commands/credential_helper/__init__.py +39 -0
- cloudsmith_cli/cli/commands/credential_helper/docker.py +66 -0
- cloudsmith_cli/cli/commands/credential_helper/manage.py +299 -0
- cloudsmith_cli/cli/commands/delete.py +67 -0
- cloudsmith_cli/cli/commands/dependencies.py +108 -0
- cloudsmith_cli/cli/commands/docs.py +16 -0
- cloudsmith_cli/cli/commands/download.py +620 -0
- cloudsmith_cli/cli/commands/entitlements.py +819 -0
- cloudsmith_cli/cli/commands/help_.py +12 -0
- cloudsmith_cli/cli/commands/list_.py +317 -0
- cloudsmith_cli/cli/commands/login.py +99 -0
- cloudsmith_cli/cli/commands/logout.py +151 -0
- cloudsmith_cli/cli/commands/main.py +73 -0
- cloudsmith_cli/cli/commands/mcp.py +523 -0
- cloudsmith_cli/cli/commands/metadata.py +503 -0
- cloudsmith_cli/cli/commands/metrics/__init__.py +2 -0
- cloudsmith_cli/cli/commands/metrics/command.py +20 -0
- cloudsmith_cli/cli/commands/metrics/entitlements.py +148 -0
- cloudsmith_cli/cli/commands/metrics/packages.py +134 -0
- cloudsmith_cli/cli/commands/move.py +111 -0
- cloudsmith_cli/cli/commands/policy/__init__.py +3 -0
- cloudsmith_cli/cli/commands/policy/command.py +20 -0
- cloudsmith_cli/cli/commands/policy/deny.py +248 -0
- cloudsmith_cli/cli/commands/policy/license.py +335 -0
- cloudsmith_cli/cli/commands/policy/vulnerability.py +322 -0
- cloudsmith_cli/cli/commands/push.py +1323 -0
- cloudsmith_cli/cli/commands/quarantine.py +148 -0
- cloudsmith_cli/cli/commands/quota/__init__.py +2 -0
- cloudsmith_cli/cli/commands/quota/command.py +20 -0
- cloudsmith_cli/cli/commands/quota/history.py +122 -0
- cloudsmith_cli/cli/commands/quota/quota.py +107 -0
- cloudsmith_cli/cli/commands/repos.py +330 -0
- cloudsmith_cli/cli/commands/resync.py +90 -0
- cloudsmith_cli/cli/commands/status.py +92 -0
- cloudsmith_cli/cli/commands/tags.py +375 -0
- cloudsmith_cli/cli/commands/tokens.py +318 -0
- cloudsmith_cli/cli/commands/upstream.py +479 -0
- cloudsmith_cli/cli/commands/vulnerabilities.py +141 -0
- cloudsmith_cli/cli/commands/whoami.py +188 -0
- cloudsmith_cli/cli/config.py +635 -0
- cloudsmith_cli/cli/decorators.py +624 -0
- cloudsmith_cli/cli/exceptions.py +215 -0
- cloudsmith_cli/cli/metadata_common.py +146 -0
- cloudsmith_cli/cli/saml.py +109 -0
- cloudsmith_cli/cli/table.py +59 -0
- cloudsmith_cli/cli/types.py +14 -0
- cloudsmith_cli/cli/utils.py +267 -0
- cloudsmith_cli/cli/validators.py +378 -0
- cloudsmith_cli/cli/webserver.py +263 -0
- cloudsmith_cli/core/__init__.py +1 -0
- cloudsmith_cli/core/api/__init__.py +1 -0
- cloudsmith_cli/core/api/distros.py +31 -0
- cloudsmith_cli/core/api/entitlements.py +130 -0
- cloudsmith_cli/core/api/exceptions.py +57 -0
- cloudsmith_cli/core/api/files.py +131 -0
- cloudsmith_cli/core/api/init.py +109 -0
- cloudsmith_cli/core/api/metadata.py +217 -0
- cloudsmith_cli/core/api/metrics.py +78 -0
- cloudsmith_cli/core/api/orgs.py +201 -0
- cloudsmith_cli/core/api/packages.py +309 -0
- cloudsmith_cli/core/api/quota.py +64 -0
- cloudsmith_cli/core/api/rates.py +28 -0
- cloudsmith_cli/core/api/repos.py +81 -0
- cloudsmith_cli/core/api/status.py +27 -0
- cloudsmith_cli/core/api/upstreams.py +72 -0
- cloudsmith_cli/core/api/user.py +109 -0
- cloudsmith_cli/core/api/version.py +15 -0
- cloudsmith_cli/core/api/vulnerabilities.py +230 -0
- cloudsmith_cli/core/cache_utils.py +160 -0
- cloudsmith_cli/core/config.py +140 -0
- cloudsmith_cli/core/credentials/__init__.py +0 -0
- cloudsmith_cli/core/credentials/chain.py +69 -0
- cloudsmith_cli/core/credentials/models.py +44 -0
- cloudsmith_cli/core/credentials/oidc/__init__.py +6 -0
- cloudsmith_cli/core/credentials/oidc/cache.py +220 -0
- cloudsmith_cli/core/credentials/oidc/detectors/__init__.py +122 -0
- cloudsmith_cli/core/credentials/oidc/detectors/aws.py +85 -0
- cloudsmith_cli/core/credentials/oidc/detectors/azure_devops.py +70 -0
- cloudsmith_cli/core/credentials/oidc/detectors/base.py +26 -0
- cloudsmith_cli/core/credentials/oidc/detectors/bitbucket_pipelines.py +35 -0
- cloudsmith_cli/core/credentials/oidc/detectors/circleci.py +40 -0
- cloudsmith_cli/core/credentials/oidc/detectors/generic.py +42 -0
- cloudsmith_cli/core/credentials/oidc/detectors/github_actions.py +64 -0
- cloudsmith_cli/core/credentials/oidc/detectors/gitlab_ci.py +49 -0
- cloudsmith_cli/core/credentials/oidc/exchange.py +87 -0
- cloudsmith_cli/core/credentials/provider.py +17 -0
- cloudsmith_cli/core/credentials/providers/__init__.py +15 -0
- cloudsmith_cli/core/credentials/providers/cli_flag.py +24 -0
- cloudsmith_cli/core/credentials/providers/credentials_file.py +24 -0
- cloudsmith_cli/core/credentials/providers/env_var.py +24 -0
- cloudsmith_cli/core/credentials/providers/keyring_provider.py +59 -0
- cloudsmith_cli/core/credentials/providers/oidc_provider.py +115 -0
- cloudsmith_cli/core/download.py +594 -0
- cloudsmith_cli/core/keyring.py +171 -0
- cloudsmith_cli/core/mcp/__init__.py +0 -0
- cloudsmith_cli/core/mcp/data.py +17 -0
- cloudsmith_cli/core/mcp/server.py +786 -0
- cloudsmith_cli/core/pagination.py +131 -0
- cloudsmith_cli/core/ratelimits.py +88 -0
- cloudsmith_cli/core/rest.py +255 -0
- cloudsmith_cli/core/utils.py +95 -0
- cloudsmith_cli/core/version.py +20 -0
- cloudsmith_cli/credential_helpers/__init__.py +7 -0
- cloudsmith_cli/credential_helpers/backends.py +41 -0
- cloudsmith_cli/credential_helpers/common.py +111 -0
- cloudsmith_cli/credential_helpers/custom_domains.py +281 -0
- cloudsmith_cli/credential_helpers/docker/__init__.py +4 -0
- cloudsmith_cli/credential_helpers/docker/installer.py +349 -0
- cloudsmith_cli/credential_helpers/docker/runtime.py +117 -0
- cloudsmith_cli/credential_helpers/launchers.py +175 -0
- cloudsmith_cli/data/VERSION +1 -0
- cloudsmith_cli/data/config.ini +23 -0
- cloudsmith_cli/data/credentials.ini +14 -0
- cloudsmith_cli/templates/__init__.py +3 -0
- cloudsmith_cli/templates/auth_error.html +45 -0
- cloudsmith_cli/templates/auth_success.html +37 -0
- cloudsmith_cli-1.20.0.dist-info/METADATA +610 -0
- cloudsmith_cli-1.20.0.dist-info/RECORD +130 -0
- cloudsmith_cli-1.20.0.dist-info/WHEEL +5 -0
- cloudsmith_cli-1.20.0.dist-info/entry_points.txt +2 -0
- cloudsmith_cli-1.20.0.dist-info/licenses/LICENSE +201 -0
- cloudsmith_cli-1.20.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
"""CLI/Commands - create, retrieve, update or delete repository upstreams."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from ...core.api import upstreams as api
|
|
8
|
+
from ...core.pagination import paginate_results
|
|
9
|
+
from .. import command, decorators, utils, validators
|
|
10
|
+
from ..exceptions import handle_api_exceptions
|
|
11
|
+
from ..utils import (
|
|
12
|
+
fmt_bool,
|
|
13
|
+
fmt_datetime,
|
|
14
|
+
maybe_spinner,
|
|
15
|
+
maybe_truncate_list,
|
|
16
|
+
maybe_truncate_string,
|
|
17
|
+
)
|
|
18
|
+
from .main import main
|
|
19
|
+
|
|
20
|
+
UPSTREAM_FORMATS = [
|
|
21
|
+
"alpine",
|
|
22
|
+
"cargo",
|
|
23
|
+
"conda",
|
|
24
|
+
"cran",
|
|
25
|
+
"dart",
|
|
26
|
+
"deb",
|
|
27
|
+
"docker",
|
|
28
|
+
"generic",
|
|
29
|
+
"go",
|
|
30
|
+
"helm",
|
|
31
|
+
"hex",
|
|
32
|
+
"huggingface",
|
|
33
|
+
"maven",
|
|
34
|
+
"npm",
|
|
35
|
+
"nuget",
|
|
36
|
+
"python",
|
|
37
|
+
"rpm",
|
|
38
|
+
"ruby",
|
|
39
|
+
"swift",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def print_upstreams(upstreams, upstream_fmt, page_info=None, page_all=False):
|
|
44
|
+
"""Print upstreams as a table or output in another format."""
|
|
45
|
+
|
|
46
|
+
def build_row(u):
|
|
47
|
+
row = [
|
|
48
|
+
click.style(u["name"], fg="cyan"),
|
|
49
|
+
click.style(maybe_truncate_string(u["upstream_url"]), fg="cyan"),
|
|
50
|
+
click.style(str(u["auth_mode"]), fg="yellow"),
|
|
51
|
+
click.style(
|
|
52
|
+
maybe_truncate_string(str(u["auth_secret"] or "")),
|
|
53
|
+
fg="yellow",
|
|
54
|
+
),
|
|
55
|
+
click.style(str(u["auth_username"] or ""), fg="yellow"),
|
|
56
|
+
click.style(fmt_datetime(u["created_at"]), fg="blue"),
|
|
57
|
+
click.style(str(u["extra_header_1"] or ""), fg="yellow"),
|
|
58
|
+
click.style(str(u["extra_header_2"] or ""), fg="yellow"),
|
|
59
|
+
click.style(str(u["extra_value_1"] or ""), fg="yellow"),
|
|
60
|
+
click.style(str(u["extra_value_2"] or ""), fg="yellow"),
|
|
61
|
+
click.style(fmt_bool(u["is_active"]), fg="green"),
|
|
62
|
+
click.style(u["mode"], fg="green"),
|
|
63
|
+
click.style(str(u["priority"]), fg="green"),
|
|
64
|
+
click.style(u["slug_perm"], fg="green"),
|
|
65
|
+
click.style(fmt_datetime(u["updated_at"]), fg="blue"),
|
|
66
|
+
click.style(fmt_bool(u["verify_ssl"]), fg="green"),
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
if upstream_fmt == "deb":
|
|
70
|
+
# `Component`, `Distribution Versions` and `Upstream Distribution` are deb-only
|
|
71
|
+
row.append(click.style(str(u.get("component", None)), fg="yellow"))
|
|
72
|
+
row.append(
|
|
73
|
+
click.style(
|
|
74
|
+
str(maybe_truncate_list(u.get("distro_versions", []))),
|
|
75
|
+
fg="yellow",
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
row.append(
|
|
79
|
+
click.style(str(u.get("upstream_distribution", None)), fg="yellow")
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if upstream_fmt == "rpm":
|
|
83
|
+
# `Distribution Version` is rpm-only
|
|
84
|
+
row.append(click.style(str(u.get("distro_version", "")), fg="yellow"))
|
|
85
|
+
|
|
86
|
+
return row
|
|
87
|
+
|
|
88
|
+
headers = [
|
|
89
|
+
"Name",
|
|
90
|
+
"Upstream Url",
|
|
91
|
+
"Auth mode",
|
|
92
|
+
"Auth Secret",
|
|
93
|
+
"Auth Username",
|
|
94
|
+
"Created At",
|
|
95
|
+
"Extra Header 1",
|
|
96
|
+
"Extra Header 2",
|
|
97
|
+
"Extra Value 1",
|
|
98
|
+
"Extra Value 2",
|
|
99
|
+
"Active",
|
|
100
|
+
"Mode",
|
|
101
|
+
"Priority",
|
|
102
|
+
"Slug Perm",
|
|
103
|
+
"Updated At",
|
|
104
|
+
"Verify SSL",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
if upstream_fmt == "deb":
|
|
108
|
+
headers.append("Component")
|
|
109
|
+
headers.append("Distribution Versions")
|
|
110
|
+
headers.append("Upstream Distribution")
|
|
111
|
+
|
|
112
|
+
if upstream_fmt == "rpm":
|
|
113
|
+
headers.append("Distribution Version")
|
|
114
|
+
|
|
115
|
+
rows = [build_row(x) for x in upstreams]
|
|
116
|
+
|
|
117
|
+
click.echo()
|
|
118
|
+
utils.pretty_print_table(headers, rows)
|
|
119
|
+
click.echo()
|
|
120
|
+
|
|
121
|
+
num_results = len(upstreams)
|
|
122
|
+
list_suffix = "upstream%s" % ("" if num_results == 1 else "s")
|
|
123
|
+
utils.pretty_print_list_info(
|
|
124
|
+
num_results=num_results,
|
|
125
|
+
page_info=None if page_all else page_info,
|
|
126
|
+
suffix=list_suffix,
|
|
127
|
+
page_all=page_all,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@main.group(cls=command.AliasGroup, name="upstream", aliases=[])
|
|
132
|
+
@decorators.common_cli_config_options
|
|
133
|
+
@decorators.common_cli_output_options
|
|
134
|
+
@decorators.common_api_auth_options
|
|
135
|
+
@decorators.initialise_api
|
|
136
|
+
@click.pass_context
|
|
137
|
+
def upstream(*args, **kwargs):
|
|
138
|
+
"""
|
|
139
|
+
Manage upstreams for a repository.
|
|
140
|
+
|
|
141
|
+
See the help for subcommands for more information on each.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def build_upstream_group_func(upstream_fmt):
|
|
146
|
+
@decorators.common_cli_config_options
|
|
147
|
+
@decorators.common_cli_output_options
|
|
148
|
+
@decorators.common_api_auth_options
|
|
149
|
+
@decorators.initialise_api
|
|
150
|
+
@click.pass_context
|
|
151
|
+
def func(ctx, opts):
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
func.__doc__ = (
|
|
155
|
+
"""
|
|
156
|
+
Manage %s upstreams for a repository.
|
|
157
|
+
|
|
158
|
+
See the help for subcommands for more information on each.
|
|
159
|
+
"""
|
|
160
|
+
% upstream_fmt
|
|
161
|
+
)
|
|
162
|
+
return func
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def build_upstream_list_command(upstream_fmt):
|
|
166
|
+
@decorators.common_cli_config_options
|
|
167
|
+
@decorators.common_cli_list_options
|
|
168
|
+
@decorators.common_cli_output_options
|
|
169
|
+
@decorators.common_api_auth_options
|
|
170
|
+
@decorators.initialise_api
|
|
171
|
+
@click.argument(
|
|
172
|
+
"owner_repo", metavar="OWNER/REPO", callback=validators.validate_owner_repo
|
|
173
|
+
)
|
|
174
|
+
@click.pass_context
|
|
175
|
+
def func(ctx, opts, owner_repo, page, page_size, page_all):
|
|
176
|
+
owner, repo = owner_repo
|
|
177
|
+
|
|
178
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
179
|
+
|
|
180
|
+
if not use_stderr:
|
|
181
|
+
click.echo("Getting upstreams... ", nl=False, err=use_stderr)
|
|
182
|
+
|
|
183
|
+
context_msg = "Failed to get upstreams!"
|
|
184
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
185
|
+
with maybe_spinner(opts):
|
|
186
|
+
upstreams, page_info = paginate_results(
|
|
187
|
+
api.list_upstreams,
|
|
188
|
+
page_all,
|
|
189
|
+
page,
|
|
190
|
+
page_size,
|
|
191
|
+
owner=owner,
|
|
192
|
+
repo=repo,
|
|
193
|
+
upstream_format=upstream_fmt,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if not use_stderr:
|
|
197
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
198
|
+
|
|
199
|
+
if utils.maybe_print_as_json(opts, upstreams, page_info):
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
print_upstreams(upstreams, upstream_fmt, page_info, page_all)
|
|
203
|
+
|
|
204
|
+
func.__doc__ = f"""
|
|
205
|
+
List {upstream_fmt} upstreams for a repository.
|
|
206
|
+
|
|
207
|
+
This requires appropriate permissions for the owner (a member of the organisation, repository privileges and a valid API key).
|
|
208
|
+
|
|
209
|
+
- OWNER/REPO: Specify the OWNER namespace (organization) and REPO (repository) to target a specific Cloudsmith repository.
|
|
210
|
+
|
|
211
|
+
Example: 'your-org/your-repo'
|
|
212
|
+
|
|
213
|
+
Full CLI example:
|
|
214
|
+
|
|
215
|
+
$ cloudsmith upstream {upstream_fmt} ls your-org/your-repo
|
|
216
|
+
"""
|
|
217
|
+
return func
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def build_upstream_create_command(upstream_fmt):
|
|
221
|
+
@decorators.common_cli_config_options
|
|
222
|
+
@decorators.common_cli_output_options
|
|
223
|
+
@decorators.common_api_auth_options
|
|
224
|
+
@decorators.initialise_api
|
|
225
|
+
@click.argument(
|
|
226
|
+
"owner_repo", metavar="OWNER/REPO", callback=validators.validate_owner_repo
|
|
227
|
+
)
|
|
228
|
+
@click.argument("upstream_config_file", type=click.File("rb"), required=True)
|
|
229
|
+
@click.pass_context
|
|
230
|
+
def func(ctx, opts, owner_repo, upstream_config_file):
|
|
231
|
+
# Use stderr for messages if the output is something else (e.g. JSON)
|
|
232
|
+
use_stderr = opts.output != "pretty"
|
|
233
|
+
|
|
234
|
+
owner, repo = owner_repo
|
|
235
|
+
|
|
236
|
+
upstream_config = json.load(upstream_config_file)
|
|
237
|
+
|
|
238
|
+
upstream_name = upstream_config.get("name", None)
|
|
239
|
+
|
|
240
|
+
if upstream_name is None:
|
|
241
|
+
raise click.BadParameter(
|
|
242
|
+
"Name is a required field for creating an upstream.", param="name"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
if not use_stderr:
|
|
246
|
+
click.secho(
|
|
247
|
+
'Creating "%(name)s" upstream for the %(owner)s/%(repo)s repository...'
|
|
248
|
+
% {
|
|
249
|
+
"name": click.style(upstream_name, bold=True),
|
|
250
|
+
"owner": click.style(owner, bold=True),
|
|
251
|
+
"repo": click.style(repo, bold=True),
|
|
252
|
+
},
|
|
253
|
+
nl=False,
|
|
254
|
+
err=use_stderr,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
context_msg = "Failed to create the upstream!"
|
|
258
|
+
|
|
259
|
+
with handle_api_exceptions(ctx, opts, context_msg=context_msg):
|
|
260
|
+
with maybe_spinner(opts):
|
|
261
|
+
upstream_resp_data = api.create_upstream(
|
|
262
|
+
owner, repo, upstream_fmt, upstream_config
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if not use_stderr:
|
|
266
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
267
|
+
|
|
268
|
+
if utils.maybe_print_as_json(opts, upstream_resp_data):
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
print_upstreams([upstream_resp_data], upstream_fmt)
|
|
272
|
+
|
|
273
|
+
func.__doc__ = f"""
|
|
274
|
+
Create a {upstream_fmt} upstream for a repository.
|
|
275
|
+
|
|
276
|
+
This requires appropriate permissions for the owner (a member of the organisation, repository privileges and a valid API key).
|
|
277
|
+
|
|
278
|
+
- OWNER/REPO: Specify the OWNER namespace (organization) and REPO (repository) to target a specific Cloudsmith repository.
|
|
279
|
+
|
|
280
|
+
Example: 'your-org/your-repo'
|
|
281
|
+
|
|
282
|
+
- UPSTREAM_CONFIG_FILE: Config json file specifying the settings for the upstream to be updated.
|
|
283
|
+
|
|
284
|
+
For a full list of supported config properties, please refer to the "body params" section of the api reference for the relevant endpoint at:
|
|
285
|
+
|
|
286
|
+
https://docs.cloudsmith.com/api/repos/upstream/{upstream_fmt}/create
|
|
287
|
+
|
|
288
|
+
\b
|
|
289
|
+
Example:
|
|
290
|
+
{{
|
|
291
|
+
"name": "Some Upstream",
|
|
292
|
+
"upstream_url": "https://someupstream.net",
|
|
293
|
+
"mode": "Proxy Only",
|
|
294
|
+
"auth_mode": "None",
|
|
295
|
+
"priority": 5,
|
|
296
|
+
...
|
|
297
|
+
}}
|
|
298
|
+
|
|
299
|
+
Full CLI example:
|
|
300
|
+
|
|
301
|
+
$ cloudsmith upstream {upstream_fmt} create your-org/your-repo ./path/to/upstream-config.json
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
return func
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def build_upstream_update_command(upstream_fmt):
|
|
308
|
+
@decorators.common_cli_config_options
|
|
309
|
+
@decorators.common_cli_output_options
|
|
310
|
+
@decorators.common_api_auth_options
|
|
311
|
+
@decorators.initialise_api
|
|
312
|
+
@click.argument(
|
|
313
|
+
"owner_repo_slug_perm",
|
|
314
|
+
metavar="OWNER/REPO/SLUG_PERM",
|
|
315
|
+
callback=validators.validate_owner_repo_slug_perm,
|
|
316
|
+
)
|
|
317
|
+
@click.argument("upstream_config_file", type=click.File("rb"), required=True)
|
|
318
|
+
@click.pass_context
|
|
319
|
+
def func(ctx, opts, owner_repo_slug_perm, upstream_config_file):
|
|
320
|
+
# Use stderr for message if the output is something else (e.g. JSON)
|
|
321
|
+
use_stderr = opts.output != "pretty"
|
|
322
|
+
|
|
323
|
+
owner, repo, slug_perm = owner_repo_slug_perm
|
|
324
|
+
|
|
325
|
+
upstream_config = json.load(upstream_config_file)
|
|
326
|
+
|
|
327
|
+
if not use_stderr:
|
|
328
|
+
click.secho(
|
|
329
|
+
"Updating the %(slug_perm)s upstream from the %(owner)s/%(repo)s repository ... "
|
|
330
|
+
% {
|
|
331
|
+
"owner": click.style(owner, bold=True),
|
|
332
|
+
"repo": click.style(repo, bold=True),
|
|
333
|
+
"slug_perm": click.style(slug_perm, bold=True),
|
|
334
|
+
},
|
|
335
|
+
nl=False,
|
|
336
|
+
err=use_stderr,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
context_msg = "Failed to update the upstream!"
|
|
340
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
341
|
+
with maybe_spinner(opts):
|
|
342
|
+
upstream_resp_data = api.update_upstream(
|
|
343
|
+
owner, repo, slug_perm, upstream_fmt, upstream_config
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
if not use_stderr:
|
|
347
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
348
|
+
|
|
349
|
+
if utils.maybe_print_as_json(opts, upstream_resp_data):
|
|
350
|
+
return
|
|
351
|
+
|
|
352
|
+
print_upstreams([upstream_resp_data], upstream_fmt)
|
|
353
|
+
|
|
354
|
+
func.__doc__ = f"""
|
|
355
|
+
Update a {upstream_fmt} upstream for a repository.
|
|
356
|
+
|
|
357
|
+
This requires appropriate permissions for the owner (a member of the organisation, repository privileges and a valid API key).
|
|
358
|
+
|
|
359
|
+
- OWNER/REPO/SLUG_PERM: Specify the OWNER namespace (organization), REPO (repository) and SLUG_PERM (upstream) to target a specific upstream belonging to a repo.
|
|
360
|
+
|
|
361
|
+
Example: 'your-org/your-repo/abcdefg'
|
|
362
|
+
|
|
363
|
+
- UPSTREAM_CONFIG_FILE: Config json file specifying the settings for the upstream to be updated.
|
|
364
|
+
|
|
365
|
+
For a full list of supported config properties, please refer to the "body params" section of the api reference for the relevant endpoint at:
|
|
366
|
+
|
|
367
|
+
https://docs.cloudsmith.com/api/repos/upstream/{upstream_fmt}/partial-update
|
|
368
|
+
|
|
369
|
+
\b
|
|
370
|
+
Example:
|
|
371
|
+
{{
|
|
372
|
+
"name": "Some Upstream",
|
|
373
|
+
"upstream_url": "https://someupstream.net",
|
|
374
|
+
"mode": "Proxy Only",
|
|
375
|
+
"auth_mode": "None",
|
|
376
|
+
"priority": 5,
|
|
377
|
+
...
|
|
378
|
+
}}
|
|
379
|
+
|
|
380
|
+
Full CLI example:
|
|
381
|
+
|
|
382
|
+
$ cloudsmith upstream {upstream_fmt} update your-org/your-repo/abcdefg ./path/to/upstream-config.json
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
return func
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def build_upstream_delete_command(upstream_fmt):
|
|
389
|
+
@decorators.common_cli_config_options
|
|
390
|
+
@decorators.common_cli_output_options
|
|
391
|
+
@decorators.common_api_auth_options
|
|
392
|
+
@decorators.initialise_api
|
|
393
|
+
@click.argument(
|
|
394
|
+
"owner_repo_slug_perm",
|
|
395
|
+
metavar="OWNER/REPO/SLUG_PERM",
|
|
396
|
+
callback=validators.validate_owner_repo_slug_perm,
|
|
397
|
+
)
|
|
398
|
+
@click.option(
|
|
399
|
+
"-y",
|
|
400
|
+
"--yes",
|
|
401
|
+
default=False,
|
|
402
|
+
is_flag=True,
|
|
403
|
+
help="Assume yes as default answer to questions (this is dangerous!)",
|
|
404
|
+
)
|
|
405
|
+
@click.pass_context
|
|
406
|
+
def func(ctx, opts, owner_repo_slug_perm, yes):
|
|
407
|
+
# Use stderr for message if the output is something else (e.g. JSON)
|
|
408
|
+
use_stderr = opts.output != "pretty"
|
|
409
|
+
|
|
410
|
+
owner, repo, slug_perm = owner_repo_slug_perm
|
|
411
|
+
|
|
412
|
+
delete_args = {
|
|
413
|
+
"owner": click.style(owner, bold=True),
|
|
414
|
+
"repo": click.style(repo, bold=True),
|
|
415
|
+
"slug_perm": click.style(slug_perm, bold=True),
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
prompt = (
|
|
419
|
+
"delete the %(slug_perm)s upstream from the %(owner)s/%(repo)s repository"
|
|
420
|
+
% delete_args
|
|
421
|
+
)
|
|
422
|
+
if not utils.confirm_operation(prompt, assume_yes=yes, err=use_stderr):
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
if not use_stderr:
|
|
426
|
+
click.secho(
|
|
427
|
+
"Deleting the %(slug_perm)s upstream from the %(owner)s/%(repo)s repository ... "
|
|
428
|
+
% delete_args,
|
|
429
|
+
nl=False,
|
|
430
|
+
err=use_stderr,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
context_msg = "Failed to delete the upstream!"
|
|
434
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
435
|
+
with maybe_spinner(opts):
|
|
436
|
+
api.delete_upstream(owner, repo, upstream_fmt, slug_perm)
|
|
437
|
+
|
|
438
|
+
if not use_stderr:
|
|
439
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
440
|
+
|
|
441
|
+
func.__doc__ = f"""
|
|
442
|
+
Delete a {upstream_fmt} upstream for a repository.
|
|
443
|
+
|
|
444
|
+
This requires appropriate permissions for the owner (a member of the organisation, repository privileges and a valid API key).
|
|
445
|
+
|
|
446
|
+
- OWNER/REPO/SLUG_PERM: Specify the OWNER namespace (organization), REPO (repository) and SLUG_PERM (upstream) to target a specific upstream belonging to a repo.
|
|
447
|
+
|
|
448
|
+
Example: 'your-org/your-repo/abcdefg'
|
|
449
|
+
|
|
450
|
+
Full CLI example:
|
|
451
|
+
|
|
452
|
+
$ cloudsmith upstream {upstream_fmt} delete your-org/your-repo/abcdefg
|
|
453
|
+
"""
|
|
454
|
+
|
|
455
|
+
return func
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
for upstream_format in UPSTREAM_FORMATS:
|
|
459
|
+
# Build a click group for the upstream name e.g. dart, npm, ruby.
|
|
460
|
+
# Add it to the "upstream" parent group.
|
|
461
|
+
# This gives us e.g. `cloudsmith upstream dart` in the cli.
|
|
462
|
+
upstream_group = upstream.group(
|
|
463
|
+
cls=command.AliasGroup, name=upstream_format, aliases=[]
|
|
464
|
+
)(build_upstream_group_func(upstream_format))
|
|
465
|
+
|
|
466
|
+
# Add create/list/update/delete commands to the child group we created above.
|
|
467
|
+
# This gives us e.g. `cloudsmith upstream dart ls`.
|
|
468
|
+
upstream_group.command(name="list", aliases=["ls"])(
|
|
469
|
+
build_upstream_list_command(upstream_format)
|
|
470
|
+
)
|
|
471
|
+
upstream_group.command(name="create", aliases=["new"])(
|
|
472
|
+
build_upstream_create_command(upstream_format)
|
|
473
|
+
)
|
|
474
|
+
upstream_group.command(name="delete", aliases=["rm"])(
|
|
475
|
+
build_upstream_delete_command(upstream_format)
|
|
476
|
+
)
|
|
477
|
+
upstream_group.command(name="update")(
|
|
478
|
+
build_upstream_update_command(upstream_format)
|
|
479
|
+
)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""CLI/Commands - Vulnerabilities."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from ...core.api.vulnerabilities import (
|
|
6
|
+
_print_vulnerabilities_assessment_table,
|
|
7
|
+
_print_vulnerabilities_summary_table,
|
|
8
|
+
get_package_scan_result,
|
|
9
|
+
)
|
|
10
|
+
from .. import decorators, utils, validators
|
|
11
|
+
from ..exceptions import handle_api_exceptions
|
|
12
|
+
from .main import main
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@main.command()
|
|
16
|
+
@decorators.common_cli_config_options
|
|
17
|
+
@decorators.common_cli_output_options
|
|
18
|
+
@decorators.common_api_auth_options
|
|
19
|
+
@decorators.initialise_api
|
|
20
|
+
@click.argument(
|
|
21
|
+
"owner_repo_package",
|
|
22
|
+
metavar="OWNER/REPO/PACKAGE",
|
|
23
|
+
callback=validators.validate_owner_repo_package,
|
|
24
|
+
)
|
|
25
|
+
@click.option(
|
|
26
|
+
"-A",
|
|
27
|
+
"--show-assessment",
|
|
28
|
+
is_flag=True,
|
|
29
|
+
help="Show assessment with vulnerability details.",
|
|
30
|
+
)
|
|
31
|
+
@click.option(
|
|
32
|
+
"--fixable/--non-fixable",
|
|
33
|
+
is_flag=True,
|
|
34
|
+
default=None, # Changed to allow None (Show All) vs True/False
|
|
35
|
+
help="Filter by fixable status (only fixable vs only non-fixable).",
|
|
36
|
+
)
|
|
37
|
+
@click.option(
|
|
38
|
+
"--severity",
|
|
39
|
+
"severity_filter",
|
|
40
|
+
help="Filter by severities (e.g., 'CRITICAL', 'HIGH', 'MEDIUM', 'LOW').",
|
|
41
|
+
)
|
|
42
|
+
@click.pass_context
|
|
43
|
+
def vulnerabilities(
|
|
44
|
+
ctx, opts, owner_repo_package, show_assessment, fixable, severity_filter
|
|
45
|
+
):
|
|
46
|
+
"""
|
|
47
|
+
Retrieve vulnerability scan results for a package.
|
|
48
|
+
|
|
49
|
+
\b
|
|
50
|
+
Usage:
|
|
51
|
+
cloudsmith vulnerabilities myorg/repo/pkg_identifier [flags]
|
|
52
|
+
|
|
53
|
+
\b
|
|
54
|
+
Aliases:
|
|
55
|
+
vulnerabilities, vuln
|
|
56
|
+
|
|
57
|
+
Examples:
|
|
58
|
+
|
|
59
|
+
\b
|
|
60
|
+
# Display the vulnerability summary
|
|
61
|
+
cloudsmith vulnerabilities myorg/repo/pkg_identifier
|
|
62
|
+
|
|
63
|
+
\b
|
|
64
|
+
# Display detailed vulnerability assessment
|
|
65
|
+
cloudsmith vulnerabilities myorg/repo/pkg_identifier -A / --show-assessment
|
|
66
|
+
|
|
67
|
+
\b
|
|
68
|
+
# Filter the result by severity
|
|
69
|
+
cloudsmith vulnerabilities myorg/repo/pkg_identifier --severity critical,high
|
|
70
|
+
|
|
71
|
+
\b
|
|
72
|
+
# Filter by fixable or non-fixable vulnerabilities
|
|
73
|
+
cloudsmith vulnerabilities myorg/repo/pkg_identifier --fixable / --non-fixable
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
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,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
97
|
+
|
|
98
|
+
# Filter results if severity or fixable flags are active
|
|
99
|
+
if severity_filter or fixable is not None:
|
|
100
|
+
scans = getattr(data, "scans", [])
|
|
101
|
+
|
|
102
|
+
allowed_severities = (
|
|
103
|
+
[s.strip().lower() for s in severity_filter.split(",")]
|
|
104
|
+
if severity_filter
|
|
105
|
+
else None
|
|
106
|
+
)
|
|
107
|
+
|
|
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)
|