diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b87e660..18e4243b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added +- Added `-w` and `--workspace` as the shared Workspace option used by authentication, OIDC, and custom-domain discovery. Set `CLOUDSMITH_WORKSPACE` or `workspace` in `config.ini` to configure it once for every command. - Added `cloudsmith repos gpg` for managing the GPG key a repository signs its package indexes with. `get` shows the active key and its armored public block, `upload` installs a key you supply, and `regenerate` replaces the current key with a freshly generated Cloudsmith one. Key material and passphrases are only ever read from a file, stdin, or a hidden prompt, never from a command-line value, and `--debug` is refused on `upload` so the request body can't be logged. Both mutating subcommands accept `-n/--dry-run`, which checks the inputs and the key currently in place - naming the fingerprint that would be replaced - then stops before the request, so a mistyped repository or a stale credential fails there rather than on the real attempt. `regenerate` asks you to type `regenerate` to confirm - with no terminal attached it fails instead of blocking, so pass `-y/--yes` for unattended runs. There's no `delete` subcommand because the API has no way to remove a repository's key. - Added `cloudsmith repos privileges` for managing explicit repository access from the terminal. `list` shows the teams, users and service accounts that were granted access explicitly; `set` grants access to any number of them and leaves everyone else untouched, asking first if it would lower access someone already has; `revoke` takes access away from the ones named, skipping any that had none; and `replace` makes a JSON file (or stdin) the complete truth for the repository. `revoke` and `replace` ask for confirmation first unless `-y` is passed. ### Changed - `cloudsmith copy` now prints `Copied: owner/repo/slug (slug_perm)` after a successful copy and includes `slug_perm` in `-F json` output, matching `push`. +- `cloudsmith domains list` now includes the Workspace slug in a `workspace` field. ## [1.25.0] - 2026-08-24 diff --git a/cloudsmith_cli/cli/commands/auth.py b/cloudsmith_cli/cli/commands/auth.py index 0955b4c7..1bfbd599 100644 --- a/cloudsmith_cli/cli/commands/auth.py +++ b/cloudsmith_cli/cli/commands/auth.py @@ -31,7 +31,7 @@ def _perform_saml_authentication( idp_url = get_idp_url(api_host, owner, session=session) click.echo( - f"Your organization's SAML IDP URL is: {click.style(idp_url, bold=True)}", + f"Your Workspace's SAML IDP URL is: {click.style(idp_url, bold=True)}", err=use_stderr, ) click.echo(err=use_stderr) @@ -75,15 +75,6 @@ def _perform_saml_authentication( @main.command(aliases=["auth"]) -@click.option( - "-o", - "--owner", - metavar="OWNER", - required=True, - callback=validators.validate_owner, - prompt=True, - help="The name of the Cloudsmith organization to authenticate with.", -) @click.option( "-t", "--token", @@ -132,7 +123,6 @@ def _perform_saml_authentication( def authenticate( ctx, opts, - owner, token, force, save_config, @@ -140,7 +130,7 @@ def authenticate( request_api_key_flag, no_browser, ): - """Authenticate to Cloudsmith using the org's SAML setup.""" + """Authenticate to Cloudsmith using the Workspace's SAML setup.""" # Validate mutual exclusivity if request_api_key_flag and (token or force): raise click.UsageError( @@ -175,10 +165,13 @@ def authenticate( err=True, ) - owner = owner[0].strip("[]'") + workspace = opts.org or click.prompt("Workspace", err=use_stderr) + workspace = validators.validate_owner(ctx, None, workspace)[0] + opts.org = workspace click.echo( - f"Beginning authentication for the {click.style(owner, bold=True)} org ... ", + "Beginning authentication for the " + f"{click.style(workspace, bold=True)} Workspace ... ", err=use_stderr, ) @@ -189,7 +182,7 @@ def authenticate( with handle_api_exceptions(ctx, opts=opts, context_msg=context_message): _perform_saml_authentication( opts, - owner, + workspace, enable_token_creation=enable_token_creation, use_stderr=use_stderr, no_browser=no_browser, diff --git a/cloudsmith_cli/cli/commands/credential_helper/cargo.py b/cloudsmith_cli/cli/commands/credential_helper/cargo.py index f40dd2d4..b78e0838 100644 --- a/cloudsmith_cli/cli/commands/credential_helper/cargo.py +++ b/cloudsmith_cli/cli/commands/credential_helper/cargo.py @@ -35,9 +35,10 @@ def cargo(opts, cargo_plugin, provider_args): # pylint: disable=unused-argument the supported protocol versions, then one response per request. Provides credentials for all Cloudsmith Cargo registries: ``*.cloudsmith.io``, - ``*.cloudsmith.com``, and any custom domains configured for the organisation - (requires an organisation - ``--org``, CLOUDSMITH_ORG or ``org`` in - ``config.ini`` - and a valid API key/token). + ``*.cloudsmith.com``, and any custom domains configured for the Workspace + (requires a Workspace - ``--workspace``, CLOUDSMITH_WORKSPACE, or + ``workspace`` in ``config.ini``; legacy aliases are also accepted - and a + valid API key/token). A registry that is not a Cloudsmith one is answered with ``url-not-supported`` so Cargo falls through to the next configured @@ -74,7 +75,7 @@ def cargo(opts, cargo_plugin, provider_args): # pylint: disable=unused-argument \b Environment variables: CLOUDSMITH_API_KEY: API key for authentication (optional) - CLOUDSMITH_ORG: Organisation slug (required for custom domain support) + CLOUDSMITH_WORKSPACE: Workspace slug (CLOUDSMITH_ORG is also accepted) """ # `provider_args` collects the extra arguments Cargo appends from the # credential-provider config entry. This provider takes no configuration diff --git a/cloudsmith_cli/cli/commands/credential_helper/docker.py b/cloudsmith_cli/cli/commands/credential_helper/docker.py index 7c34f8a7..25d3d81f 100644 --- a/cloudsmith_cli/cli/commands/credential_helper/docker.py +++ b/cloudsmith_cli/cli/commands/credential_helper/docker.py @@ -28,9 +28,10 @@ def docker(opts, operation): (get/store/erase/list). Provides credentials for all Cloudsmith Docker registries: ``*.cloudsmith.io``, - ``*.cloudsmith.com``, and any custom domains configured for the organisation - (requires an organisation - ``--org``, CLOUDSMITH_ORG or ``org`` in - ``config.ini`` - and a valid API key/token). + ``*.cloudsmith.com``, and any custom domains configured for the Workspace + (requires a Workspace - ``--workspace``, CLOUDSMITH_WORKSPACE, or + ``workspace`` in ``config.ini``; legacy aliases are also accepted - and a + valid API key/token). Input (stdin): Server URL as plain text (e.g. "docker.cloudsmith.io") @@ -51,7 +52,7 @@ def docker(opts, operation): Environment variables: CLOUDSMITH_API_KEY: API key for authentication (optional) - CLOUDSMITH_ORG: Organisation slug (required for custom domain support) + CLOUDSMITH_WORKSPACE: Workspace slug (CLOUDSMITH_ORG is also accepted) """ exit_code, stdout, stderr = execute( operation, diff --git a/cloudsmith_cli/cli/commands/credential_helper/generic.py b/cloudsmith_cli/cli/commands/credential_helper/generic.py index e5f1356f..db98b17d 100644 --- a/cloudsmith_cli/cli/commands/credential_helper/generic.py +++ b/cloudsmith_cli/cli/commands/credential_helper/generic.py @@ -27,7 +27,7 @@ def generic(opts): Resolves a credential through the full provider chain and writes a versioned JSON document to stdout. Takes no arguments: a Cloudsmith token - is organisation-wide, so the host it will be used against does not change + is Workspace-wide, so the host it will be used against does not change which credential resolves. \b diff --git a/cloudsmith_cli/cli/commands/credential_helper/pnpm.py b/cloudsmith_cli/cli/commands/credential_helper/pnpm.py index 55654c7a..bfe9866a 100644 --- a/cloudsmith_cli/cli/commands/credential_helper/pnpm.py +++ b/cloudsmith_cli/cli/commands/credential_helper/pnpm.py @@ -33,7 +33,7 @@ def pnpm(opts, repo): \b Environment variables: CLOUDSMITH_API_KEY: API key for authentication (optional) - CLOUDSMITH_ORG: Organisation slug (required for custom domain support) + CLOUDSMITH_WORKSPACE: Workspace slug (CLOUDSMITH_ORG is also accepted) """ exit_code, stdout, stderr = execute( diff --git a/cloudsmith_cli/cli/commands/domains.py b/cloudsmith_cli/cli/commands/domains.py index f6647255..c4f42ae6 100644 --- a/cloudsmith_cli/cli/commands/domains.py +++ b/cloudsmith_cli/cli/commands/domains.py @@ -63,6 +63,7 @@ def _custom_entries(records) -> list[dict]: "format": format_for_backend_kind(record.backend_kind), "type": "custom", "domain_type": record.domain_type.value, + "workspace": record.org, "org": record.org, "repository": record.repository, "primary": record.primary, @@ -133,19 +134,19 @@ def list_domains( # pylint: disable=too-many-arguments ``*.cloudsmith.io`` service hosts alongside custom domains, with ``type`` distinguishing ``default`` from ``custom`` and ``domain_type`` saying what each host is for: ``download``, ``upload``, or ``native_api`` for a host - speaking one package format's own protocol. An organisation's own custom + speaking one package format's own protocol. A Workspace's own custom domains are listed ahead of the built-in hosts, which serve as the fallback. Only usable hosts are listed: a custom domain that is disabled or not yet validated serves nothing, so it is left out rather than offered as somewhere to publish to. Check it in the Cloudsmith UI if one you expect is missing here. - Built-in hosts are always listed and need no organisation or authentication. - An organisation from ``--org``, CLOUDSMITH_ORG or ``org`` in ``config.ini`` - adds its custom domains, and a failed lookup exits non-zero rather than - rendering as "no domains". With no organisation the command lists whatever - earlier runs cached and makes no API call; ``--refresh`` bypasses that cache - for a configured organisation. + Built-in hosts are always listed and need no Workspace or authentication. + A Workspace from ``--workspace``, CLOUDSMITH_WORKSPACE, or ``workspace`` in + ``config.ini`` adds its custom domains, and a failed lookup exits non-zero + rather than rendering as "no domains". With no Workspace the command lists + whatever earlier runs cached and makes no API call; ``--refresh`` bypasses + that cache for a configured Workspace. Where two custom domains could serve the same request Cloudsmith picks the one bound to the repository in hand, then ``primary`` over secondary, then @@ -163,7 +164,7 @@ def list_domains( # pylint: disable=too-many-arguments Output (stdout): JSON: {"version": 1, "domains": [{"host": ..., "format": ..., - "type": ..., "domain_type": ..., "org": ..., "repository": ..., + "type": ..., "domain_type": ..., "workspace": ..., "repository": ..., "primary": ..., "created_at": ...}], "meta": {"pagination": {...}}} "meta" is only present when the result is paginated (i.e. not @@ -177,16 +178,16 @@ def list_domains( # pylint: disable=too-many-arguments $ cloudsmith domains list \b - # List built-in hosts plus an organisation's custom domains - $ cloudsmith domains list --org my-org + # List built-in hosts plus a Workspace's custom domains + $ cloudsmith domains list --workspace my-workspace \b # The hosts usable for one repository, most-preferred first - $ cloudsmith domains list --org my-org --repo my-repo --format maven + $ cloudsmith domains list --workspace my-workspace --repo my-repo --format maven \b # Where to upload to - $ cloudsmith domains list --org my-org --domain-type upload + $ cloudsmith domains list --workspace my-workspace --domain-type upload """ if not page_all: explicit_sources = { @@ -229,6 +230,7 @@ def list_domains( # pylint: disable=too-many-arguments "format": domain.format_label, "type": "default", "domain_type": domain.domain_type.value, + "workspace": None, "org": None, "repository": None, "primary": True, @@ -257,9 +259,9 @@ def list_domains( # pylint: disable=too-many-arguments else: if refresh: click.secho( - "Warning: --refresh needs an organisation to fetch from, so the " - "cached custom domains below are unchanged. Set --org, " - "CLOUDSMITH_ORG or org in config.ini.", + "Warning: --refresh needs a Workspace to fetch from, so the " + "cached custom domains below are unchanged. Set --workspace, " + "CLOUDSMITH_WORKSPACE, or workspace in config.ini.", fg="yellow", err=True, ) diff --git a/cloudsmith_cli/cli/config.py b/cloudsmith_cli/cli/config.py index 7281121e..271da3e9 100644 --- a/cloudsmith_cli/cli/config.py +++ b/cloudsmith_cli/cli/config.py @@ -67,6 +67,7 @@ class Default(SectionSchema): mcp_allowed_tools = ConfigParam(name="mcp_allowed_tools", type=str) mcp_allowed_tool_groups = ConfigParam(name="mcp_allowed_tool_groups", type=str) oidc_audience = ConfigParam(name="oidc_audience", type=str) + workspace = ConfigParam(name="workspace", type=str) org = ConfigParam(name="org", type=str) organization = ConfigParam(name="organization", type=str) oidc_org = ConfigParam(name="oidc_org", type=str) @@ -202,7 +203,18 @@ def load_config(cls, opts, path=None, profile=None): @staticmethod def _load_values_into_opts(opts, values): + preferred_workspace = values.get("workspace") + if isinstance(preferred_workspace, str): + preferred_workspace = preferred_workspace.strip() + if preferred_workspace.startswith(('"', "'")): + preferred_workspace = preferred_workspace[1:] + if preferred_workspace.endswith(('"', "'")): + preferred_workspace = preferred_workspace[:-1] + preferred_workspace = preferred_workspace.strip() + has_preferred_workspace = bool(preferred_workspace) for k, v in values.items(): + if has_preferred_workspace and k in ("org", "organization", "oidc_org"): + continue if v is None: continue if isinstance(v, str): @@ -500,6 +512,16 @@ def org(self, value): value = value.strip() or None self._set_option("org", value) + @property + def workspace(self): + """Get the Workspace slug using current terminology.""" + return self.org + + @workspace.setter + def workspace(self, value): + """Set the Workspace slug using current terminology.""" + self.org = value + @property def organization(self): """Get the organisation slug, spelled in full.""" diff --git a/cloudsmith_cli/cli/decorators.py b/cloudsmith_cli/cli/decorators.py index 9012ab1d..a76305e8 100644 --- a/cloudsmith_cli/cli/decorators.py +++ b/cloudsmith_cli/cli/decorators.py @@ -436,12 +436,16 @@ def resolve_credentials(f): help="The OIDC audience for token requests.", ) @click.option( + "-w", + "--workspace", "--org", "--organization", "--oidc-org", + "-o", + "--owner", "org", - envvar="CLOUDSMITH_ORG", - help="The Cloudsmith organisation slug.", + envvar=["CLOUDSMITH_WORKSPACE", "CLOUDSMITH_ORG"], + help="The Cloudsmith Workspace slug.", ) @click.option( "--oidc-service-slug", diff --git a/cloudsmith_cli/cli/tests/commands/test_auth.py b/cloudsmith_cli/cli/tests/commands/test_auth.py index 68bb7616..79546de5 100644 --- a/cloudsmith_cli/cli/tests/commands/test_auth.py +++ b/cloudsmith_cli/cli/tests/commands/test_auth.py @@ -51,6 +51,58 @@ def mock_auth_server(): class TestAuthenticateCommand: """Tests for the authenticate command.""" + @pytest.mark.parametrize( + "option", + [ + "--workspace", + "-w", + "--org", + "--organization", + "--oidc-org", + "--owner", + "-o", + ], + ) + def test_workspace_option_aliases( + self, + option, + runner, + mock_saml_session, + mock_get_idp_url, + mock_webbrowser, + mock_auth_server, + ): + """Current and legacy option spellings identify the same Workspace.""" + result = runner.invoke( + authenticate, + [option, "test-workspace", "--no-browser"], + catch_exceptions=False, + ) + + assert result.exit_code == 0, result.output + assert mock_auth_server.call_args.kwargs["owner"] == "test-workspace" + + @pytest.mark.parametrize("envvar", ["CLOUDSMITH_WORKSPACE", "CLOUDSMITH_ORG"]) + def test_workspace_environment_aliases( + self, + envvar, + runner, + mock_saml_session, + mock_get_idp_url, + mock_webbrowser, + mock_auth_server, + ): + """Authentication inherits either Workspace environment variable.""" + result = runner.invoke( + authenticate, + ["--no-browser"], + env={envvar: "test-workspace"}, + catch_exceptions=False, + ) + + assert result.exit_code == 0, result.output + assert mock_auth_server.call_args.kwargs["owner"] == "test-workspace" + def test_auth_command_invokes_webserver( self, runner, @@ -204,8 +256,8 @@ def test_no_browser_skips_webbrowser_open( assert result.exit_code == 0 mock_webbrowser.open.assert_not_called() assert "Skipping automatic browser launch" in result.output - assert "Opening your organization's SAML IDP URL" not in result.output - assert "Your organization's SAML IDP URL is:" in result.output + assert "Opening your Workspace's SAML IDP URL" not in result.output + assert "Your Workspace's SAML IDP URL is:" in result.output mock_auth_server.assert_called_once() mock_auth_server.return_value.handle_request.assert_called_once() @@ -414,5 +466,5 @@ def test_json_mode_writes_error_json_to_stdout( payload = json.loads(result.stdout) assert payload["detail"] == "Invalid input." assert payload["meta"]["code"] == 422 - assert "Beginning authentication for the testorg org" in result.stderr + assert "Beginning authentication for the testorg Workspace" in result.stderr assert "Beginning authentication for the testorg org" not in result.stdout diff --git a/cloudsmith_cli/cli/tests/commands/test_domains.py b/cloudsmith_cli/cli/tests/commands/test_domains.py index 19440fc3..bc877130 100644 --- a/cloudsmith_cli/cli/tests/commands/test_domains.py +++ b/cloudsmith_cli/cli/tests/commands/test_domains.py @@ -122,6 +122,7 @@ def test_document_has_one_schema_for_both_kinds_of_host(runner, monkeypatch): "format": "python", "type": "custom", "domain_type": "native_api", + "workspace": "acme", "org": "acme", "repository": None, "primary": True, @@ -132,6 +133,7 @@ def test_document_has_one_schema_for_both_kinds_of_host(runner, monkeypatch): "format": "python", "type": "default", "domain_type": "native_api", + "workspace": None, "org": None, "repository": None, "primary": True, diff --git a/cloudsmith_cli/cli/tests/test_org_option.py b/cloudsmith_cli/cli/tests/test_org_option.py index e44c7957..518f4a5f 100644 --- a/cloudsmith_cli/cli/tests/test_org_option.py +++ b/cloudsmith_cli/cli/tests/test_org_option.py @@ -1,10 +1,9 @@ # Copyright 2026 Cloudsmith Ltd -"""Tests for the organisation option and its accepted spellings. +"""Tests for the Workspace option and its accepted spellings. -The organisation is named three ways for historical reasons: ``--org`` is the -name to use, ``--organization`` reads naturally in scripts, and ``--oidc-org`` -is what the option was called when only OIDC token exchange consumed it. All -three are one option, so a consumer never has to know which era it came from. +``--workspace`` and ``-w`` use current Cloudsmith terminology. ``--org``, +``--organization``, ``--oidc-org``, ``--owner``, and ``-o`` remain available +for compatibility. All seven flags are one option. """ import click @@ -36,6 +35,7 @@ def org_reporting_command(monkeypatch): The Options object is a process-wide thread-local, so it is cleared per test to stop one invocation's organisation leaking into the next. """ + monkeypatch.delenv("CLOUDSMITH_WORKSPACE", raising=False) monkeypatch.delenv("CLOUDSMITH_ORG", raising=False) monkeypatch.delattr(OPTIONS, "value", raising=False) @@ -48,31 +48,87 @@ def report(ctx, opts): # pylint: disable=unused-argument return report -@pytest.mark.parametrize("key", ["org", "organization", "oidc_org"]) +@pytest.mark.parametrize("key", ["workspace", "org", "organization", "oidc_org"]) def test_every_config_spelling_sets_the_one_organisation(config_file, key): """The aliases are one value in config.ini, not three independent settings.""" opts = Options() opts.load_config_file(config_file(f"[default]\n{key} = acme\n")) assert opts.org == "acme" + assert opts.workspace == "acme" assert opts.organization == "acme" assert opts.oidc_org == "acme" -@pytest.mark.parametrize("flag", ["--org", "--organization", "--oidc-org"]) +def test_workspace_config_takes_precedence_over_legacy_aliases(config_file): + """The current config key wins regardless of the order used in the file.""" + opts = Options() + opts.load_config_file( + config_file( + "[default]\n" + "workspace = preferred-workspace\n" + "org = legacy-org\n" + "organization = legacy-organization\n" + "oidc_org = legacy-oidc-org\n" + ) + ) + + assert opts.workspace == "preferred-workspace" + assert opts.org == "preferred-workspace" + + +@pytest.mark.parametrize("empty_workspace", ["", '""', "''", '" "']) +def test_empty_workspace_config_does_not_mask_legacy_alias( + config_file, empty_workspace +): + """An empty preferred key allows a populated compatibility key to apply.""" + opts = Options() + opts.load_config_file( + config_file( + f"[default]\nworkspace = {empty_workspace}\norg = legacy-workspace\n" + ) + ) + + assert opts.workspace == "legacy-workspace" + + +@pytest.mark.parametrize( + "flag", + [ + "--workspace", + "-w", + "--org", + "--organization", + "--oidc-org", + "--owner", + "-o", + ], +) def test_every_flag_sets_the_organisation(org_reporting_command, flag): - """All three flags are one option, so any of them reaches ``opts.org``.""" + """Every flag reaches the existing internal ``opts.org`` value.""" result = click.testing.CliRunner().invoke(org_reporting_command, [flag, "acme"]) assert result.exit_code == 0, result.output assert "org=acme" in result.output -def test_environment_sets_the_organisation(org_reporting_command, monkeypatch): - """CLOUDSMITH_ORG is unchanged by the rename, and is still honoured.""" - monkeypatch.setenv("CLOUDSMITH_ORG", "acme-from-env") +@pytest.mark.parametrize("envvar", ["CLOUDSMITH_WORKSPACE", "CLOUDSMITH_ORG"]) +def test_environment_sets_the_organisation(org_reporting_command, monkeypatch, envvar): + """Both environment-variable spellings set the internal organisation value.""" + monkeypatch.setenv(envvar, "acme-from-env") result = click.testing.CliRunner().invoke(org_reporting_command, []) assert result.exit_code == 0, result.output assert "org=acme-from-env" in result.output + + +def test_workspace_environment_takes_precedence(org_reporting_command, monkeypatch): + """Current Workspace terminology wins when both environment aliases are set.""" + monkeypatch.setenv("CLOUDSMITH_ORG", "legacy-org") + monkeypatch.setenv("CLOUDSMITH_WORKSPACE", "preferred-workspace") + + result = click.testing.CliRunner().invoke(org_reporting_command, []) + + assert result.exit_code == 0, result.output + assert "org=preferred-workspace" in result.output diff --git a/cloudsmith_cli/core/credentials/providers/oidc_provider.py b/cloudsmith_cli/core/credentials/providers/oidc_provider.py index 680571b6..62d81d8e 100644 --- a/cloudsmith_cli/core/credentials/providers/oidc_provider.py +++ b/cloudsmith_cli/core/credentials/providers/oidc_provider.py @@ -13,9 +13,9 @@ class OidcProvider(CredentialProvider): """Resolves credentials via OIDC auto-discovery. - Requires CLOUDSMITH_ORG and CLOUDSMITH_SERVICE_SLUG to be set (via env - vars or click options). Auto-detects the environment, fetches the vendor - OIDC JWT, and exchanges it for a short-lived Cloudsmith API token. + Requires a Workspace and CLOUDSMITH_SERVICE_SLUG to be set (via environment + variables or Click options). Auto-detects the environment, fetches the + vendor OIDC JWT, and exchanges it for a short-lived Cloudsmith API token. """ name = "oidc" @@ -37,7 +37,8 @@ def resolve( # pylint: disable=too-many-return-statements if not org or not service_slug: if context.debug: logger.debug( - "OidcProvider: CLOUDSMITH_ORG and/or CLOUDSMITH_SERVICE_SLUG " + "OidcProvider: CLOUDSMITH_WORKSPACE (or CLOUDSMITH_ORG) " + "and/or CLOUDSMITH_SERVICE_SLUG " "not set, skipping OIDC auto-discovery" ) return None diff --git a/cloudsmith_cli/credential_helpers/common.py b/cloudsmith_cli/credential_helpers/common.py index 17db3dca..e036799a 100644 --- a/cloudsmith_cli/credential_helpers/common.py +++ b/cloudsmith_cli/credential_helpers/common.py @@ -62,8 +62,9 @@ def is_cloudsmith_domain( backend_kind: If given, custom domains only match when their backend_kind equals it (standard *.cloudsmith.io domains always match regardless). When None (default), any enabled+validated custom domain matches. - org: Organisation slug whose custom domains to match against, as the - CLI resolved it from --org, CLOUDSMITH_ORG or config.ini + org: Workspace slug whose custom domains to match against, as the CLI + resolved it from --workspace, CLOUDSMITH_WORKSPACE, + CLOUDSMITH_ORG, workspace in config.ini, or a legacy config alias Returns: bool: True if this is a Cloudsmith domain diff --git a/cloudsmith_cli/data/config.ini b/cloudsmith_cli/data/config.ini index e62071b9..b00ad540 100644 --- a/cloudsmith_cli/data/config.ini +++ b/cloudsmith_cli/data/config.ini @@ -12,6 +12,9 @@ api_ssl_verify=true # The user agent to use for requests (default: calculated). api_user_agent= +# The Cloudsmith Workspace slug (default: None). +workspace= + # Profile-based configuration # You can set as many additional profiles as you need to provide