cloudsmith-cli 1.30.0__tar.gz → 1.30.1__tar.gz
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-1.30.0/cloudsmith_cli.egg-info → cloudsmith_cli-1.30.1}/PKG-INFO +2 -2
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/auth.py +22 -1
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/manage.py +2 -2
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/main.py +1 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/mcp.py +10 -7
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/update.py +2 -2
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/vulnerabilities.py +27 -8
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/config.py +68 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/decorators.py +61 -1
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/validators.py +11 -5
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/conftest.py +7 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/exceptions.py +21 -5
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/init.py +15 -4
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/user.py +3 -1
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/vulnerabilities.py +31 -17
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/cache.py +11 -9
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/download.py +3 -3
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/keyring.py +37 -13
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/pagination.py +16 -13
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/sso.py +2 -2
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/update_check.py +1 -1
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/docker/installer.py +7 -3
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/pnpm/installer.py +5 -7
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/pnpm/rc.py +7 -4
- cloudsmith_cli-1.30.1/cloudsmith_cli/data/VERSION +1 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1/cloudsmith_cli.egg-info}/PKG-INFO +2 -2
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli.egg-info/requires.txt +1 -1
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/pyproject.toml +5 -4
- cloudsmith_cli-1.30.0/cloudsmith_cli/data/VERSION +0 -1
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/LICENSE +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/MANIFEST.in +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/README.md +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/__main__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/command.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/check.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/copy.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/cargo.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/docker.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/generic.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/pnpm.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/terraform.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/delete.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/dependencies.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/docs.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/domains.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/download.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/entitlements.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/help_.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/list_.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/login.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/logout.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/metadata.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/metrics/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/metrics/command.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/metrics/entitlements.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/metrics/packages.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/move.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/policy/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/policy/command.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/policy/deny.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/policy/license.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/policy/vulnerability.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/push.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/quarantine.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/quota/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/quota/command.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/quota/history.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/quota/quota.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/registry.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/repos.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/resync.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/status.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/tags.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/tokens.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/upstream.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/whoami.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/dsc_parser.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/exceptions.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/metadata_common.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/saml.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/table.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/types.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/utils.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/webserver.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/distros.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/entitlements.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/files.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/metadata.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/metrics.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/orgs.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/packages.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/quota.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/rates.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/repos.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/status.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/upstreams.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/v2.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/version.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/workspaces.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/cache_utils.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/certificate_errors.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/config.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/chain.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/models.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/aws.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/azure_devops.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/base.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/bitbucket_pipelines.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/buildkite.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/circleci.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/gcp.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/generic.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/github_actions.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/gitlab_ci.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/exchange.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/provider.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/providers/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/providers/cli_flag.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/providers/credentials_file.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/providers/env_var.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/providers/keyring_provider.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/providers/oidc_provider.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/installation.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/macos_keychain.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/mcp/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/mcp/data.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/mcp/server.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/ratelimits.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/rest.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/self_update.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/session.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/telemetry.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/utils.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/version.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/backends.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/cargo/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/cargo/installer.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/cargo/runtime.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/common.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/docker/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/docker/runtime.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/domains.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/generic.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/launchers.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/pnpm/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/pnpm/runtime.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/terraform/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/terraform/installer.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/terraform/runtime.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/terraform/terraformrc.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/data/config.ini +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/data/credentials.ini +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/templates/__init__.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/templates/auth_error.html +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/templates/auth_success.html +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/templates/auth_two_factor.html +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/wrapper.py +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli.egg-info/SOURCES.txt +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli.egg-info/dependency_links.txt +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli.egg-info/entry_points.txt +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli.egg-info/top_level.txt +0 -0
- {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudsmith-cli
|
|
3
|
-
Version: 1.30.
|
|
3
|
+
Version: 1.30.1
|
|
4
4
|
Summary: Cloudsmith Command-Line Interface (CLI)
|
|
5
5
|
Author-email: Cloudsmith Ltd <support@cloudsmith.io>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -22,7 +22,7 @@ Classifier: Topic :: Utilities
|
|
|
22
22
|
Requires-Python: >=3.11.0
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
|
-
Requires-Dist: certifi>=
|
|
25
|
+
Requires-Dist: certifi>=2026.10.23
|
|
26
26
|
Requires-Dist: click!=8.3.0,>=8.2
|
|
27
27
|
Requires-Dist: click-configfile>=0.2.3
|
|
28
28
|
Requires-Dist: click-didyoumean>=0.0.3
|
|
@@ -158,10 +158,28 @@ def _perform_saml_authentication(
|
|
|
158
158
|
)
|
|
159
159
|
|
|
160
160
|
auth_server.handle_request()
|
|
161
|
+
return auth_server.sso_access_token is not None
|
|
161
162
|
finally:
|
|
162
163
|
auth_server.server_close()
|
|
163
164
|
|
|
164
165
|
|
|
166
|
+
def _save_workspace(ctx, opts, workspace, use_stderr):
|
|
167
|
+
"""Save the Workspace to config.ini, or warn when the CLI cannot write it."""
|
|
168
|
+
try:
|
|
169
|
+
saved_path = opts.get_config_reader().save_workspace(
|
|
170
|
+
workspace, profile=ctx.meta.get("profile"), path=ctx.meta.get("config_file")
|
|
171
|
+
)
|
|
172
|
+
except (OSError, ValueError) as exc:
|
|
173
|
+
click.secho(
|
|
174
|
+
f"Could not save the {workspace} Workspace to config.ini: {exc}",
|
|
175
|
+
fg="yellow",
|
|
176
|
+
err=True,
|
|
177
|
+
)
|
|
178
|
+
return
|
|
179
|
+
if saved_path:
|
|
180
|
+
click.echo(f"Saved the {workspace} Workspace to {saved_path}.", err=use_stderr)
|
|
181
|
+
|
|
182
|
+
|
|
165
183
|
@main.command(aliases=["auth"])
|
|
166
184
|
@click.option(
|
|
167
185
|
"-t",
|
|
@@ -276,7 +294,7 @@ def authenticate(
|
|
|
276
294
|
|
|
277
295
|
context_message = "Failed to authenticate via SAML!"
|
|
278
296
|
with handle_api_exceptions(ctx, opts=opts, context_msg=context_message):
|
|
279
|
-
_perform_saml_authentication(
|
|
297
|
+
authenticated = _perform_saml_authentication(
|
|
280
298
|
opts,
|
|
281
299
|
workspace,
|
|
282
300
|
enable_token_creation=enable_token_creation,
|
|
@@ -285,6 +303,9 @@ def authenticate(
|
|
|
285
303
|
profile=ctx.meta.get("profile"),
|
|
286
304
|
)
|
|
287
305
|
|
|
306
|
+
if authenticated:
|
|
307
|
+
_save_workspace(ctx, opts, workspace, use_stderr)
|
|
308
|
+
|
|
288
309
|
if request_api_key_flag:
|
|
289
310
|
# Non-interactive token retrieval
|
|
290
311
|
new_token = request_api_key(ctx, opts, save_config=save_config)
|
|
@@ -351,7 +351,7 @@ def install_cmd(
|
|
|
351
351
|
sys.exit(ec)
|
|
352
352
|
|
|
353
353
|
if dry_run:
|
|
354
|
-
click.echo("Dry run
|
|
354
|
+
click.echo("Dry run - no changes will be made:", err=use_stderr)
|
|
355
355
|
for action in normal:
|
|
356
356
|
click.echo(f" {action}" if dry_run else action, err=use_stderr)
|
|
357
357
|
for warning in warnings:
|
|
@@ -419,7 +419,7 @@ def uninstall_cmd(ctx, opts, helper: str, bin_dir: str | None, dry_run: bool) ->
|
|
|
419
419
|
return
|
|
420
420
|
|
|
421
421
|
if dry_run:
|
|
422
|
-
click.echo("Dry run
|
|
422
|
+
click.echo("Dry run - no changes will be made:", err=use_stderr)
|
|
423
423
|
for action in normal:
|
|
424
424
|
click.echo(f" {action}" if dry_run else action, err=use_stderr)
|
|
425
425
|
for warning in warnings:
|
|
@@ -64,6 +64,7 @@ For issues/contributing: {get_github_website()}
|
|
|
64
64
|
envvar="CLOUDSMITH_NO_UPDATE_CHECK",
|
|
65
65
|
help="Disable the once-a-day check for a newer version of the CLI.",
|
|
66
66
|
)
|
|
67
|
+
@decorators.keyring_options
|
|
67
68
|
@decorators.common_cli_config_options
|
|
68
69
|
@decorators.common_cli_output_options
|
|
69
70
|
@click.pass_context
|
|
@@ -16,6 +16,7 @@ import json5
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
17
|
from ...core.mcp import server
|
|
18
18
|
from ...core.mcp.data import OpenAPITool
|
|
19
|
+
from ..config import Options
|
|
19
20
|
|
|
20
21
|
from .. import command, decorators, utils
|
|
21
22
|
from .main import main
|
|
@@ -196,7 +197,9 @@ def print_groups(group_list: dict[str, list[str]]):
|
|
|
196
197
|
)
|
|
197
198
|
@decorators.initialise_api
|
|
198
199
|
@click.pass_context
|
|
199
|
-
def configure(
|
|
200
|
+
def configure( # pylint: disable=unused-argument
|
|
201
|
+
ctx: click.Context, opts: Options, client: str | None, is_global: bool
|
|
202
|
+
) -> None:
|
|
200
203
|
"""
|
|
201
204
|
Configure the Cloudsmith MCP server for supported clients.
|
|
202
205
|
|
|
@@ -253,7 +256,7 @@ def configure(ctx, opts, client, is_global): # pylint: disable=unused-argument
|
|
|
253
256
|
if configure_client(client_name, server_config, is_global, profile):
|
|
254
257
|
if not use_stderr:
|
|
255
258
|
click.echo(
|
|
256
|
-
click.style(f"
|
|
259
|
+
click.style(f"Configured {client_name.title()}", fg="green")
|
|
257
260
|
)
|
|
258
261
|
success_count += 1
|
|
259
262
|
results.append({"client": client_name, "success": True})
|
|
@@ -261,7 +264,7 @@ def configure(ctx, opts, client, is_global): # pylint: disable=unused-argument
|
|
|
261
264
|
if not use_stderr:
|
|
262
265
|
click.echo(
|
|
263
266
|
click.style(
|
|
264
|
-
f"
|
|
267
|
+
f"Failed to configure {client_name.title()}", fg="red"
|
|
265
268
|
)
|
|
266
269
|
)
|
|
267
270
|
results.append(
|
|
@@ -275,7 +278,7 @@ def configure(ctx, opts, client, is_global): # pylint: disable=unused-argument
|
|
|
275
278
|
if not use_stderr:
|
|
276
279
|
click.echo(
|
|
277
280
|
click.style(
|
|
278
|
-
f"
|
|
281
|
+
f"Error configuring {client_name.title()}: {e!s}", fg="red"
|
|
279
282
|
)
|
|
280
283
|
)
|
|
281
284
|
results.append({"client": client_name, "success": False, "error": str(e)})
|
|
@@ -286,14 +289,14 @@ def configure(ctx, opts, client, is_global): # pylint: disable=unused-argument
|
|
|
286
289
|
if success_count > 0:
|
|
287
290
|
click.echo(
|
|
288
291
|
click.style(
|
|
289
|
-
f"\
|
|
292
|
+
f"\nSuccessfully configured {success_count} client(s)", fg="green"
|
|
290
293
|
)
|
|
291
294
|
)
|
|
292
295
|
click.echo(
|
|
293
296
|
"\nNote: You may need to restart the client application for changes to take effect."
|
|
294
297
|
)
|
|
295
298
|
else:
|
|
296
|
-
click.echo(click.style("\
|
|
299
|
+
click.echo(click.style("\nNo clients were configured successfully", fg="red"))
|
|
297
300
|
|
|
298
301
|
|
|
299
302
|
def _get_server_config(profile=None):
|
|
@@ -502,7 +505,7 @@ def _safe_update_json(path: Path, mutate, *, max_retries: int = 3) -> None:
|
|
|
502
505
|
for _ in range(max_retries):
|
|
503
506
|
if path.exists():
|
|
504
507
|
mtime_before = path.stat().st_mtime_ns
|
|
505
|
-
with open(path) as f:
|
|
508
|
+
with open(path, encoding="utf-8") as f:
|
|
506
509
|
content = f.read()
|
|
507
510
|
try:
|
|
508
511
|
config = json5.loads(content)
|
|
@@ -85,7 +85,7 @@ def _print_manual_update(opts, current, latest, manifest, data):
|
|
|
85
85
|
if utils.maybe_print_as_json(opts, data):
|
|
86
86
|
return
|
|
87
87
|
click.echo(
|
|
88
|
-
f"A new version of the Cloudsmith CLI is available: {current}
|
|
88
|
+
f"A new version of the Cloudsmith CLI is available: {current} -> {latest}"
|
|
89
89
|
)
|
|
90
90
|
click.echo(f"See what's changed: {changelog}")
|
|
91
91
|
click.echo("Self-update is not supported for the standalone binary on Windows.")
|
|
@@ -150,7 +150,7 @@ def update(ctx, opts, assume_yes):
|
|
|
150
150
|
if utils.maybe_print_as_json(opts, data):
|
|
151
151
|
return
|
|
152
152
|
click.echo(
|
|
153
|
-
f"A new version of the Cloudsmith CLI is available: {current}
|
|
153
|
+
f"A new version of the Cloudsmith CLI is available: {current} -> {latest}"
|
|
154
154
|
)
|
|
155
155
|
click.echo(f"See what's changed: {changelog}")
|
|
156
156
|
click.echo(f"The CLI was installed via {channel}. To update, run:")
|
{cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/vulnerabilities.py
RENAMED
|
@@ -2,16 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
4
|
|
|
5
|
+
from ...core.api.exceptions import ApiException
|
|
5
6
|
from ...core.api.vulnerabilities import (
|
|
7
|
+
NO_SCAN_RESULTS_MSG,
|
|
6
8
|
_print_vulnerabilities_assessment_table,
|
|
7
9
|
_print_vulnerabilities_summary_table,
|
|
10
|
+
_severity_of,
|
|
8
11
|
get_package_scan_result,
|
|
9
12
|
)
|
|
10
13
|
from .. import decorators, utils, validators
|
|
14
|
+
from ..config import Options
|
|
11
15
|
from ..exceptions import handle_api_exceptions
|
|
12
16
|
from .main import main
|
|
13
17
|
|
|
14
18
|
|
|
19
|
+
def _summarise_no_scan_results(
|
|
20
|
+
_exc: ApiException, detail: str | None, _fields: dict
|
|
21
|
+
) -> str | None:
|
|
22
|
+
"""Show only the detail when the package has no scan results."""
|
|
23
|
+
return detail if detail == NO_SCAN_RESULTS_MSG else None
|
|
24
|
+
|
|
25
|
+
|
|
15
26
|
@main.command()
|
|
16
27
|
@decorators.common_cli_config_options
|
|
17
28
|
@decorators.common_cli_output_options
|
|
@@ -41,8 +52,13 @@ from .main import main
|
|
|
41
52
|
)
|
|
42
53
|
@click.pass_context
|
|
43
54
|
def vulnerabilities(
|
|
44
|
-
ctx
|
|
45
|
-
|
|
55
|
+
ctx: click.Context,
|
|
56
|
+
opts: Options,
|
|
57
|
+
owner_repo_package: tuple[str, str, str],
|
|
58
|
+
show_assessment: bool,
|
|
59
|
+
fixable: bool | None,
|
|
60
|
+
severity_filter: str | None,
|
|
61
|
+
) -> None:
|
|
46
62
|
"""
|
|
47
63
|
Retrieve vulnerability scan results for a package.
|
|
48
64
|
|
|
@@ -82,7 +98,12 @@ def vulnerabilities(
|
|
|
82
98
|
|
|
83
99
|
context_msg = "Failed to retrieve vulnerability report!"
|
|
84
100
|
with (
|
|
85
|
-
handle_api_exceptions(
|
|
101
|
+
handle_api_exceptions(
|
|
102
|
+
ctx,
|
|
103
|
+
opts=opts,
|
|
104
|
+
context_msg=context_msg,
|
|
105
|
+
summarise_error=_summarise_no_scan_results,
|
|
106
|
+
),
|
|
86
107
|
utils.maybe_spinner(opts),
|
|
87
108
|
):
|
|
88
109
|
data = get_package_scan_result(
|
|
@@ -99,7 +120,7 @@ def vulnerabilities(
|
|
|
99
120
|
|
|
100
121
|
# Filter results if severity or fixable flags are active
|
|
101
122
|
if severity_filter or fixable is not None:
|
|
102
|
-
scans = getattr(data, "scans", []
|
|
123
|
+
scans = getattr(data, "scans", None) or []
|
|
103
124
|
|
|
104
125
|
allowed_severities = (
|
|
105
126
|
[s.strip().lower() for s in severity_filter.split(",")]
|
|
@@ -108,14 +129,12 @@ def vulnerabilities(
|
|
|
108
129
|
)
|
|
109
130
|
|
|
110
131
|
for scan in scans:
|
|
111
|
-
results = getattr(scan, "results", []
|
|
132
|
+
results = getattr(scan, "results", None) or []
|
|
112
133
|
|
|
113
134
|
# 1. Filter by Severity
|
|
114
135
|
if allowed_severities:
|
|
115
136
|
results = [
|
|
116
|
-
res
|
|
117
|
-
for res in results
|
|
118
|
-
if getattr(res, "severity", "unknown").lower() in allowed_severities
|
|
137
|
+
res for res in results if _severity_of(res) in allowed_severities
|
|
119
138
|
]
|
|
120
139
|
|
|
121
140
|
# 2. Filter by Fixable Status
|
|
@@ -155,6 +155,44 @@ class ConfigReader(ConfigFileReader):
|
|
|
155
155
|
|
|
156
156
|
return True
|
|
157
157
|
|
|
158
|
+
@classmethod
|
|
159
|
+
def get_user_filepath(cls):
|
|
160
|
+
"""Get the user-level config file with the highest precedence, existing or not."""
|
|
161
|
+
user_paths = [
|
|
162
|
+
os.path.join(searchpath, filename)
|
|
163
|
+
for searchpath in cls.config_searchpath
|
|
164
|
+
for filename in cls.config_files
|
|
165
|
+
if os.path.isabs(os.path.join(searchpath, filename))
|
|
166
|
+
]
|
|
167
|
+
existing_paths = [path for path in user_paths if os.path.isfile(path)]
|
|
168
|
+
return (existing_paths or user_paths)[0]
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def save_workspace(cls, workspace, profile=None, path=None):
|
|
172
|
+
"""Write the Workspace to the profile section; return the path if it changed."""
|
|
173
|
+
if path and os.path.isdir(path):
|
|
174
|
+
path = os.path.join(path, "config.ini")
|
|
175
|
+
path = path or cls.get_user_filepath()
|
|
176
|
+
if os.path.exists(path):
|
|
177
|
+
with open(path) as config_file:
|
|
178
|
+
content = config_file.read()
|
|
179
|
+
else:
|
|
180
|
+
content = read_file(get_data_path(), "config.ini")
|
|
181
|
+
|
|
182
|
+
section = (
|
|
183
|
+
f"profile:{profile}" if profile and profile != "default" else "default"
|
|
184
|
+
)
|
|
185
|
+
if _get_section_value(content, section, "workspace") == workspace:
|
|
186
|
+
return None
|
|
187
|
+
updated = _set_section_value(content, section, "workspace", workspace)
|
|
188
|
+
if _get_section_value(updated, section, "workspace") != workspace:
|
|
189
|
+
raise ValueError(f"the CLI cannot change the [{section}] section of {path}")
|
|
190
|
+
|
|
191
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
192
|
+
with open(path, "w") as config_file:
|
|
193
|
+
config_file.write(updated)
|
|
194
|
+
return path
|
|
195
|
+
|
|
158
196
|
@classmethod
|
|
159
197
|
def has_default_file(cls):
|
|
160
198
|
"""Check if a configuration file exists."""
|
|
@@ -249,6 +287,36 @@ class ConfigReader(ConfigFileReader):
|
|
|
249
287
|
setattr(opts, k, v)
|
|
250
288
|
|
|
251
289
|
|
|
290
|
+
def _get_section_value(content, section, key):
|
|
291
|
+
"""Get `key` from the INI `section` of `content`, or None if it does not parse."""
|
|
292
|
+
parser = configparser.ConfigParser(interpolation=None)
|
|
293
|
+
parser.optionxform = str
|
|
294
|
+
try:
|
|
295
|
+
parser.read_string(content)
|
|
296
|
+
except configparser.Error:
|
|
297
|
+
return None
|
|
298
|
+
return parser.get(section, key, fallback=None)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _set_section_value(content, section, key, value):
|
|
302
|
+
"""Set `key` in the INI `section` of `content`, preserving comments and layout."""
|
|
303
|
+
line = f"{key} = {value}"
|
|
304
|
+
header = re.search(rf"^\[{re.escape(section)}\].*$", content, re.MULTILINE)
|
|
305
|
+
if not header:
|
|
306
|
+
separator = "\n\n" if content.strip() else ""
|
|
307
|
+
return f"{content.rstrip()}{separator}[{section}]\n{line}\n"
|
|
308
|
+
|
|
309
|
+
next_header = re.compile(r"^\[", re.MULTILINE).search(content, header.end())
|
|
310
|
+
body_end = next_header.start() if next_header else len(content)
|
|
311
|
+
body = content[header.end() : body_end]
|
|
312
|
+
entry = re.compile(rf"^{re.escape(key)}[ \t]*[=:].*$", re.MULTILINE)
|
|
313
|
+
if entry.search(body):
|
|
314
|
+
body = entry.sub(lambda _: line, body, count=1)
|
|
315
|
+
else:
|
|
316
|
+
body = f"\n{line}{body}"
|
|
317
|
+
return content[: header.end()] + body + content[body_end:]
|
|
318
|
+
|
|
319
|
+
|
|
252
320
|
class CredentialsSchema:
|
|
253
321
|
"""Schema for credentials configuration."""
|
|
254
322
|
|
|
@@ -11,7 +11,7 @@ from click.core import ParameterSource
|
|
|
11
11
|
from cloudsmith_cli.cli import validators
|
|
12
12
|
from cloudsmith_cli.core.utils import ColorMode, TTYMode, color_enabled
|
|
13
13
|
|
|
14
|
-
from ..core import update_check
|
|
14
|
+
from ..core import keyring, update_check
|
|
15
15
|
from ..core.certificate_errors import describe_certificate_error
|
|
16
16
|
from ..core.credentials.chain import CredentialProviderChain
|
|
17
17
|
from ..core.credentials.models import CredentialContext
|
|
@@ -118,6 +118,66 @@ def common_package_action_options(f):
|
|
|
118
118
|
return wrapper
|
|
119
119
|
|
|
120
120
|
|
|
121
|
+
def _absolute_path(ctx, param, value):
|
|
122
|
+
"""Expand ``~`` and environment variables in *value* and make it absolute."""
|
|
123
|
+
if not value:
|
|
124
|
+
return value
|
|
125
|
+
return os.path.abspath(os.path.expanduser(os.path.expandvars(value)))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def keyring_options(f):
|
|
129
|
+
"""Add hidden options that read the keyring settings from the environment."""
|
|
130
|
+
|
|
131
|
+
@click.option(
|
|
132
|
+
"--no-keyring-env",
|
|
133
|
+
"no_keyring",
|
|
134
|
+
envvar="CLOUDSMITH_NO_KEYRING",
|
|
135
|
+
hidden=True,
|
|
136
|
+
)
|
|
137
|
+
@click.option(
|
|
138
|
+
"--keyring-backend-env",
|
|
139
|
+
"keyring_backend",
|
|
140
|
+
envvar="CLOUDSMITH_KEYRING_BACKEND",
|
|
141
|
+
hidden=True,
|
|
142
|
+
)
|
|
143
|
+
@click.option(
|
|
144
|
+
"--keyring-key-env",
|
|
145
|
+
"keyring_key",
|
|
146
|
+
envvar="CLOUDSMITH_KEYRING_KEY",
|
|
147
|
+
hidden=True,
|
|
148
|
+
)
|
|
149
|
+
@click.option(
|
|
150
|
+
"--keyring-file-path-env",
|
|
151
|
+
"keyring_file_path",
|
|
152
|
+
envvar="CLOUDSMITH_KEYRING_FILE_PATH",
|
|
153
|
+
callback=_absolute_path,
|
|
154
|
+
hidden=True,
|
|
155
|
+
)
|
|
156
|
+
@click.option(
|
|
157
|
+
"--keyring-dir-env",
|
|
158
|
+
"keyring_dir",
|
|
159
|
+
envvar="CLOUDSMITH_KEYRING_DIR",
|
|
160
|
+
callback=_absolute_path,
|
|
161
|
+
hidden=True,
|
|
162
|
+
)
|
|
163
|
+
@click.pass_context
|
|
164
|
+
@functools.wraps(f)
|
|
165
|
+
def wrapper(ctx, *args, **kwargs):
|
|
166
|
+
keyring.configure(
|
|
167
|
+
keyring.KeyringSettings(
|
|
168
|
+
no_keyring=(kwargs.pop("no_keyring") or "").strip().lower()
|
|
169
|
+
in ("1", "true", "yes"),
|
|
170
|
+
backend=kwargs.pop("keyring_backend"),
|
|
171
|
+
key=kwargs.pop("keyring_key"),
|
|
172
|
+
file_path=kwargs.pop("keyring_file_path"),
|
|
173
|
+
directory=kwargs.pop("keyring_dir"),
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
return ctx.invoke(f, *args, **kwargs)
|
|
177
|
+
|
|
178
|
+
return wrapper
|
|
179
|
+
|
|
180
|
+
|
|
121
181
|
def common_cli_config_options(f):
|
|
122
182
|
"""Add common CLI config options to commands."""
|
|
123
183
|
|
|
@@ -39,14 +39,19 @@ class IntOrWildcard(click.ParamType):
|
|
|
39
39
|
self.fail(f"{value!r} is not a valid integer or '*'", param, ctx)
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def
|
|
42
|
+
def _option_hint(param: str) -> str:
|
|
43
|
+
"""Return the Click error hint for the option that has the param name."""
|
|
44
|
+
return f"'--{param.replace('_', '-')}'"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def transform_api_header_authorization(param: str, value: str) -> str:
|
|
43
48
|
"""Transform a username:password value into a base64 string."""
|
|
44
49
|
try:
|
|
45
50
|
username, password = value.split(":", 1)
|
|
46
51
|
except ValueError:
|
|
47
52
|
raise click.BadParameter(
|
|
48
53
|
"Authorization header needs to be Authorization=username:password",
|
|
49
|
-
|
|
54
|
+
param_hint=_option_hint(param),
|
|
50
55
|
)
|
|
51
56
|
|
|
52
57
|
value = f"{username.strip()}:{password}"
|
|
@@ -57,7 +62,7 @@ def transform_api_header_authorization(param, value):
|
|
|
57
62
|
API_HEADER_TRANSFORMS["Authorization"] = transform_api_header_authorization
|
|
58
63
|
|
|
59
64
|
|
|
60
|
-
def validate_api_headers(param, value):
|
|
65
|
+
def validate_api_headers(param: str, value: str | None) -> dict[str, str] | None:
|
|
61
66
|
"""Validate that API headers is a CSV of k=v pairs."""
|
|
62
67
|
# pylint: disable=unused-argument
|
|
63
68
|
if not value:
|
|
@@ -73,7 +78,7 @@ def validate_api_headers(param, value):
|
|
|
73
78
|
if bad_header == k:
|
|
74
79
|
raise click.BadParameter(
|
|
75
80
|
f"{bad_header} is not an allowed header",
|
|
76
|
-
|
|
81
|
+
param_hint=_option_hint(param),
|
|
77
82
|
)
|
|
78
83
|
|
|
79
84
|
if k in API_HEADER_TRANSFORMS:
|
|
@@ -81,7 +86,8 @@ def validate_api_headers(param, value):
|
|
|
81
86
|
v = transform_func(param, v)
|
|
82
87
|
except ValueError:
|
|
83
88
|
raise click.BadParameter(
|
|
84
|
-
"Values need to be a CSV of key=value pairs",
|
|
89
|
+
"Values need to be a CSV of key=value pairs",
|
|
90
|
+
param_hint=_option_hint(param),
|
|
85
91
|
)
|
|
86
92
|
|
|
87
93
|
headers[k] = v
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import cloudsmith_api
|
|
2
2
|
import pytest
|
|
3
3
|
|
|
4
|
+
from cloudsmith_cli.core import keyring as core_keyring
|
|
4
5
|
from cloudsmith_cli.core import session, telemetry
|
|
5
6
|
|
|
6
7
|
#: A syntactically valid DSN that is never contacted: tests that report use
|
|
@@ -19,6 +20,12 @@ def no_real_error_reports(monkeypatch):
|
|
|
19
20
|
monkeypatch.setattr(telemetry, "_transport", None)
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
@pytest.fixture(autouse=True)
|
|
24
|
+
def default_keyring_settings():
|
|
25
|
+
"""Start each test with the default keyring settings."""
|
|
26
|
+
core_keyring.configure(core_keyring.KeyringSettings())
|
|
27
|
+
|
|
28
|
+
|
|
22
29
|
@pytest.fixture()
|
|
23
30
|
def telemetry_config_dir(monkeypatch, tmp_path_factory):
|
|
24
31
|
"""An empty config search path, isolated from the developer's config.ini.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import contextlib
|
|
4
4
|
import http.client
|
|
5
5
|
import json
|
|
6
|
+
from collections.abc import Iterator
|
|
6
7
|
|
|
7
8
|
from cloudsmith_api.rest import ApiException as _ApiException
|
|
8
9
|
|
|
@@ -37,8 +38,26 @@ class ApiException(Exception):
|
|
|
37
38
|
return f"{self.status} - {self.detail or self.status_description}"
|
|
38
39
|
|
|
39
40
|
|
|
41
|
+
def error_detail_and_fields(
|
|
42
|
+
data: object,
|
|
43
|
+
) -> tuple[str | None, dict[str, object] | None]:
|
|
44
|
+
"""Get the detail and the fields from a parsed API error body of any JSON shape."""
|
|
45
|
+
if isinstance(data, dict):
|
|
46
|
+
detail = data.get("detail")
|
|
47
|
+
fields = data.get("fields")
|
|
48
|
+
return (
|
|
49
|
+
None if detail is None else str(detail),
|
|
50
|
+
fields if isinstance(fields, dict) else None,
|
|
51
|
+
)
|
|
52
|
+
if isinstance(data, list):
|
|
53
|
+
return " ".join(item for item in data if isinstance(item, str)) or None, None
|
|
54
|
+
if isinstance(data, str):
|
|
55
|
+
return data or None, None
|
|
56
|
+
return None, None
|
|
57
|
+
|
|
58
|
+
|
|
40
59
|
@contextlib.contextmanager
|
|
41
|
-
def catch_raise_api_exception():
|
|
60
|
+
def catch_raise_api_exception() -> Iterator[None]:
|
|
42
61
|
"""Context manager that translates upstream API exceptions."""
|
|
43
62
|
try:
|
|
44
63
|
yield
|
|
@@ -48,10 +67,7 @@ def catch_raise_api_exception():
|
|
|
48
67
|
|
|
49
68
|
if exc.body:
|
|
50
69
|
try:
|
|
51
|
-
|
|
52
|
-
data = json.loads(exc.body)
|
|
53
|
-
detail = data.get("detail", None)
|
|
54
|
-
fields = data.get("fields", None)
|
|
70
|
+
detail, fields = error_detail_and_fields(json.loads(exc.body))
|
|
55
71
|
except ValueError:
|
|
56
72
|
pass
|
|
57
73
|
|
|
@@ -9,6 +9,19 @@ import cloudsmith_api
|
|
|
9
9
|
from ..rest import RestClient
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
def _basic_auth_credentials(encoded: str) -> tuple[str, str]:
|
|
13
|
+
"""Decode a Basic auth token into a username and password."""
|
|
14
|
+
try:
|
|
15
|
+
decoded = base64.b64decode(encoded, validate=True).decode("utf-8")
|
|
16
|
+
username, password = decoded.split(":", 1)
|
|
17
|
+
except ValueError as exc:
|
|
18
|
+
raise click.BadParameter(
|
|
19
|
+
"Authorization header needs to be Authorization=username:password",
|
|
20
|
+
param_hint="'--api-headers'",
|
|
21
|
+
) from exc
|
|
22
|
+
return username, password
|
|
23
|
+
|
|
24
|
+
|
|
12
25
|
def initialise_api(
|
|
13
26
|
debug=False,
|
|
14
27
|
host=None,
|
|
@@ -24,7 +37,7 @@ def initialise_api(
|
|
|
24
37
|
error_retry_backoff=None,
|
|
25
38
|
error_retry_codes=None,
|
|
26
39
|
error_retry_cb=None,
|
|
27
|
-
):
|
|
40
|
+
) -> cloudsmith_api.Configuration:
|
|
28
41
|
"""Initialise the cloudsmith_api.Configuration."""
|
|
29
42
|
# FIXME: pylint: disable=too-many-arguments
|
|
30
43
|
config = cloudsmith_api.Configuration()
|
|
@@ -60,9 +73,7 @@ def initialise_api(
|
|
|
60
73
|
if auth_header and " " in auth_header:
|
|
61
74
|
auth_type, encoded = auth_header.split(" ", 1)
|
|
62
75
|
if auth_type == "Basic":
|
|
63
|
-
|
|
64
|
-
values = decoded.decode("utf-8")
|
|
65
|
-
config.username, config.password = values.split(":")
|
|
76
|
+
config.username, config.password = _basic_auth_credentials(encoded)
|
|
66
77
|
|
|
67
78
|
if config.debug:
|
|
68
79
|
click.echo("Username and password config values set")
|
|
@@ -43,7 +43,9 @@ def get_user_token(login, password, totp_token=None, two_factor_token=None):
|
|
|
43
43
|
if e.status == 422 and e.body:
|
|
44
44
|
try:
|
|
45
45
|
response_data = json.loads(e.body)
|
|
46
|
-
if response_data.get(
|
|
46
|
+
if isinstance(response_data, dict) and response_data.get(
|
|
47
|
+
"two_factor_required"
|
|
48
|
+
):
|
|
47
49
|
two_factor_token = response_data.get("two_factor_token")
|
|
48
50
|
# Raise custom exception for 2FA requirement
|
|
49
51
|
raise TwoFactorRequiredException(two_factor_token)
|