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,375 @@
|
|
|
1
|
+
"""CLI/Commands - List objects."""
|
|
2
|
+
|
|
3
|
+
from operator import itemgetter
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from ...core.api.packages import (
|
|
8
|
+
get_package_tags as api_get_package_tags,
|
|
9
|
+
tag_package as api_tag_package,
|
|
10
|
+
)
|
|
11
|
+
from .. import command, decorators, utils, validators
|
|
12
|
+
from ..exceptions import handle_api_exceptions
|
|
13
|
+
from ..utils import maybe_spinner
|
|
14
|
+
from .main import main
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _parse_tags(tags):
|
|
18
|
+
"""Parse tags from CSV into a list."""
|
|
19
|
+
return [x.strip() for x in (tags or "").split(",")]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _print_tags(opts, all_tags, all_immutable_tags):
|
|
23
|
+
"""Print the tags for a package."""
|
|
24
|
+
all_combined_tags = {"tags": all_tags, "tags_immutable": all_immutable_tags}
|
|
25
|
+
|
|
26
|
+
if utils.maybe_print_as_json(opts, all_combined_tags):
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
headers = ["Tag", "Type", "Immutable"]
|
|
30
|
+
|
|
31
|
+
rows = []
|
|
32
|
+
for tag_type, tags in sorted(all_tags.items(), key=itemgetter(0)):
|
|
33
|
+
immutable_tags = all_immutable_tags.get(tag_type) or []
|
|
34
|
+
for tag in sorted(tags):
|
|
35
|
+
immutable = "Yes" if tag in immutable_tags else "No"
|
|
36
|
+
rows.append(
|
|
37
|
+
[
|
|
38
|
+
click.style(tag, fg="cyan"),
|
|
39
|
+
click.style(tag_type, fg="yellow"),
|
|
40
|
+
click.style(immutable, fg="magenta"),
|
|
41
|
+
]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if all_tags:
|
|
45
|
+
click.echo()
|
|
46
|
+
utils.pretty_print_table(headers, rows)
|
|
47
|
+
|
|
48
|
+
click.echo()
|
|
49
|
+
|
|
50
|
+
num_results = len(rows)
|
|
51
|
+
list_suffix = "tag%s" % ("s" if num_results != 1 else "")
|
|
52
|
+
utils.pretty_print_list_info(num_results=num_results, suffix=list_suffix)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@main.group(name="tags", cls=command.AliasGroup, aliases=["tag"])
|
|
56
|
+
@decorators.common_cli_config_options
|
|
57
|
+
@decorators.common_cli_output_options
|
|
58
|
+
@decorators.common_api_auth_options
|
|
59
|
+
@decorators.initialise_api
|
|
60
|
+
@click.pass_context
|
|
61
|
+
def tags_(ctx, opts): # pylint: disable=unused-argument
|
|
62
|
+
"""
|
|
63
|
+
Manage the tags for a package in a repository.
|
|
64
|
+
|
|
65
|
+
See the help for subcommands for more information on each.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@tags_.command(name="list", aliases=["ls"])
|
|
70
|
+
@decorators.common_cli_config_options
|
|
71
|
+
@decorators.common_cli_output_options
|
|
72
|
+
@decorators.common_api_auth_options
|
|
73
|
+
@decorators.initialise_api
|
|
74
|
+
@click.argument(
|
|
75
|
+
"owner_repo_package",
|
|
76
|
+
metavar="OWNER/REPO/PACKAGE",
|
|
77
|
+
callback=validators.validate_owner_repo_package,
|
|
78
|
+
)
|
|
79
|
+
@click.pass_context
|
|
80
|
+
def list_tags(ctx, opts, owner_repo_package):
|
|
81
|
+
"""
|
|
82
|
+
List tags for a package in a repository.
|
|
83
|
+
|
|
84
|
+
This requires appropriate (read) permissions for the package.
|
|
85
|
+
|
|
86
|
+
- OWNER/REPO/PACKAGE: Specify the OWNER namespace (i.e. user or org), the
|
|
87
|
+
REPO name where the package is stored, and the PACKAGE identifier of the
|
|
88
|
+
package itself. All separated by a slash.
|
|
89
|
+
|
|
90
|
+
Example: 'your-org/awesome-repo/better-pkg'.
|
|
91
|
+
|
|
92
|
+
Full CLI example:
|
|
93
|
+
|
|
94
|
+
$ cloudsmith tags list your-org/awesome-repo/better-pkg
|
|
95
|
+
"""
|
|
96
|
+
owner, repo, package = owner_repo_package
|
|
97
|
+
|
|
98
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
99
|
+
|
|
100
|
+
click.echo(
|
|
101
|
+
"Listing tags for the '%(package)s' package ... "
|
|
102
|
+
% {"package": click.style(package, bold=True)},
|
|
103
|
+
nl=False,
|
|
104
|
+
err=use_stderr,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
context_msg = "Failed to list tags for the package!"
|
|
108
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
109
|
+
with maybe_spinner(opts):
|
|
110
|
+
package_tags, package_tags_immutable = api_get_package_tags(
|
|
111
|
+
owner=owner, repo=repo, identifier=package
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
115
|
+
|
|
116
|
+
_print_tags(opts, package_tags, package_tags_immutable)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@tags_.command(name="add")
|
|
120
|
+
@decorators.common_cli_config_options
|
|
121
|
+
@decorators.common_cli_output_options
|
|
122
|
+
@decorators.common_api_auth_options
|
|
123
|
+
@decorators.initialise_api
|
|
124
|
+
@click.argument(
|
|
125
|
+
"owner_repo_package",
|
|
126
|
+
metavar="OWNER/REPO/PACKAGE",
|
|
127
|
+
callback=validators.validate_owner_repo_package,
|
|
128
|
+
)
|
|
129
|
+
@click.argument("tags", metavar="TAGS")
|
|
130
|
+
@click.option(
|
|
131
|
+
"--immutable",
|
|
132
|
+
default=False,
|
|
133
|
+
is_flag=True,
|
|
134
|
+
help=(
|
|
135
|
+
"If true, the tags created will be immutable (cannot be changed). In "
|
|
136
|
+
"practice, this means the tags cannot be (easily) deleted. A repository "
|
|
137
|
+
"admin can explicitly remove immutable tags."
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
@click.pass_context
|
|
141
|
+
def add_tags(ctx, opts, owner_repo_package, tags, immutable):
|
|
142
|
+
"""
|
|
143
|
+
Add tags to a package in a repository.
|
|
144
|
+
|
|
145
|
+
This requires appropriate (write) permissions for the package.
|
|
146
|
+
|
|
147
|
+
- OWNER/REPO/PACKAGE: Specify the OWNER namespace (i.e. user or org), the
|
|
148
|
+
REPO name where the package is stored, and the PACKAGE identifier of the
|
|
149
|
+
package itself. All separated by a slash.
|
|
150
|
+
|
|
151
|
+
Example: 'your-org/awesome-repo/better-pkg'.
|
|
152
|
+
|
|
153
|
+
- TAGS: A comma-separated value list of the tags you want to add.
|
|
154
|
+
|
|
155
|
+
Example: foo,bar
|
|
156
|
+
|
|
157
|
+
Full CLI example:
|
|
158
|
+
|
|
159
|
+
$ cloudsmith tags add your-org/awesome-repo/better-pkg foo,bar
|
|
160
|
+
"""
|
|
161
|
+
owner, repo, package = owner_repo_package
|
|
162
|
+
tags = _parse_tags(tags)
|
|
163
|
+
|
|
164
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
165
|
+
|
|
166
|
+
click.echo(
|
|
167
|
+
"Adding '%(tags)s' tag%(s)s to the '%(package)s' package ... "
|
|
168
|
+
% {
|
|
169
|
+
"package": click.style(package, bold=True),
|
|
170
|
+
"tags": click.style(", ".join(tags or [])),
|
|
171
|
+
"s": "s" if len(tags) != 1 else "",
|
|
172
|
+
},
|
|
173
|
+
nl=False,
|
|
174
|
+
err=use_stderr,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
context_msg = "Failed to add tags to package!"
|
|
178
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
179
|
+
with maybe_spinner(opts):
|
|
180
|
+
package_tags, package_tags_immutable = api_tag_package(
|
|
181
|
+
owner=owner,
|
|
182
|
+
repo=repo,
|
|
183
|
+
identifier=package,
|
|
184
|
+
data={"action": "add", "tags": tags, "is_immutable": immutable},
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
188
|
+
|
|
189
|
+
_print_tags(opts, package_tags, package_tags_immutable)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@tags_.command(name="clear")
|
|
193
|
+
@decorators.common_cli_config_options
|
|
194
|
+
@decorators.common_cli_output_options
|
|
195
|
+
@decorators.common_api_auth_options
|
|
196
|
+
@decorators.initialise_api
|
|
197
|
+
@click.argument(
|
|
198
|
+
"owner_repo_package",
|
|
199
|
+
metavar="OWNER/REPO/PACKAGE",
|
|
200
|
+
callback=validators.validate_owner_repo_package,
|
|
201
|
+
)
|
|
202
|
+
@click.pass_context
|
|
203
|
+
def clear_tags(ctx, opts, owner_repo_package):
|
|
204
|
+
"""
|
|
205
|
+
Clear all existing (non-immutable) tags from a package in a repository.
|
|
206
|
+
|
|
207
|
+
This requires appropriate (write) permissions for the package.
|
|
208
|
+
|
|
209
|
+
- OWNER/REPO/PACKAGE: Specify the OWNER namespace (i.e. user or org), the
|
|
210
|
+
REPO name where the package is stored, and the PACKAGE identifier of the
|
|
211
|
+
package itself. All separated by a slash.
|
|
212
|
+
|
|
213
|
+
Example: 'your-org/awesome-repo/better-pkg'.
|
|
214
|
+
|
|
215
|
+
Full CLI example:
|
|
216
|
+
|
|
217
|
+
$ cloudsmith tags clear your-org/awesome-repo/better-pkg
|
|
218
|
+
"""
|
|
219
|
+
owner, repo, package = owner_repo_package
|
|
220
|
+
|
|
221
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
222
|
+
|
|
223
|
+
click.echo(
|
|
224
|
+
"Clearing tags on the '%(package)s' package ... "
|
|
225
|
+
% {"package": click.style(package, bold=True)},
|
|
226
|
+
nl=False,
|
|
227
|
+
err=use_stderr,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
context_msg = "Failed to clear tags on package!"
|
|
231
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
232
|
+
with maybe_spinner(opts):
|
|
233
|
+
package_tags, package_tags_immutable = api_tag_package(
|
|
234
|
+
owner=owner, repo=repo, identifier=package, data={"action": "clear"}
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
238
|
+
|
|
239
|
+
_print_tags(opts, package_tags, package_tags_immutable)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@tags_.command(name="remove", aliases=["rm"])
|
|
243
|
+
@decorators.common_cli_config_options
|
|
244
|
+
@decorators.common_cli_output_options
|
|
245
|
+
@decorators.common_api_auth_options
|
|
246
|
+
@decorators.initialise_api
|
|
247
|
+
@click.argument(
|
|
248
|
+
"owner_repo_package",
|
|
249
|
+
metavar="OWNER/REPO/PACKAGE",
|
|
250
|
+
callback=validators.validate_owner_repo_package,
|
|
251
|
+
)
|
|
252
|
+
@click.argument("tags", metavar="TAGS")
|
|
253
|
+
@click.pass_context
|
|
254
|
+
def remove_tags(ctx, opts, owner_repo_package, tags):
|
|
255
|
+
"""
|
|
256
|
+
Remove tags from a package in a repository.
|
|
257
|
+
|
|
258
|
+
This requires appropriate (write) permissions for the package.
|
|
259
|
+
|
|
260
|
+
- OWNER/REPO/PACKAGE: Specify the OWNER namespace (i.e. user or org), the
|
|
261
|
+
REPO name where the package is stored, and the PACKAGE identifier of the
|
|
262
|
+
package itself. All separated by a slash.
|
|
263
|
+
|
|
264
|
+
Example: 'your-org/awesome-repo/better-pkg'.
|
|
265
|
+
|
|
266
|
+
- TAGS: A comma-separated value list of the tags you want to remove.
|
|
267
|
+
|
|
268
|
+
Example: foo,bar
|
|
269
|
+
|
|
270
|
+
Full CLI example:
|
|
271
|
+
|
|
272
|
+
$ cloudsmith tags remove your-org/awesome-repo/better-pkg foo,bar
|
|
273
|
+
"""
|
|
274
|
+
owner, repo, package = owner_repo_package
|
|
275
|
+
tags = _parse_tags(tags)
|
|
276
|
+
|
|
277
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
278
|
+
|
|
279
|
+
click.echo(
|
|
280
|
+
"Removing '%(tags)s' tag%(s)s from the '%(package)s' package ... "
|
|
281
|
+
% {
|
|
282
|
+
"package": click.style(package, bold=True),
|
|
283
|
+
"tags": click.style(", ".join(tags or [])),
|
|
284
|
+
"s": "s" if len(tags) != 1 else "",
|
|
285
|
+
},
|
|
286
|
+
nl=False,
|
|
287
|
+
err=use_stderr,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
context_msg = "Failed to remove tags from package!"
|
|
291
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
292
|
+
with maybe_spinner(opts):
|
|
293
|
+
package_tags, package_tags_immutable = api_tag_package(
|
|
294
|
+
owner=owner,
|
|
295
|
+
repo=repo,
|
|
296
|
+
identifier=package,
|
|
297
|
+
data={"action": "remove", "tags": tags},
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
301
|
+
|
|
302
|
+
_print_tags(opts, package_tags, package_tags_immutable)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@tags_.command(name="replace")
|
|
306
|
+
@decorators.common_cli_config_options
|
|
307
|
+
@decorators.common_cli_output_options
|
|
308
|
+
@decorators.common_api_auth_options
|
|
309
|
+
@decorators.initialise_api
|
|
310
|
+
@click.argument(
|
|
311
|
+
"owner_repo_package",
|
|
312
|
+
metavar="OWNER/REPO/PACKAGE",
|
|
313
|
+
callback=validators.validate_owner_repo_package,
|
|
314
|
+
)
|
|
315
|
+
@click.argument("tags", metavar="TAGS")
|
|
316
|
+
@click.option(
|
|
317
|
+
"--immutable",
|
|
318
|
+
default=False,
|
|
319
|
+
is_flag=True,
|
|
320
|
+
help=(
|
|
321
|
+
"If true, the tags created will be immutable (cannot be changed). In "
|
|
322
|
+
"practice, this means the tags cannot be (easily) deleted. A repository "
|
|
323
|
+
"admin can explicitly remove immutable tags."
|
|
324
|
+
),
|
|
325
|
+
)
|
|
326
|
+
@click.pass_context
|
|
327
|
+
def replace_tags(ctx, opts, owner_repo_package, tags, immutable):
|
|
328
|
+
"""
|
|
329
|
+
Replace all existing (non-immutable) tags on a package in a repository.
|
|
330
|
+
|
|
331
|
+
This requires appropriate (write) permissions for the package.
|
|
332
|
+
|
|
333
|
+
- OWNER/REPO/PACKAGE: Specify the OWNER namespace (i.e. user or org), the
|
|
334
|
+
REPO name where the package is stored, and the PACKAGE identifier of the
|
|
335
|
+
package itself. All separated by a slash.
|
|
336
|
+
|
|
337
|
+
Example: 'your-org/awesome-repo/better-pkg'.
|
|
338
|
+
|
|
339
|
+
- TAGS: A comma-separated value list of the tags you want to replace existing with.
|
|
340
|
+
|
|
341
|
+
Example: foo,bar
|
|
342
|
+
|
|
343
|
+
Full CLI example:
|
|
344
|
+
|
|
345
|
+
$ cloudsmith tags replace your-org/awesome-repo/better-pkg foo,bar
|
|
346
|
+
"""
|
|
347
|
+
owner, repo, package = owner_repo_package
|
|
348
|
+
tags = _parse_tags(tags)
|
|
349
|
+
|
|
350
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
351
|
+
|
|
352
|
+
click.echo(
|
|
353
|
+
"Replacing existing with '%(tags)s' tag%(s)s on the '%(package)s' package ... "
|
|
354
|
+
% {
|
|
355
|
+
"package": click.style(package, bold=True),
|
|
356
|
+
"tags": click.style(", ".join(tags or [])),
|
|
357
|
+
"s": "s" if len(tags) != 1 else "",
|
|
358
|
+
},
|
|
359
|
+
nl=False,
|
|
360
|
+
err=use_stderr,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
context_msg = "Failed to replace tags on package!"
|
|
364
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
365
|
+
with maybe_spinner(opts):
|
|
366
|
+
package_tags, package_tags_immutable = api_tag_package(
|
|
367
|
+
owner=owner,
|
|
368
|
+
repo=repo,
|
|
369
|
+
identifier=package,
|
|
370
|
+
data={"action": "replace", "tags": tags, "is_immutable": immutable},
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
374
|
+
|
|
375
|
+
_print_tags(opts, package_tags, package_tags_immutable)
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from ...core.api import exceptions, user as api
|
|
4
|
+
from ...core.config import create_config_files, new_config_messaging
|
|
5
|
+
from .. import command, decorators, utils
|
|
6
|
+
from ..exceptions import handle_api_exceptions
|
|
7
|
+
from .main import main
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def handle_duplicate_token_error(exc, ctx, opts, save_config, force, json):
|
|
11
|
+
"""
|
|
12
|
+
Handle the case where user already has a token.
|
|
13
|
+
|
|
14
|
+
Returns the token from refresh if user confirms, otherwise exits.
|
|
15
|
+
"""
|
|
16
|
+
if (
|
|
17
|
+
exc.status == 400
|
|
18
|
+
and exc.detail
|
|
19
|
+
and "User has already created an API key" in exc.detail
|
|
20
|
+
):
|
|
21
|
+
if not force:
|
|
22
|
+
if not click.confirm(
|
|
23
|
+
"User already has a token. Would you like to recreate it?",
|
|
24
|
+
abort=False,
|
|
25
|
+
err=json,
|
|
26
|
+
):
|
|
27
|
+
return None
|
|
28
|
+
return refresh_existing_token_interactive(
|
|
29
|
+
ctx, opts, save_config=save_config, force=force, json=json
|
|
30
|
+
)
|
|
31
|
+
raise exc
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def request_api_key(ctx, opts, save_config=False):
|
|
35
|
+
"""
|
|
36
|
+
Request an API key non-interactively.
|
|
37
|
+
|
|
38
|
+
This function creates a new token or rotates an existing one without any prompts.
|
|
39
|
+
Used by the --request-api-key flag in the auth command.
|
|
40
|
+
|
|
41
|
+
Returns the token object on success.
|
|
42
|
+
Raises ApiException on failure.
|
|
43
|
+
"""
|
|
44
|
+
context_msg = "Failed to retrieve API token!"
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
# Don't use handle_api_exceptions here so we can catch and handle
|
|
48
|
+
# the "already has token" error ourselves
|
|
49
|
+
with utils.maybe_spinner(opts):
|
|
50
|
+
new_token = api.create_user_token_saml()
|
|
51
|
+
|
|
52
|
+
if save_config:
|
|
53
|
+
create, has_errors = create_config_files(
|
|
54
|
+
ctx, opts, api_key=new_token.key, force=True
|
|
55
|
+
)
|
|
56
|
+
new_config_messaging(has_errors, opts, create, api_key=new_token.key)
|
|
57
|
+
|
|
58
|
+
return new_token
|
|
59
|
+
|
|
60
|
+
except exceptions.ApiException as exc:
|
|
61
|
+
if exc.status == 401:
|
|
62
|
+
# Unauthorized - re-raise with handler
|
|
63
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
64
|
+
raise
|
|
65
|
+
|
|
66
|
+
if (
|
|
67
|
+
exc.status == 400
|
|
68
|
+
and exc.detail
|
|
69
|
+
and "User has already created an API key" in exc.detail
|
|
70
|
+
):
|
|
71
|
+
# Token exists - rotate it automatically
|
|
72
|
+
click.echo(
|
|
73
|
+
"Warning: Rotating existing API token. Your old key will be invalidated.",
|
|
74
|
+
err=True,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# List tokens and select the first one
|
|
78
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
79
|
+
with utils.maybe_spinner(opts):
|
|
80
|
+
api_tokens = api.list_user_tokens()
|
|
81
|
+
|
|
82
|
+
if not api_tokens:
|
|
83
|
+
raise click.ClickException("No existing tokens found to rotate.")
|
|
84
|
+
|
|
85
|
+
token_slug = api_tokens[0].slug_perm
|
|
86
|
+
|
|
87
|
+
# Refresh the token
|
|
88
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
89
|
+
with utils.maybe_spinner(opts):
|
|
90
|
+
new_token = api.refresh_user_token(token_slug)
|
|
91
|
+
|
|
92
|
+
if save_config:
|
|
93
|
+
create, has_errors = create_config_files(
|
|
94
|
+
ctx, opts, api_key=new_token.key, force=True
|
|
95
|
+
)
|
|
96
|
+
new_config_messaging(has_errors, opts, create, api_key=new_token.key)
|
|
97
|
+
|
|
98
|
+
return new_token
|
|
99
|
+
|
|
100
|
+
# Other errors - use the handler
|
|
101
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
102
|
+
raise exc
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@main.group(cls=command.AliasGroup, name="tokens")
|
|
106
|
+
@decorators.common_cli_config_options
|
|
107
|
+
@decorators.common_cli_output_options
|
|
108
|
+
@click.pass_context
|
|
109
|
+
def tokens(ctx, opts):
|
|
110
|
+
"""Manage your user API tokens."""
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@tokens.command(name="list", aliases=["ls"])
|
|
114
|
+
@decorators.common_cli_config_options
|
|
115
|
+
@decorators.common_cli_output_options
|
|
116
|
+
@decorators.common_api_auth_options
|
|
117
|
+
@decorators.initialise_api
|
|
118
|
+
@click.pass_context
|
|
119
|
+
def list_tokens(ctx, opts):
|
|
120
|
+
"""List all user API tokens."""
|
|
121
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
122
|
+
|
|
123
|
+
click.echo("Retrieving API tokens... ", nl=False, err=use_stderr)
|
|
124
|
+
|
|
125
|
+
context_msg = "Failed to retrieve API tokens!"
|
|
126
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
127
|
+
with utils.maybe_spinner(opts):
|
|
128
|
+
tokens = api.list_user_tokens()
|
|
129
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
130
|
+
|
|
131
|
+
if utils.maybe_print_as_json(opts, tokens):
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
print_tokens(tokens)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@tokens.command()
|
|
138
|
+
@click.option(
|
|
139
|
+
"--save-config",
|
|
140
|
+
default=False,
|
|
141
|
+
is_flag=True,
|
|
142
|
+
help="Save the new API key to your configuration files.",
|
|
143
|
+
)
|
|
144
|
+
@click.option(
|
|
145
|
+
"-f",
|
|
146
|
+
"--force",
|
|
147
|
+
default=False,
|
|
148
|
+
is_flag=True,
|
|
149
|
+
help="Force creation of user API token without prompts.",
|
|
150
|
+
)
|
|
151
|
+
@decorators.common_cli_config_options
|
|
152
|
+
@decorators.common_cli_output_options
|
|
153
|
+
@decorators.common_api_auth_options
|
|
154
|
+
@decorators.initialise_api
|
|
155
|
+
@click.pass_context
|
|
156
|
+
def create(ctx, opts, save_config, force=False, json=False):
|
|
157
|
+
"""Create a new API token."""
|
|
158
|
+
new_token = _create(ctx, opts, save_config, force, json)
|
|
159
|
+
|
|
160
|
+
if save_config:
|
|
161
|
+
create, has_errors = create_config_files(
|
|
162
|
+
ctx, opts, api_key=new_token.key, force=force
|
|
163
|
+
)
|
|
164
|
+
new_config_messaging(has_errors, opts, create, api_key=new_token.key)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@tokens.command()
|
|
168
|
+
@click.argument(
|
|
169
|
+
"token_slug",
|
|
170
|
+
required=False,
|
|
171
|
+
)
|
|
172
|
+
@click.option(
|
|
173
|
+
"--save-config",
|
|
174
|
+
default=False,
|
|
175
|
+
is_flag=True,
|
|
176
|
+
help="Save the new API key to your configuration files.",
|
|
177
|
+
)
|
|
178
|
+
@click.option(
|
|
179
|
+
"-f",
|
|
180
|
+
"--force",
|
|
181
|
+
default=False,
|
|
182
|
+
is_flag=True,
|
|
183
|
+
help="Force refresh of user API token without prompts.",
|
|
184
|
+
)
|
|
185
|
+
@decorators.common_cli_config_options
|
|
186
|
+
@decorators.common_cli_output_options
|
|
187
|
+
@decorators.common_api_auth_options
|
|
188
|
+
@decorators.initialise_api
|
|
189
|
+
@click.pass_context
|
|
190
|
+
def refresh(ctx, opts, token_slug, force, save_config):
|
|
191
|
+
"""Refresh a specific API token by its slug."""
|
|
192
|
+
new_token = refresh_existing_token_interactive(
|
|
193
|
+
ctx, opts, token_slug, save_config, force
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if new_token:
|
|
197
|
+
if utils.maybe_print_as_json(opts, new_token):
|
|
198
|
+
return new_token
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def print_tokens(tokens):
|
|
202
|
+
for token in tokens:
|
|
203
|
+
click.echo(
|
|
204
|
+
f"Token: {click.style(token.key, fg='magenta')}, "
|
|
205
|
+
f"Created: {click.style(token.created, fg='green')}, "
|
|
206
|
+
f"slug_perm: {click.style(token.slug_perm, fg='cyan')}"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def refresh_existing_token_interactive(
|
|
211
|
+
ctx, opts, token_slug=None, save_config=False, force=False, json=False
|
|
212
|
+
):
|
|
213
|
+
"""Refresh an existing API token with interactive token selection, or create new if none exist."""
|
|
214
|
+
json = json or utils.should_use_stderr(opts)
|
|
215
|
+
use_stderr = json
|
|
216
|
+
context_msg = "Failed to refresh the token!"
|
|
217
|
+
|
|
218
|
+
if not token_slug:
|
|
219
|
+
try:
|
|
220
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
221
|
+
api_tokens = api.list_user_tokens()
|
|
222
|
+
except exceptions.ApiException as exc:
|
|
223
|
+
# If we can't list tokens due to API error, fall back to creating a new one
|
|
224
|
+
if opts.debug:
|
|
225
|
+
click.echo(f"Debug: Failed to list tokens with error: {exc}", err=True)
|
|
226
|
+
click.echo(
|
|
227
|
+
"Unable to list existing tokens. Creating a new token instead...",
|
|
228
|
+
err=use_stderr,
|
|
229
|
+
)
|
|
230
|
+
return _create(ctx, opts, save_config=save_config, force=force, json=json)
|
|
231
|
+
|
|
232
|
+
if not api_tokens:
|
|
233
|
+
click.echo(
|
|
234
|
+
"No existing tokens found. Creating a new token instead...",
|
|
235
|
+
err=use_stderr,
|
|
236
|
+
)
|
|
237
|
+
return _create(ctx, opts, save_config=save_config, force=force, json=json)
|
|
238
|
+
|
|
239
|
+
if not json:
|
|
240
|
+
click.echo("Current tokens:")
|
|
241
|
+
print_tokens(api_tokens)
|
|
242
|
+
|
|
243
|
+
if not force:
|
|
244
|
+
token_slug = click.prompt(
|
|
245
|
+
"Please enter the slug_perm of the token you would like to refresh",
|
|
246
|
+
err=use_stderr,
|
|
247
|
+
)
|
|
248
|
+
else:
|
|
249
|
+
# Use the first available slug_perm when force is enabled
|
|
250
|
+
token_slug = api_tokens[0].slug_perm
|
|
251
|
+
if not json:
|
|
252
|
+
click.echo(f"Using token {token_slug} (first available)")
|
|
253
|
+
|
|
254
|
+
if not json:
|
|
255
|
+
click.echo(f"Refreshing token {token_slug}... ", nl=False)
|
|
256
|
+
else:
|
|
257
|
+
# In JSON mode, print info to stderr
|
|
258
|
+
click.echo(f"Refreshing token {token_slug}... ", nl=False, err=json)
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
262
|
+
with utils.maybe_spinner(opts):
|
|
263
|
+
new_token = api.refresh_user_token(token_slug)
|
|
264
|
+
|
|
265
|
+
if save_config:
|
|
266
|
+
create, has_errors = create_config_files(
|
|
267
|
+
ctx, opts, api_key=new_token.key, force=force
|
|
268
|
+
)
|
|
269
|
+
new_config_messaging(has_errors, opts, create, api_key=new_token.key)
|
|
270
|
+
|
|
271
|
+
if utils.maybe_print_as_json(opts, new_token):
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
if not json:
|
|
275
|
+
click.secho("OK", fg="green")
|
|
276
|
+
click.echo(f"New token value: {click.style(new_token.key, fg='magenta')}")
|
|
277
|
+
|
|
278
|
+
return new_token
|
|
279
|
+
except exceptions.ApiException as exc:
|
|
280
|
+
# If refresh fails due to API error, offer to create a new token instead
|
|
281
|
+
if opts.debug:
|
|
282
|
+
click.echo(f"\nDebug: Refresh failed with error: {exc}", err=True)
|
|
283
|
+
click.echo("\nRefresh failed. Creating a new token instead...", err=use_stderr)
|
|
284
|
+
return _create(ctx, opts, save_config=save_config, force=force, json=json)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _create(ctx, opts, save_config=False, force=False, json=False):
|
|
288
|
+
"""Create a new API token."""
|
|
289
|
+
json = json or utils.should_use_stderr(opts)
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
new_token = api.create_user_token_saml()
|
|
293
|
+
|
|
294
|
+
if new_token:
|
|
295
|
+
# NOTE: This mutation is necessary during the deprecation period of the --json flag.
|
|
296
|
+
if json and opts.output not in ("json", "pretty_json"):
|
|
297
|
+
opts.output = "json"
|
|
298
|
+
|
|
299
|
+
if utils.maybe_print_as_json(opts, new_token):
|
|
300
|
+
return
|
|
301
|
+
|
|
302
|
+
click.echo(f"New token value: {click.style(new_token.key, fg='magenta')}")
|
|
303
|
+
return new_token
|
|
304
|
+
|
|
305
|
+
return new_token
|
|
306
|
+
|
|
307
|
+
except exceptions.ApiException as exc:
|
|
308
|
+
if exc.status == 401:
|
|
309
|
+
click.echo(f"{exc.detail}", err=True)
|
|
310
|
+
return
|
|
311
|
+
# NOTE: This mutation is necessary during the deprecation period of the --json flag.
|
|
312
|
+
if json and opts.output not in ("json", "pretty_json"):
|
|
313
|
+
opts.output = "json"
|
|
314
|
+
if exc.status == 400:
|
|
315
|
+
new_token = handle_duplicate_token_error(
|
|
316
|
+
exc, ctx, opts, save_config=save_config, force=force, json=json
|
|
317
|
+
)
|
|
318
|
+
return new_token
|