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,317 @@
|
|
|
1
|
+
"""CLI/Commands - List objects."""
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
from operator import itemgetter
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from ...core.api.distros import list_distros
|
|
9
|
+
from ...core.api.packages import get_package_format_names_with_distros, list_packages
|
|
10
|
+
from ...core.pagination import paginate_results
|
|
11
|
+
from .. import command, decorators, utils, validators
|
|
12
|
+
from ..exceptions import handle_api_exceptions
|
|
13
|
+
from ..utils import maybe_spinner
|
|
14
|
+
from . import dependencies, entitlements
|
|
15
|
+
from .main import main
|
|
16
|
+
from .repos import get as get_repos
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@main.group(cls=command.AliasGroup, name="list", aliases=["ls"])
|
|
20
|
+
@decorators.common_cli_config_options
|
|
21
|
+
@decorators.common_cli_output_options
|
|
22
|
+
@decorators.common_api_auth_options
|
|
23
|
+
@decorators.initialise_api
|
|
24
|
+
@click.pass_context
|
|
25
|
+
def list_(ctx, opts): # pylint: disable=unused-argument
|
|
26
|
+
"""
|
|
27
|
+
List distros, packages, repos and entitlements.
|
|
28
|
+
|
|
29
|
+
See the help for subcommands for more information on each.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@list_.command(name="dependencies", aliases=["deps"])
|
|
34
|
+
@dependencies.list_dependencies_options
|
|
35
|
+
@functools.wraps(entitlements.list_entitlements)
|
|
36
|
+
@click.pass_context
|
|
37
|
+
def dependencies_(*args, **kwargs): # pylint: disable=missing-docstring
|
|
38
|
+
return dependencies.list_dependencies(*args, **kwargs)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@list_.command()
|
|
42
|
+
@decorators.common_cli_config_options
|
|
43
|
+
@decorators.common_cli_output_options
|
|
44
|
+
@decorators.common_api_auth_options
|
|
45
|
+
@decorators.initialise_api
|
|
46
|
+
@click.argument(
|
|
47
|
+
"package-format",
|
|
48
|
+
default=None,
|
|
49
|
+
required=False,
|
|
50
|
+
type=click.Choice(get_package_format_names_with_distros()),
|
|
51
|
+
)
|
|
52
|
+
@click.pass_context
|
|
53
|
+
def distros(ctx, opts, package_format):
|
|
54
|
+
"""List available distributions."""
|
|
55
|
+
# Use stderr for messages if the output is something else (e.g. # JSON)
|
|
56
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
57
|
+
|
|
58
|
+
if not use_stderr:
|
|
59
|
+
click.echo("Getting list of distributions ... ", nl=False, err=use_stderr)
|
|
60
|
+
|
|
61
|
+
context_msg = "Failed to get list of distributions!"
|
|
62
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
63
|
+
with maybe_spinner(opts):
|
|
64
|
+
distros_ = list_distros(package_format=package_format)
|
|
65
|
+
|
|
66
|
+
if not use_stderr:
|
|
67
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
68
|
+
|
|
69
|
+
if utils.maybe_print_as_json(opts, distros_):
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
headers = ["Distro", "Release", "Format", "Distro / Release (Identifier)"]
|
|
73
|
+
if package_format:
|
|
74
|
+
headers.remove("Format")
|
|
75
|
+
|
|
76
|
+
rows = []
|
|
77
|
+
for distro in sorted(distros_, key=itemgetter("slug")):
|
|
78
|
+
if not distro["versions"]:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
for release in sorted(distro["versions"], key=itemgetter("slug")):
|
|
82
|
+
row = [
|
|
83
|
+
click.style(distro["name"], fg="cyan"),
|
|
84
|
+
click.style(release["name"], fg="yellow"),
|
|
85
|
+
click.style(distro["format"], fg="blue"),
|
|
86
|
+
"%(distro)s/%(release)s"
|
|
87
|
+
% {
|
|
88
|
+
"distro": click.style(distro["slug"], fg="magenta"),
|
|
89
|
+
"release": click.style(release["slug"], fg="green"),
|
|
90
|
+
},
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
if package_format:
|
|
94
|
+
row.pop(2) # Remove format column
|
|
95
|
+
|
|
96
|
+
rows.append(row)
|
|
97
|
+
|
|
98
|
+
if distros_:
|
|
99
|
+
click.echo()
|
|
100
|
+
utils.pretty_print_table(headers, rows)
|
|
101
|
+
|
|
102
|
+
click.echo()
|
|
103
|
+
|
|
104
|
+
num_results = sum(
|
|
105
|
+
1 for distro in distros_ for release in distro["versions"] if release
|
|
106
|
+
)
|
|
107
|
+
list_suffix = "distribution release%s" % ("s" if num_results != 1 else "")
|
|
108
|
+
utils.pretty_print_list_info(num_results=num_results, suffix=list_suffix)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@list_.command(name="entitlements", aliases=["ents"])
|
|
112
|
+
@entitlements.list_entitlements_options
|
|
113
|
+
@functools.wraps(entitlements.list_entitlements)
|
|
114
|
+
@click.pass_context
|
|
115
|
+
def entitlements_(*args, **kwargs): # pylint: disable=missing-docstring
|
|
116
|
+
return entitlements.list_entitlements(*args, **kwargs)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@list_.command(aliases=["pkgs"])
|
|
120
|
+
@decorators.common_cli_config_options
|
|
121
|
+
@decorators.common_cli_output_options
|
|
122
|
+
@decorators.common_cli_list_options
|
|
123
|
+
@decorators.common_api_auth_options
|
|
124
|
+
@decorators.initialise_api
|
|
125
|
+
@click.argument(
|
|
126
|
+
"owner_repo", metavar="OWNER/REPO", callback=validators.validate_owner_repo
|
|
127
|
+
)
|
|
128
|
+
@click.option(
|
|
129
|
+
"-q",
|
|
130
|
+
"--query",
|
|
131
|
+
help=("A boolean-like search term for querying package attributes."),
|
|
132
|
+
)
|
|
133
|
+
@click.option(
|
|
134
|
+
"--sort",
|
|
135
|
+
type=click.Choice(
|
|
136
|
+
[
|
|
137
|
+
"date",
|
|
138
|
+
"-date",
|
|
139
|
+
"downloads",
|
|
140
|
+
"-downloads",
|
|
141
|
+
"format",
|
|
142
|
+
"-format",
|
|
143
|
+
"name",
|
|
144
|
+
"-name",
|
|
145
|
+
"scan",
|
|
146
|
+
"-scan",
|
|
147
|
+
"scan_date",
|
|
148
|
+
"-scan_date",
|
|
149
|
+
"size",
|
|
150
|
+
"-size",
|
|
151
|
+
"status",
|
|
152
|
+
"-status",
|
|
153
|
+
"version",
|
|
154
|
+
"-version",
|
|
155
|
+
]
|
|
156
|
+
),
|
|
157
|
+
help=("Sort packages by field. Prefix with '-' for descending order."),
|
|
158
|
+
)
|
|
159
|
+
@click.pass_context
|
|
160
|
+
def packages(ctx, opts, owner_repo, page, page_size, query, sort, page_all):
|
|
161
|
+
"""
|
|
162
|
+
List packages for a repository.
|
|
163
|
+
|
|
164
|
+
OWNER/REPO: Specify the OWNER namespace (i.e. user or org), and the
|
|
165
|
+
REPO name to list packages for that namespace and repository. All separated
|
|
166
|
+
by a slash.
|
|
167
|
+
|
|
168
|
+
You can use the search query (-q|--query) to filter packages:
|
|
169
|
+
|
|
170
|
+
- By name: 'my-package' (implicit) or 'name:my-package'
|
|
171
|
+
|
|
172
|
+
- By filename: 'pkg.ext' (implicit) or 'filename:pkg.ext' (explicit)
|
|
173
|
+
|
|
174
|
+
- By version: '1.0.0' (implicit) or 'version:1.0.0' (explicit)
|
|
175
|
+
|
|
176
|
+
- By arch: 'x86_64' (implicit) or 'architecture:x86_64' (explicit)
|
|
177
|
+
|
|
178
|
+
- By distro: 'el' (implicit) or 'distribution:el' (explicit)
|
|
179
|
+
|
|
180
|
+
You can also modify the search terms:
|
|
181
|
+
|
|
182
|
+
- '^foo' to anchor to start of term
|
|
183
|
+
|
|
184
|
+
- 'foo$' to anchor to end of term
|
|
185
|
+
|
|
186
|
+
- 'foo*bar' for fuzzy matching
|
|
187
|
+
|
|
188
|
+
- '~foo' for negation of the term (explicit only, e.g. name:~foo)
|
|
189
|
+
|
|
190
|
+
Multiple search terms are conjunctive (AND).
|
|
191
|
+
|
|
192
|
+
Examples, to find packages named exactly foo, with a zip filename, that are
|
|
193
|
+
NOT the x86 architecture, use something like this:
|
|
194
|
+
|
|
195
|
+
--query 'name:^foo$ filename:.zip$ architecture:~x86'
|
|
196
|
+
|
|
197
|
+
You can sort the results using --sort with these fields:
|
|
198
|
+
- date/-date: Sort by creation date
|
|
199
|
+
- downloads/-downloads: Sort by download count
|
|
200
|
+
- format/-format: Sort by package format
|
|
201
|
+
- name/-name: Sort by package name
|
|
202
|
+
- scan/-scan: Sort by scan status
|
|
203
|
+
- scan_date/-scan_date: Sort by last scan date
|
|
204
|
+
- size/-size: Sort by package size
|
|
205
|
+
- status/-status: Sort by package status
|
|
206
|
+
- version/-version: Sort by version
|
|
207
|
+
|
|
208
|
+
Prefix any field with '-' for descending order (e.g. -date for newest first).
|
|
209
|
+
"""
|
|
210
|
+
owner, repo = owner_repo
|
|
211
|
+
|
|
212
|
+
# Use stderr for messages if the output is something else (e.g. # JSON)
|
|
213
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
214
|
+
|
|
215
|
+
if not use_stderr:
|
|
216
|
+
click.echo("Getting list of packages ... ", nl=False, err=use_stderr)
|
|
217
|
+
|
|
218
|
+
context_msg = "Failed to get list of packages!"
|
|
219
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
220
|
+
with maybe_spinner(opts):
|
|
221
|
+
packages_, page_info = paginate_results(
|
|
222
|
+
list_packages,
|
|
223
|
+
page_all=page_all,
|
|
224
|
+
page=page,
|
|
225
|
+
page_size=page_size,
|
|
226
|
+
owner=owner,
|
|
227
|
+
repo=repo,
|
|
228
|
+
query=query,
|
|
229
|
+
sort=sort,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if not use_stderr:
|
|
233
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
234
|
+
|
|
235
|
+
if utils.maybe_print_as_json(opts, packages_, page_info):
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
headers = ["Name", "Version", "Status", "Owner / Repository (Identifier)"]
|
|
239
|
+
rows = []
|
|
240
|
+
for package in sorted(packages_, key=itemgetter("namespace", "slug")):
|
|
241
|
+
rows.append(
|
|
242
|
+
[
|
|
243
|
+
click.style(_get_package_name(package), fg="cyan"),
|
|
244
|
+
click.style(_get_package_version(package), fg="yellow"),
|
|
245
|
+
click.style(_get_package_status(package), fg="blue"),
|
|
246
|
+
"%(owner_slug)s/%(repo_slug)s/%(slug)s"
|
|
247
|
+
% {
|
|
248
|
+
"owner_slug": click.style(package["namespace"], fg="magenta"),
|
|
249
|
+
"repo_slug": click.style(package["repository"], fg="magenta"),
|
|
250
|
+
"slug": click.style(package["slug"], fg="green"),
|
|
251
|
+
},
|
|
252
|
+
]
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
if packages_:
|
|
256
|
+
click.echo()
|
|
257
|
+
utils.pretty_print_table(headers, rows)
|
|
258
|
+
|
|
259
|
+
click.echo()
|
|
260
|
+
|
|
261
|
+
num_results = len(packages_)
|
|
262
|
+
list_suffix = "package%s" % ("s" if num_results != 1 else "")
|
|
263
|
+
utils.pretty_print_list_info(
|
|
264
|
+
num_results=num_results,
|
|
265
|
+
page_info=None if page_all else page_info,
|
|
266
|
+
suffix=f"{list_suffix} retrieved" if page_all else f"{list_suffix} visible",
|
|
267
|
+
page_all=page_all,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@list_.command()
|
|
272
|
+
@decorators.common_cli_config_options
|
|
273
|
+
@decorators.common_cli_output_options
|
|
274
|
+
@decorators.common_cli_list_options
|
|
275
|
+
@decorators.common_api_auth_options
|
|
276
|
+
@decorators.initialise_api
|
|
277
|
+
@click.argument(
|
|
278
|
+
"owner_repo",
|
|
279
|
+
metavar="OWNER/REPO",
|
|
280
|
+
callback=validators.validate_optional_owner_repo,
|
|
281
|
+
default="",
|
|
282
|
+
required=False,
|
|
283
|
+
)
|
|
284
|
+
@click.pass_context
|
|
285
|
+
def repos(ctx, opts, owner_repo, page, page_size, page_all):
|
|
286
|
+
"""
|
|
287
|
+
List repositories for a namespace (owner).
|
|
288
|
+
|
|
289
|
+
OWNER/REPO: Specify the OWNER namespace (i.e user or org) to list the
|
|
290
|
+
repositories for that namespace.
|
|
291
|
+
|
|
292
|
+
If REPO isn't specified, all repositories will be retrieved from the
|
|
293
|
+
OWNER namespace.
|
|
294
|
+
|
|
295
|
+
If OWNER isn't specified it'll default to the currently authenticated user
|
|
296
|
+
(if any). If you're unauthenticated, no results will be returned.
|
|
297
|
+
"""
|
|
298
|
+
ctx.forward(get_repos)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _get_package_name(package):
|
|
302
|
+
"""Get the name (or filename) for a package."""
|
|
303
|
+
return package["name"] or package["filename"]
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _get_package_status(package):
|
|
307
|
+
"""Get the status for a package."""
|
|
308
|
+
status = package["status_str"] or "Unknown"
|
|
309
|
+
stage = package["stage_str"] or "Unknown"
|
|
310
|
+
if stage == "Fully Synchronised":
|
|
311
|
+
return status
|
|
312
|
+
return f"{status} / {stage}"
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _get_package_version(package):
|
|
316
|
+
"""Get the version for a package (if any)."""
|
|
317
|
+
return package["version"] or "None"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""CLI/Commands - Get an API token."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import cloudsmith_api
|
|
5
|
+
|
|
6
|
+
from ...core.api.exceptions import TwoFactorRequiredException
|
|
7
|
+
from ...core.api.user import get_user_token
|
|
8
|
+
from ...core.config import create_config_files, new_config_messaging
|
|
9
|
+
from .. import decorators, utils
|
|
10
|
+
from ..exceptions import handle_api_exceptions
|
|
11
|
+
from ..utils import maybe_spinner
|
|
12
|
+
from .main import main
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def validate_login(ctx, param, value):
|
|
16
|
+
"""Ensure that login is not blank."""
|
|
17
|
+
# pylint: disable=unused-argument
|
|
18
|
+
value = value.strip()
|
|
19
|
+
if not value:
|
|
20
|
+
raise click.BadParameter("The value cannot be blank.", param=param)
|
|
21
|
+
return value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@main.command(aliases=["token"])
|
|
25
|
+
@click.option(
|
|
26
|
+
"-l",
|
|
27
|
+
"--login",
|
|
28
|
+
required=True,
|
|
29
|
+
callback=validate_login,
|
|
30
|
+
prompt=True,
|
|
31
|
+
help="Your Cloudsmith login account (email address).",
|
|
32
|
+
)
|
|
33
|
+
@click.password_option("-p", "--password", help="Your Cloudsmith login password.")
|
|
34
|
+
@decorators.common_cli_config_options
|
|
35
|
+
@decorators.common_cli_output_options
|
|
36
|
+
@decorators.initialise_api
|
|
37
|
+
@click.pass_context
|
|
38
|
+
def login(ctx, opts, login, password): # pylint: disable=redefined-outer-name
|
|
39
|
+
"""Retrieve your API authentication token/key via login."""
|
|
40
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
41
|
+
click.echo(
|
|
42
|
+
"Retrieving API token for %(login)s ... "
|
|
43
|
+
% {"login": click.style(login, bold=True)},
|
|
44
|
+
nl=False,
|
|
45
|
+
err=use_stderr,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
context_msg = "Failed to retrieve the API token!"
|
|
49
|
+
try:
|
|
50
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
51
|
+
with maybe_spinner(opts):
|
|
52
|
+
api_key = get_user_token(login=login, password=password)
|
|
53
|
+
except TwoFactorRequiredException as e:
|
|
54
|
+
click.echo("\r\033[K", nl=False, err=use_stderr)
|
|
55
|
+
click.echo("Two-factor authentication is required.", err=use_stderr)
|
|
56
|
+
|
|
57
|
+
totp_token = click.prompt(
|
|
58
|
+
"Enter your two-factor authentication code", type=str, err=use_stderr
|
|
59
|
+
)
|
|
60
|
+
click.echo(
|
|
61
|
+
"Verifying two-factor code for %(login)s ... "
|
|
62
|
+
% {"login": click.style(login, bold=True)},
|
|
63
|
+
nl=False,
|
|
64
|
+
err=use_stderr,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
69
|
+
with maybe_spinner(opts):
|
|
70
|
+
api_key = get_user_token(
|
|
71
|
+
login=login,
|
|
72
|
+
password=password,
|
|
73
|
+
totp_token=totp_token,
|
|
74
|
+
two_factor_token=e.two_factor_token,
|
|
75
|
+
)
|
|
76
|
+
except cloudsmith_api.rest.ApiException:
|
|
77
|
+
click.echo("\r\033[K", nl=False, err=use_stderr)
|
|
78
|
+
click.secho(
|
|
79
|
+
"Authentication failed: The entered TOTP token is not valid.",
|
|
80
|
+
fg="red",
|
|
81
|
+
err=use_stderr,
|
|
82
|
+
)
|
|
83
|
+
ctx.exit(1)
|
|
84
|
+
|
|
85
|
+
except cloudsmith_api.rest.ApiException as e:
|
|
86
|
+
click.echo("\r\033[K", nl=False, err=use_stderr)
|
|
87
|
+
click.secho(f"Authentication failed: {str(e)}", fg="red", err=use_stderr)
|
|
88
|
+
ctx.exit(1)
|
|
89
|
+
|
|
90
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
91
|
+
|
|
92
|
+
if not utils.maybe_print_as_json(opts, {"token": api_key, "login": login}):
|
|
93
|
+
click.echo(
|
|
94
|
+
"Your API key/token is: %(token)s"
|
|
95
|
+
% {"token": click.style(api_key, fg="magenta")}
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
create, has_errors = create_config_files(ctx, opts, api_key=api_key)
|
|
99
|
+
new_config_messaging(has_errors, opts, create, api_key=api_key)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Copyright 2026 Cloudsmith Ltd
|
|
2
|
+
"""CLI/Commands - Log out and clear authentication state."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
import cloudsmith_api
|
|
8
|
+
|
|
9
|
+
from ...core import keyring
|
|
10
|
+
from .. import decorators, utils
|
|
11
|
+
from ..config import CredentialsReader
|
|
12
|
+
from .main import main
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _clear_credentials(dry_run, use_stderr):
|
|
16
|
+
"""Clear credential files. Returns result dict."""
|
|
17
|
+
creds_files = CredentialsReader.find_existing_files()
|
|
18
|
+
if not creds_files:
|
|
19
|
+
click.echo("No credentials file found.", err=use_stderr)
|
|
20
|
+
return {"action": "not_found", "files": []}
|
|
21
|
+
|
|
22
|
+
if not dry_run:
|
|
23
|
+
for path in creds_files:
|
|
24
|
+
CredentialsReader.clear_api_key(path)
|
|
25
|
+
|
|
26
|
+
verb = "Would remove" if dry_run else "Removed"
|
|
27
|
+
for path in creds_files:
|
|
28
|
+
click.echo(
|
|
29
|
+
f"{verb} credentials from: " + click.style(path, bold=True),
|
|
30
|
+
err=use_stderr,
|
|
31
|
+
)
|
|
32
|
+
action = "would_remove" if dry_run else "removed"
|
|
33
|
+
return {"action": action, "files": list(creds_files)}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _clear_keyring(api_host, dry_run, use_stderr):
|
|
37
|
+
"""Clear SSO tokens from keyring. Returns result dict."""
|
|
38
|
+
if not keyring.should_use_keyring():
|
|
39
|
+
click.secho(
|
|
40
|
+
"Keyring is disabled (CLOUDSMITH_NO_KEYRING is set).",
|
|
41
|
+
fg="yellow",
|
|
42
|
+
err=use_stderr,
|
|
43
|
+
)
|
|
44
|
+
return {"action": "disabled"}
|
|
45
|
+
|
|
46
|
+
if not keyring.has_sso_tokens(api_host):
|
|
47
|
+
click.echo("No SSO tokens found in system keyring.", err=use_stderr)
|
|
48
|
+
return {"action": "not_found"}
|
|
49
|
+
|
|
50
|
+
if dry_run:
|
|
51
|
+
click.echo("Would remove SSO tokens from system keyring.", err=use_stderr)
|
|
52
|
+
return {"action": "would_remove"}
|
|
53
|
+
|
|
54
|
+
deleted = keyring.delete_sso_tokens(api_host)
|
|
55
|
+
action = "removed" if deleted else "failed"
|
|
56
|
+
msg = f"{'Removed' if deleted else 'Failed to remove'} SSO tokens from system keyring."
|
|
57
|
+
click.secho(msg, fg=None if deleted else "red", err=use_stderr)
|
|
58
|
+
return {"action": action} if deleted else {"action": action, "message": msg}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _env_api_key_status():
|
|
62
|
+
"""Return structured status for the CLOUDSMITH_API_KEY env var."""
|
|
63
|
+
is_set = bool(os.environ.get("CLOUDSMITH_API_KEY"))
|
|
64
|
+
return {
|
|
65
|
+
"is_set": is_set,
|
|
66
|
+
"action": "unset CLOUDSMITH_API_KEY" if is_set else "none",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _collect_warnings(keyring_only, config_only):
|
|
71
|
+
"""Collect advisory warnings based on flags and environment."""
|
|
72
|
+
warnings = []
|
|
73
|
+
if config_only:
|
|
74
|
+
warnings.append("SSO tokens were not modified (--config-only).")
|
|
75
|
+
if keyring_only:
|
|
76
|
+
warnings.append("credentials.ini was not modified (--keyring-only).")
|
|
77
|
+
if os.environ.get("CLOUDSMITH_API_KEY"):
|
|
78
|
+
warnings.append(
|
|
79
|
+
"CLOUDSMITH_API_KEY is set in your environment. "
|
|
80
|
+
"Run: unset CLOUDSMITH_API_KEY"
|
|
81
|
+
)
|
|
82
|
+
return warnings
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@main.command()
|
|
86
|
+
@click.option(
|
|
87
|
+
"--api-host",
|
|
88
|
+
envvar="CLOUDSMITH_API_HOST",
|
|
89
|
+
default=None,
|
|
90
|
+
help="The API host to clear keyring tokens for.",
|
|
91
|
+
)
|
|
92
|
+
@click.option(
|
|
93
|
+
"--keyring-only",
|
|
94
|
+
is_flag=True,
|
|
95
|
+
default=False,
|
|
96
|
+
help="Only clear SSO tokens from the system keyring.",
|
|
97
|
+
)
|
|
98
|
+
@click.option(
|
|
99
|
+
"--config-only",
|
|
100
|
+
is_flag=True,
|
|
101
|
+
default=False,
|
|
102
|
+
help="Only clear credentials from credentials.ini.",
|
|
103
|
+
)
|
|
104
|
+
@click.option(
|
|
105
|
+
"--dry-run",
|
|
106
|
+
is_flag=True,
|
|
107
|
+
default=False,
|
|
108
|
+
help="Show what would be removed without removing anything.",
|
|
109
|
+
)
|
|
110
|
+
@decorators.common_cli_config_options
|
|
111
|
+
@decorators.common_cli_output_options
|
|
112
|
+
@click.pass_context
|
|
113
|
+
def logout(ctx, opts, api_host, keyring_only, config_only, dry_run):
|
|
114
|
+
"""Clear stored authentication credentials and SSO tokens."""
|
|
115
|
+
if keyring_only and config_only:
|
|
116
|
+
raise click.UsageError(
|
|
117
|
+
"--keyring-only and --config-only are mutually exclusive."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if api_host is None:
|
|
121
|
+
api_host = opts.api_host or cloudsmith_api.Configuration().host
|
|
122
|
+
|
|
123
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
124
|
+
|
|
125
|
+
credential_file = (
|
|
126
|
+
_clear_credentials(dry_run, use_stderr)
|
|
127
|
+
if not keyring_only
|
|
128
|
+
else {"action": "skipped", "files": []}
|
|
129
|
+
)
|
|
130
|
+
keyring_result = (
|
|
131
|
+
_clear_keyring(api_host, dry_run, use_stderr)
|
|
132
|
+
if not config_only
|
|
133
|
+
else {"action": "skipped"}
|
|
134
|
+
)
|
|
135
|
+
warnings = _collect_warnings(keyring_only, config_only)
|
|
136
|
+
|
|
137
|
+
for warning in warnings:
|
|
138
|
+
click.secho(f"Note: {warning}", fg="yellow", err=use_stderr)
|
|
139
|
+
|
|
140
|
+
utils.maybe_print_as_json(
|
|
141
|
+
opts,
|
|
142
|
+
{
|
|
143
|
+
"api_host": api_host,
|
|
144
|
+
"dry_run": dry_run,
|
|
145
|
+
"sources": {
|
|
146
|
+
"credential_file": credential_file,
|
|
147
|
+
"keyring": keyring_result,
|
|
148
|
+
"environment_api_key": _env_api_key_status(),
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Main command/entrypoint."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from ...core.api.version import get_version as get_api_version
|
|
6
|
+
from ...core.utils import get_github_website, get_help_website
|
|
7
|
+
from ...core.version import get_version as get_cli_version
|
|
8
|
+
from .. import command, decorators, utils
|
|
9
|
+
|
|
10
|
+
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def print_version(opts):
|
|
14
|
+
"""Print the environment versions."""
|
|
15
|
+
cli_version = get_cli_version()
|
|
16
|
+
api_version = get_api_version()
|
|
17
|
+
|
|
18
|
+
data = {
|
|
19
|
+
"cli_version": cli_version,
|
|
20
|
+
"api_version": api_version,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if not utils.maybe_print_as_json(opts, data):
|
|
24
|
+
click.echo("Versions:")
|
|
25
|
+
click.secho(
|
|
26
|
+
"CLI Package Version: %(version)s"
|
|
27
|
+
% {"version": click.style(cli_version, bold=True)}
|
|
28
|
+
)
|
|
29
|
+
click.secho(
|
|
30
|
+
"API Package Version: %(version)s"
|
|
31
|
+
% {"version": click.style(api_version, bold=True)}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@click.group(
|
|
36
|
+
cls=command.AliasGroup,
|
|
37
|
+
context_settings=CONTEXT_SETTINGS,
|
|
38
|
+
invoke_without_command=True,
|
|
39
|
+
help="""\b
|
|
40
|
+
________ __ _ __ __ ________ ____
|
|
41
|
+
/ ____/ /___ __ ______/ /________ ___ (_) /_/ /_ / ____/ / / _/
|
|
42
|
+
/ / / / __ \\/ / / / __ / ___/ __ `__ \\/ / __/ __ \\ / / / / / /
|
|
43
|
+
/ /___/ / /_/ / /_/ / /_/ (__ ) / / / / / / /_/ / / / / /___/ /____/ /
|
|
44
|
+
\\____/_/\\____/\\__,_/\\__,_/____/_/ /_/ /_/_/\\__/_/ /_/ \\____/_____/___/
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
The Cloudsmith Command-Line Interface - Be Awesome. Automate Everything.
|
|
48
|
+
""",
|
|
49
|
+
epilog="""
|
|
50
|
+
For more help, see the docs: %(help_website)s
|
|
51
|
+
|
|
52
|
+
For issues/contributing: %(github_website)s
|
|
53
|
+
"""
|
|
54
|
+
% {"help_website": get_help_website(), "github_website": get_github_website()},
|
|
55
|
+
)
|
|
56
|
+
@click.option(
|
|
57
|
+
"-V",
|
|
58
|
+
"--version",
|
|
59
|
+
help="Show the version numbers for the API and CLI.",
|
|
60
|
+
is_flag=True,
|
|
61
|
+
is_eager=True,
|
|
62
|
+
)
|
|
63
|
+
@decorators.common_cli_config_options
|
|
64
|
+
@decorators.common_cli_output_options
|
|
65
|
+
@click.pass_context
|
|
66
|
+
def main(ctx, opts, version):
|
|
67
|
+
"""Handle entrypoint to CLI."""
|
|
68
|
+
# pylint: disable=unused-argument
|
|
69
|
+
|
|
70
|
+
if version:
|
|
71
|
+
print_version(opts)
|
|
72
|
+
elif ctx.invoked_subcommand is None:
|
|
73
|
+
click.echo(ctx.get_help())
|