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 @@
|
|
|
1
|
+
"""CLI-specific functionality."""
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""CLI - Group/Command classes."""
|
|
2
|
+
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
|
|
5
|
+
import click.exceptions
|
|
6
|
+
from click_didyoumean import DYMGroup
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _is_json_output_requested(exception):
|
|
10
|
+
"""Determine if JSON output was requested, checking context and argv."""
|
|
11
|
+
# Check context if available
|
|
12
|
+
ctx = getattr(exception, "ctx", None)
|
|
13
|
+
if ctx and ctx.params:
|
|
14
|
+
fmt = ctx.params.get("output")
|
|
15
|
+
if fmt in ("json", "pretty_json"):
|
|
16
|
+
return True
|
|
17
|
+
|
|
18
|
+
# Fallback: check sys.argv for output format flags
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
argv = sys.argv
|
|
22
|
+
|
|
23
|
+
if "--output-format=json" in argv or "--output-format=pretty_json" in argv:
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
for idx, arg in enumerate(argv):
|
|
27
|
+
if arg in ("-F", "--output-format") and idx + 1 < len(argv):
|
|
28
|
+
if argv[idx + 1] in ("json", "pretty_json"):
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _format_click_exception_as_json(exception):
|
|
35
|
+
"""Format a ClickException as a JSON error dict."""
|
|
36
|
+
return {
|
|
37
|
+
"detail": exception.format_message(),
|
|
38
|
+
"meta": {
|
|
39
|
+
"code": exception.exit_code,
|
|
40
|
+
"description": "Usage Error",
|
|
41
|
+
},
|
|
42
|
+
"help": {
|
|
43
|
+
"context": "Invalid usage",
|
|
44
|
+
"hint": "Check your command arguments/flags.",
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AliasGroup(DYMGroup):
|
|
50
|
+
"""A command group with DYM and alias support."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, *args, **kwargs):
|
|
53
|
+
super().__init__(*args, **kwargs)
|
|
54
|
+
self.aliases = OrderedDict()
|
|
55
|
+
self.inverse = {}
|
|
56
|
+
|
|
57
|
+
def resolve_command(self, ctx, args):
|
|
58
|
+
try:
|
|
59
|
+
return super().resolve_command(ctx, args)
|
|
60
|
+
except click.exceptions.UsageError:
|
|
61
|
+
# Before DYM kicks in, check to see if the command prefix matches
|
|
62
|
+
# exactly one command, then use that instead.
|
|
63
|
+
if args:
|
|
64
|
+
cmd_name = args[0]
|
|
65
|
+
cmds = self.list_commands(ctx)
|
|
66
|
+
matched = [cmd for cmd in cmds if cmd.startswith(cmd_name)]
|
|
67
|
+
if len(matched) == 1 and len(cmd_name) > 1:
|
|
68
|
+
args[0] = matched[0]
|
|
69
|
+
return super().resolve_command(ctx, args)
|
|
70
|
+
|
|
71
|
+
raise
|
|
72
|
+
|
|
73
|
+
def list_commands(self, ctx):
|
|
74
|
+
commands = super().list_commands(ctx)
|
|
75
|
+
|
|
76
|
+
if getattr(ctx, "showing_help", False):
|
|
77
|
+
for k, v in enumerate(commands):
|
|
78
|
+
try:
|
|
79
|
+
commands[k] = f"{v}|{'|'.join(self.aliases[v])}"
|
|
80
|
+
except KeyError:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
return commands
|
|
84
|
+
|
|
85
|
+
for k in self.inverse:
|
|
86
|
+
commands.append(k)
|
|
87
|
+
|
|
88
|
+
return commands
|
|
89
|
+
|
|
90
|
+
def get_command(self, ctx, cmd_name):
|
|
91
|
+
if getattr(ctx, "showing_help", False):
|
|
92
|
+
if "|" in cmd_name:
|
|
93
|
+
cmd_name = cmd_name.split("|")[0]
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
cmd_name = self.inverse[cmd_name]
|
|
97
|
+
except KeyError:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
return super().get_command(ctx, cmd_name)
|
|
101
|
+
|
|
102
|
+
def command(self, *args, **kwargs):
|
|
103
|
+
def decorator(f):
|
|
104
|
+
# pylint: disable=missing-docstring
|
|
105
|
+
aliases = kwargs.pop("aliases", [])
|
|
106
|
+
cmd = super(AliasGroup, self).command(*args, **kwargs)(f)
|
|
107
|
+
|
|
108
|
+
if aliases:
|
|
109
|
+
self.aliases[cmd.name] = aliases
|
|
110
|
+
for alias in aliases:
|
|
111
|
+
self.inverse[alias] = cmd.name
|
|
112
|
+
|
|
113
|
+
return cmd
|
|
114
|
+
|
|
115
|
+
return decorator
|
|
116
|
+
|
|
117
|
+
def group(self, *args, **kwargs):
|
|
118
|
+
def decorator(f):
|
|
119
|
+
# pylint: disable=missing-docstring
|
|
120
|
+
aliases = kwargs.pop("aliases", [])
|
|
121
|
+
cmd = super(AliasGroup, self).group(*args, **kwargs)(f)
|
|
122
|
+
|
|
123
|
+
if aliases:
|
|
124
|
+
self.aliases[cmd.name] = aliases
|
|
125
|
+
for alias in aliases:
|
|
126
|
+
self.inverse[alias] = cmd.name
|
|
127
|
+
|
|
128
|
+
return cmd
|
|
129
|
+
|
|
130
|
+
return decorator
|
|
131
|
+
|
|
132
|
+
def format_commands(self, ctx, formatter):
|
|
133
|
+
ctx.showing_help = True
|
|
134
|
+
return super().format_commands(ctx, formatter)
|
|
135
|
+
|
|
136
|
+
def main(self, *args, **kwargs):
|
|
137
|
+
"""Override main to intercept exceptions and format as JSON if requested."""
|
|
138
|
+
import sys
|
|
139
|
+
|
|
140
|
+
original_standalone_mode = kwargs.get("standalone_mode", True)
|
|
141
|
+
kwargs["standalone_mode"] = False
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
return super().main(*args, **kwargs)
|
|
145
|
+
except click.exceptions.Abort:
|
|
146
|
+
if not original_standalone_mode:
|
|
147
|
+
raise
|
|
148
|
+
click.echo("Aborted!", err=True)
|
|
149
|
+
sys.exit(1)
|
|
150
|
+
except click.exceptions.ClickException as e:
|
|
151
|
+
if _is_json_output_requested(e):
|
|
152
|
+
import json
|
|
153
|
+
|
|
154
|
+
click.echo(json.dumps(_format_click_exception_as_json(e), indent=4))
|
|
155
|
+
sys.exit(e.exit_code)
|
|
156
|
+
|
|
157
|
+
if not original_standalone_mode:
|
|
158
|
+
raise
|
|
159
|
+
e.show()
|
|
160
|
+
sys.exit(e.exit_code)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""CLI/Commands - Import all commands."""
|
|
2
|
+
|
|
3
|
+
from . import (
|
|
4
|
+
auth,
|
|
5
|
+
check,
|
|
6
|
+
copy,
|
|
7
|
+
credential_helper,
|
|
8
|
+
delete,
|
|
9
|
+
dependencies,
|
|
10
|
+
docs,
|
|
11
|
+
download,
|
|
12
|
+
entitlements,
|
|
13
|
+
help_,
|
|
14
|
+
list_,
|
|
15
|
+
login,
|
|
16
|
+
logout,
|
|
17
|
+
mcp,
|
|
18
|
+
metadata,
|
|
19
|
+
metrics,
|
|
20
|
+
move,
|
|
21
|
+
policy,
|
|
22
|
+
push,
|
|
23
|
+
quarantine,
|
|
24
|
+
quota,
|
|
25
|
+
repos,
|
|
26
|
+
resync,
|
|
27
|
+
status,
|
|
28
|
+
tags,
|
|
29
|
+
tokens,
|
|
30
|
+
upstream,
|
|
31
|
+
vulnerabilities,
|
|
32
|
+
whoami,
|
|
33
|
+
)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""CLI/Commands - Authenticate the user."""
|
|
2
|
+
|
|
3
|
+
import webbrowser
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from .. import decorators, utils, validators
|
|
8
|
+
from ..exceptions import handle_api_exceptions
|
|
9
|
+
from ..saml import create_configured_session, get_idp_url
|
|
10
|
+
from ..webserver import AuthenticationWebRequestHandler, AuthenticationWebServer
|
|
11
|
+
from .main import main
|
|
12
|
+
from .tokens import create, request_api_key
|
|
13
|
+
|
|
14
|
+
# Authentication server configuration
|
|
15
|
+
AUTH_SERVER_HOST = "127.0.0.1"
|
|
16
|
+
AUTH_SERVER_PORT = 12400
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _perform_saml_authentication(
|
|
20
|
+
opts, owner, enable_token_creation=False, use_stderr=False
|
|
21
|
+
):
|
|
22
|
+
"""Perform SAML authentication via web browser and local web server."""
|
|
23
|
+
session = create_configured_session(opts)
|
|
24
|
+
api_host = opts.api_config.host
|
|
25
|
+
|
|
26
|
+
idp_url = get_idp_url(api_host, owner, session=session)
|
|
27
|
+
|
|
28
|
+
click.echo(
|
|
29
|
+
f"Opening your organization's SAML IDP URL in your browser: {click.style(idp_url, bold=True)}",
|
|
30
|
+
err=use_stderr,
|
|
31
|
+
)
|
|
32
|
+
click.echo(err=use_stderr)
|
|
33
|
+
webbrowser.open(idp_url)
|
|
34
|
+
|
|
35
|
+
click.echo("Starting webserver to begin authentication ... ", err=use_stderr)
|
|
36
|
+
|
|
37
|
+
auth_server = AuthenticationWebServer(
|
|
38
|
+
(AUTH_SERVER_HOST, AUTH_SERVER_PORT),
|
|
39
|
+
AuthenticationWebRequestHandler,
|
|
40
|
+
owner=owner,
|
|
41
|
+
session=session,
|
|
42
|
+
debug=opts.debug,
|
|
43
|
+
refresh_api_on_success=enable_token_creation,
|
|
44
|
+
api_opts=opts.api_config,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
auth_server.handle_request()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@main.command(aliases=["auth"])
|
|
51
|
+
@click.option(
|
|
52
|
+
"-o",
|
|
53
|
+
"--owner",
|
|
54
|
+
metavar="OWNER",
|
|
55
|
+
required=True,
|
|
56
|
+
callback=validators.validate_owner,
|
|
57
|
+
prompt=True,
|
|
58
|
+
help="The name of the Cloudsmith organization to authenticate with.",
|
|
59
|
+
)
|
|
60
|
+
@click.option(
|
|
61
|
+
"-t",
|
|
62
|
+
"--token",
|
|
63
|
+
default=False,
|
|
64
|
+
is_flag=True,
|
|
65
|
+
help="[DEPRECATED: Use --request-api-key] Retrieve a user API token after successful authentication.",
|
|
66
|
+
)
|
|
67
|
+
@click.option(
|
|
68
|
+
"-f",
|
|
69
|
+
"--force",
|
|
70
|
+
default=False,
|
|
71
|
+
is_flag=True,
|
|
72
|
+
help="[DEPRECATED: Use --request-api-key] Force refresh of user API token without prompts.",
|
|
73
|
+
)
|
|
74
|
+
@click.option(
|
|
75
|
+
"--save-config",
|
|
76
|
+
default=False,
|
|
77
|
+
is_flag=True,
|
|
78
|
+
help="Save the new API key to your configuration files.",
|
|
79
|
+
)
|
|
80
|
+
@click.option(
|
|
81
|
+
"--json",
|
|
82
|
+
default=False,
|
|
83
|
+
is_flag=True,
|
|
84
|
+
help="[DEPRECATED: Use --output-format json] Output token details in JSON format.",
|
|
85
|
+
)
|
|
86
|
+
@click.option(
|
|
87
|
+
"--request-api-key",
|
|
88
|
+
"request_api_key_flag",
|
|
89
|
+
default=False,
|
|
90
|
+
is_flag=True,
|
|
91
|
+
help="Retrieve API token (auto-creates or auto-rotates, no prompts). "
|
|
92
|
+
"Warning: If token exists, this will rotate it and invalidate the old key.",
|
|
93
|
+
)
|
|
94
|
+
@decorators.common_cli_config_options
|
|
95
|
+
@decorators.common_cli_output_options
|
|
96
|
+
@decorators.initialise_api
|
|
97
|
+
@click.pass_context
|
|
98
|
+
def authenticate(
|
|
99
|
+
ctx, opts, owner, token, force, save_config, json, request_api_key_flag
|
|
100
|
+
):
|
|
101
|
+
"""Authenticate to Cloudsmith using the org's SAML setup."""
|
|
102
|
+
# Validate mutual exclusivity
|
|
103
|
+
if request_api_key_flag and (token or force):
|
|
104
|
+
raise click.UsageError(
|
|
105
|
+
"--request-api-key cannot be used with --token or --force. "
|
|
106
|
+
"Use --request-api-key alone for fully automated token retrieval."
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Determine if we should redirect info messages to stderr
|
|
110
|
+
use_stderr = request_api_key_flag or json or utils.should_use_stderr(opts)
|
|
111
|
+
|
|
112
|
+
if token:
|
|
113
|
+
click.secho(
|
|
114
|
+
"DEPRECATION WARNING: The `--token` flag is deprecated and will be removed in a future release. "
|
|
115
|
+
"Please use `--request-api-key` instead.",
|
|
116
|
+
fg="yellow",
|
|
117
|
+
err=True,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if force:
|
|
121
|
+
click.secho(
|
|
122
|
+
"DEPRECATION WARNING: The `--force` flag is deprecated and will be removed in a future release. "
|
|
123
|
+
"Please use `--request-api-key` instead (force is implied).",
|
|
124
|
+
fg="yellow",
|
|
125
|
+
err=True,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if json and not utils.should_use_stderr(opts):
|
|
129
|
+
click.secho(
|
|
130
|
+
"DEPRECATION WARNING: The `--json` flag is deprecated and will be removed in a future release. "
|
|
131
|
+
"Please use `--output-format json` instead.",
|
|
132
|
+
fg="yellow",
|
|
133
|
+
err=True,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
owner = owner[0].strip("'[]'")
|
|
137
|
+
|
|
138
|
+
click.echo(
|
|
139
|
+
f"Beginning authentication for the {click.style(owner, bold=True)} org ... ",
|
|
140
|
+
err=use_stderr,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Determine if we need to refresh API after SSO (required for token operations)
|
|
144
|
+
enable_token_creation = token or request_api_key_flag
|
|
145
|
+
|
|
146
|
+
context_message = "Failed to authenticate via SSO!"
|
|
147
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_message):
|
|
148
|
+
_perform_saml_authentication(
|
|
149
|
+
opts,
|
|
150
|
+
owner,
|
|
151
|
+
enable_token_creation=enable_token_creation,
|
|
152
|
+
use_stderr=use_stderr,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if request_api_key_flag:
|
|
156
|
+
# Non-interactive token retrieval
|
|
157
|
+
new_token = request_api_key(ctx, opts, save_config=save_config)
|
|
158
|
+
|
|
159
|
+
if not new_token:
|
|
160
|
+
raise click.ClickException(
|
|
161
|
+
"Failed to retrieve API token. No token was returned."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Check if JSON output is requested
|
|
165
|
+
if utils.maybe_print_as_json(opts, new_token):
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
# Default: output only the raw token value to stdout
|
|
169
|
+
click.echo(new_token.key)
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
if token:
|
|
173
|
+
ctx.invoke(create, opts=opts, save_config=save_config, force=force, json=json)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""CLI/Commands - Get an API token."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import cloudsmith_api
|
|
5
|
+
import semver
|
|
6
|
+
|
|
7
|
+
from ...core.api.rates import get_rate_limits
|
|
8
|
+
from ...core.api.status import get_status
|
|
9
|
+
from ...core.api.version import get_version as get_api_version_info
|
|
10
|
+
from .. import command, decorators, utils
|
|
11
|
+
from ..exceptions import handle_api_exceptions
|
|
12
|
+
from ..utils import maybe_spinner
|
|
13
|
+
from .main import main
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@main.group(cls=command.AliasGroup)
|
|
17
|
+
@decorators.common_cli_config_options
|
|
18
|
+
@decorators.common_cli_output_options
|
|
19
|
+
@decorators.common_api_auth_options
|
|
20
|
+
@click.pass_context
|
|
21
|
+
def check(ctx, opts): # pylint: disable=unused-argument
|
|
22
|
+
"""Check rate limits and service status."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@check.command(aliases=["limits"])
|
|
26
|
+
@decorators.common_cli_config_options
|
|
27
|
+
@decorators.common_cli_output_options
|
|
28
|
+
@decorators.initialise_api
|
|
29
|
+
@click.pass_context
|
|
30
|
+
def rates(ctx, opts):
|
|
31
|
+
"""Check current API rate limits."""
|
|
32
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
33
|
+
click.echo("Retrieving rate limits ... ", nl=False, err=use_stderr)
|
|
34
|
+
|
|
35
|
+
context_msg = "Failed to retrieve status!"
|
|
36
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
37
|
+
with maybe_spinner(opts):
|
|
38
|
+
resources_limits = get_rate_limits()
|
|
39
|
+
|
|
40
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
41
|
+
|
|
42
|
+
if utils.maybe_print_as_json(opts, resources_limits):
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
headers = ["Resource", "Throttled", "Remaining", "Interval (Seconds)", "Reset"]
|
|
46
|
+
|
|
47
|
+
rows = []
|
|
48
|
+
for resource, limits in resources_limits.items():
|
|
49
|
+
rows.append(
|
|
50
|
+
[
|
|
51
|
+
click.style(resource, fg="cyan"),
|
|
52
|
+
click.style(
|
|
53
|
+
"Yes" if limits.throttled else "No",
|
|
54
|
+
fg="red" if limits.throttled else "green",
|
|
55
|
+
),
|
|
56
|
+
"%(remaining)s/%(limit)s"
|
|
57
|
+
% {
|
|
58
|
+
"remaining": click.style(str(limits.remaining), fg="yellow"),
|
|
59
|
+
"limit": click.style(str(limits.limit), fg="yellow"),
|
|
60
|
+
},
|
|
61
|
+
click.style(str(limits.interval), fg="blue"),
|
|
62
|
+
click.style(str(limits.reset), fg="magenta"),
|
|
63
|
+
]
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if resources_limits:
|
|
67
|
+
click.echo()
|
|
68
|
+
utils.pretty_print_table(headers, rows)
|
|
69
|
+
|
|
70
|
+
click.echo()
|
|
71
|
+
|
|
72
|
+
num_results = len(resources_limits)
|
|
73
|
+
list_suffix = "resource%s" % ("s" if num_results != 1 else "")
|
|
74
|
+
utils.pretty_print_list_info(num_results=num_results, suffix=list_suffix)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@check.command()
|
|
78
|
+
@decorators.common_cli_config_options
|
|
79
|
+
@decorators.common_cli_output_options
|
|
80
|
+
@decorators.initialise_api
|
|
81
|
+
@click.pass_context
|
|
82
|
+
def service(ctx, opts):
|
|
83
|
+
"""Check the status of the Cloudsmith service."""
|
|
84
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
85
|
+
click.echo("Retrieving service status ... ", nl=False, err=use_stderr)
|
|
86
|
+
|
|
87
|
+
context_msg = "Failed to retrieve status!"
|
|
88
|
+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
|
|
89
|
+
with maybe_spinner(opts):
|
|
90
|
+
status, version = get_status(with_version=True)
|
|
91
|
+
|
|
92
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
93
|
+
|
|
94
|
+
config = cloudsmith_api.Configuration()
|
|
95
|
+
|
|
96
|
+
data = {
|
|
97
|
+
"endpoint": config.host,
|
|
98
|
+
"status": status,
|
|
99
|
+
"version": version,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if utils.maybe_print_as_json(opts, data):
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
click.echo()
|
|
106
|
+
click.echo(f"The service endpoint is: {click.style(config.host, bold=True)}")
|
|
107
|
+
click.echo(f"The service status is: {click.style(status, bold=True)}")
|
|
108
|
+
click.echo(
|
|
109
|
+
f"The service version is: {click.style(version, bold=True)} ",
|
|
110
|
+
nl=False,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
api_version = get_api_version_info()
|
|
114
|
+
|
|
115
|
+
if semver.Version.parse(version).compare(api_version) > 0:
|
|
116
|
+
click.secho("(maybe out-of-date)", fg="yellow")
|
|
117
|
+
|
|
118
|
+
click.echo()
|
|
119
|
+
click.secho(
|
|
120
|
+
f"The API library used by this CLI tool is built against service version: {click.style(api_version, bold=True)}",
|
|
121
|
+
fg="yellow",
|
|
122
|
+
)
|
|
123
|
+
else:
|
|
124
|
+
click.secho("(up-to-date)", fg="green")
|
|
125
|
+
|
|
126
|
+
click.echo()
|
|
127
|
+
click.secho(
|
|
128
|
+
"The API library used by this CLI tool seems to be up-to-date.", fg="green"
|
|
129
|
+
)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""CLI/Commands - List objects."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from ...core.api.packages import copy_package
|
|
6
|
+
from .. import decorators, utils, validators
|
|
7
|
+
from ..exceptions import handle_api_exceptions
|
|
8
|
+
from ..utils import maybe_spinner
|
|
9
|
+
from .main import main
|
|
10
|
+
from .push import wait_for_package_sync
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@main.command(aliases=["cp"])
|
|
14
|
+
@decorators.common_cli_config_options
|
|
15
|
+
@decorators.common_cli_output_options
|
|
16
|
+
@decorators.common_package_action_options
|
|
17
|
+
@decorators.common_api_auth_options
|
|
18
|
+
@decorators.initialise_api
|
|
19
|
+
@click.argument(
|
|
20
|
+
"owner_repo_package",
|
|
21
|
+
metavar="OWNER/REPO/PACKAGE",
|
|
22
|
+
callback=validators.validate_owner_repo_package,
|
|
23
|
+
)
|
|
24
|
+
@click.argument("destination", metavar="DEST")
|
|
25
|
+
@click.pass_context
|
|
26
|
+
def copy(
|
|
27
|
+
ctx,
|
|
28
|
+
opts,
|
|
29
|
+
owner_repo_package,
|
|
30
|
+
destination,
|
|
31
|
+
skip_errors,
|
|
32
|
+
wait_interval,
|
|
33
|
+
no_wait_for_sync,
|
|
34
|
+
sync_attempts,
|
|
35
|
+
):
|
|
36
|
+
"""
|
|
37
|
+
Copy a package to another repository.
|
|
38
|
+
|
|
39
|
+
This requires appropriate permissions for both the source
|
|
40
|
+
repository/package and the destination repository.
|
|
41
|
+
|
|
42
|
+
- OWNER/REPO/PACKAGE: Specify the OWNER namespace (i.e. user or org), the
|
|
43
|
+
REPO name where the package is stored, and the PACKAGE name (slug) of the
|
|
44
|
+
package itself. All separated by a slash.
|
|
45
|
+
|
|
46
|
+
Example: 'your-org/awesome-repo/better-pkg'.
|
|
47
|
+
|
|
48
|
+
- DEST: Specify the DEST (destination) repository to copy the package to.
|
|
49
|
+
This *must* be in the same namespace as the source repository.
|
|
50
|
+
|
|
51
|
+
Example: 'other-repo'
|
|
52
|
+
|
|
53
|
+
Full CLI example:
|
|
54
|
+
|
|
55
|
+
$ cloudsmith cp your-org/awesome-repo/better-pkg other-repo
|
|
56
|
+
"""
|
|
57
|
+
owner, source, slug = owner_repo_package
|
|
58
|
+
|
|
59
|
+
use_stderr = utils.should_use_stderr(opts)
|
|
60
|
+
|
|
61
|
+
click.echo(
|
|
62
|
+
"Copying %(slug)s package from %(source)s to %(dest)s ... "
|
|
63
|
+
% {
|
|
64
|
+
"slug": click.style(slug, bold=True),
|
|
65
|
+
"source": click.style(source, bold=True),
|
|
66
|
+
"dest": click.style(destination, bold=True),
|
|
67
|
+
},
|
|
68
|
+
nl=False,
|
|
69
|
+
err=use_stderr,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
context_msg = "Failed to copy package!"
|
|
73
|
+
with handle_api_exceptions(
|
|
74
|
+
ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors
|
|
75
|
+
):
|
|
76
|
+
with maybe_spinner(opts):
|
|
77
|
+
_, new_slug = copy_package(
|
|
78
|
+
owner=owner, repo=source, identifier=slug, destination=destination
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
click.secho("OK", fg="green", err=use_stderr)
|
|
82
|
+
|
|
83
|
+
if no_wait_for_sync:
|
|
84
|
+
utils.maybe_print_status_json(opts, {"slug": new_slug, "status": "OK"})
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
wait_for_package_sync(
|
|
88
|
+
ctx=ctx,
|
|
89
|
+
opts=opts,
|
|
90
|
+
owner=owner,
|
|
91
|
+
repo=destination,
|
|
92
|
+
slug=new_slug,
|
|
93
|
+
wait_interval=wait_interval,
|
|
94
|
+
skip_errors=skip_errors,
|
|
95
|
+
attempts=sync_attempts,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
utils.maybe_print_status_json(opts, {"slug": new_slug, "status": "OK"})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Copyright 2026 Cloudsmith Ltd
|
|
2
|
+
"""
|
|
3
|
+
Credential helper commands for Cloudsmith.
|
|
4
|
+
|
|
5
|
+
This module provides credential helper commands for package managers
|
|
6
|
+
that follow their respective credential helper protocols.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from ..main import main
|
|
12
|
+
from .docker import docker as docker_cmd
|
|
13
|
+
from .manage import install_cmd, list_cmd, uninstall_cmd
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group()
|
|
17
|
+
def credential_helper():
|
|
18
|
+
"""
|
|
19
|
+
Credential helpers for package managers.
|
|
20
|
+
|
|
21
|
+
These commands provide credentials for package managers like Docker.
|
|
22
|
+
Use ``install`` to set up the on-PATH launcher and configure the package
|
|
23
|
+
manager automatically, or run the runtime command directly for debugging.
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
# Install Docker credential helper
|
|
27
|
+
$ cloudsmith credential-helper install docker
|
|
28
|
+
|
|
29
|
+
# Test Docker credential helper directly
|
|
30
|
+
$ echo "docker.cloudsmith.io" | cloudsmith credential-helper docker
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
credential_helper.add_command(docker_cmd, name="docker")
|
|
35
|
+
credential_helper.add_command(install_cmd, name="install")
|
|
36
|
+
credential_helper.add_command(uninstall_cmd, name="uninstall")
|
|
37
|
+
credential_helper.add_command(list_cmd, name="list")
|
|
38
|
+
|
|
39
|
+
main.add_command(credential_helper, name="credential-helper")
|