Skip to content

feat(no-ticket): get Workspace domains from the v2 API - #476

Draft
cloudsmith-iduffy wants to merge 9 commits into
masterfrom
iduffy/v2-workspace-domains
Draft

cloudsmith-iduffy wants to merge 9 commits into
masterfrom
iduffy/v2-workspace-domains

Conversation

@cloudsmith-iduffy

@cloudsmith-iduffy cloudsmith-iduffy commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Description

The CLI kept its own table of Cloudsmith hostnames (dl.cloudsmith.io, docker.cloudsmith.io, and the rest). That table pinned the CLI to production hostnames. On any other API host, cloudsmith domains list and the credential helpers still showed the production hosts.

This PR removes the table. The CLI now asks the configured API host for the domains of a Workspace with GET /v2/workspaces/{workspace}/domains/. That one call returns the Cloudsmith-owned domains and the Workspace's custom domains, already in preference order. A change of API host now needs no code change.

Changes:

  • Add core/api/workspaces.list_domains, which follows the Link: rel="next" header through the shared v2 client.
  • Rename credential_helpers/custom_domains.py to workspace_domains.py. The CLI no longer sorts domains itself (by repository, primary, then created_at). The order the API returns is the priority, and the CLI keeps it. The enabled/validated checks are also removed, because the API lists only active domains.
  • Keep a separate domain cache for each API host, so a Workspace on one API host never answers for the Workspace with the same name on another.
  • Remove the [domains] section in config.ini. Its only purpose was to replace the built-in table for other hosts.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • Other (please describe)

Additional Notes

Breaking change to cloudsmith domains list. The JSON document is now version 2:

  • The primary and created_at fields are removed. The API now gives the order.
  • domain_type can also be cloudsmith_api.
  • The command needs a Workspace and authentication. Without a Workspace, it lists only cached domains and prints a warning.

Checked against live API hosts. On production, cloudsmith domains list --workspace cloudsmith --refresh returns 23 domains: the custom install.cloudsmith.com first, then dl.cloudsmith.io, docker.cloudsmith.io, and the other Cloudsmith-owned hosts. A second API host returned its own hosts, and the credential helpers matched each custom domain only on its own API host. An API host that does not serve the v2 endpoint yet makes domains list exit with a 404. The credential helpers then fall back to their default hosts.

The CLI docs in cloudsmith-docs need an update for the version 2 output and the removed [domains] section after this is released.

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings October 12, 2026 20:31
Comment thread cloudsmith_cli/cli/tests/commands/test_domains.py Fixed
Comment thread cloudsmith_cli/cli/tests/commands/test_domains.py Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Cache partitioning and redirect handling can allow credentials to cross API deployment boundaries.

2 open findings
What changed in this PR

Moves Workspace domain discovery to the configured v2 API, replacing production-specific domain tables.

Changes:

  • Adds a shared v2 client and paginated Workspace-domain API.
  • Introduces per-API-host domain caching and JSON output version 2.
  • Updates credential helpers, tests, dependencies, and documentation.
File Description
uv.lock Locks cloudsmith-api 2.0.38.
pyproject.toml Raises the minimum SDK version.
cloudsmith_cli/​credential_helpers/​workspace_domains.py Implements domain parsing and caching.
cloudsmith_cli/​credential_helpers/​pnpm/​installer.py Uses Workspace discovery.
cloudsmith_cli/​credential_helpers/​docker/​installer.py Uses Workspace discovery.
cloudsmith_cli/​credential_helpers/​default_domains.py Removes the built-in domain table.
cloudsmith_cli/​credential_helpers/​custom_domains.py Removes legacy custom-domain discovery.
cloudsmith_cli/​credential_helpers/​common.py Validates hosts against Workspace domains.
cloudsmith_cli/​credential_helpers/​cargo/​installer.py Uses Workspace discovery.
cloudsmith_cli/​core/​tests/​test_workspaces.py Tests v2 domain requests and pagination.
cloudsmith_cli/​core/​api/​workspaces.py Adds the Workspace domains API wrapper.
cloudsmith_cli/​core/​api/​v2.py Adds the shared v2 HTTP client.
cloudsmith_cli/​core/​api/​orgs.py Removes the legacy domains call.
cloudsmith_cli/​core/​api/​metadata.py Migrates metadata calls to the v2 client.
cloudsmith_cli/​cli/​tests/​commands/​test_workspace_domains.py Tests domain parsing and cache isolation.
cloudsmith_cli/​cli/​tests/​commands/​test_domains.py Updates command behavior tests.
cloudsmith_cli/​cli/​tests/​commands/​test_default_domains.py Removes obsolete table tests.
cloudsmith_cli/​cli/​tests/​commands/​test_custom_domain_precedence.py Removes client-side precedence tests.
cloudsmith_cli/​cli/​tests/​commands/​test_credential_helper.py Updates helper discovery tests.
cloudsmith_cli/​cli/​tests/​commands/​test_credential_helper_install.py Updates installer cache tests.
cloudsmith_cli/​cli/​commands/​domains.py Emits API-supplied domains using schema v2.
CHANGELOG.md Documents the breaking behavior changes.
.oss-scanner/​threat_model.md Updates the security-sensitive module path.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cloudsmith_cli/core/api/v2.py Outdated
Comment on lines +78 to +84
response = client.rest_client.request(
method,
url,
query_params=query_params,
headers=_build_headers(client.config),
body=body,
_request_timeout=REQUEST_TIMEOUT_SECONDS,
Comment on lines +173 to +178
host = urlparse(api_host).hostname if api_host else None
cache_dir = (
Path(get_default_config_path())
/ "domains_cache"
/ _safe_name(host or "default")
)
@cloudsmith-iduffy
cloudsmith-iduffy force-pushed the iduffy/v2-workspace-domains branch 4 times, most recently from 4e43950 to 47f388b Compare October 12, 2026 20:59
@cloudsmith-iduffy
cloudsmith-iduffy changed the base branch from master to iduffy/shared-v2-client October 12, 2026 20:59
@cloudsmith-iduffy
cloudsmith-iduffy added this pull request to stack #479 October 12, 2026 20:59
@cloudsmith-iduffy
cloudsmith-iduffy marked this pull request as draft October 12, 2026 21:01
@cloudsmith-iduffy
cloudsmith-iduffy force-pushed the iduffy/v2-workspace-domains branch from 47f388b to 68a1c40 Compare October 13, 2026 13:47
@cloudsmith-iduffy

Copy link
Copy Markdown
Collaborator Author

@copilot Can you modify my change so that its not a breaking change? perhaps we can introduce something like a --version 2 flag and introduce a notice for no flag being specified to say it will eventually be like --version 2 ?

Copilot AI commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

@copilot Can you modify my change so that its not a breaking change? perhaps we can introduce something like a --version 2 flag and introduce a notice for no flag being specified to say it...

Implemented in 92ffe0c (test cleanup in 7c92e81).

  • No flag keeps version 1 behavior and prints a future-default notice to stderr; JSON stdout stays unchanged.
  • --version 1 pins existing behavior without the notice.
  • --version 2 opts into API-driven discovery and the new JSON schema.
  • Existing [domains] configuration and credential-helper behavior remain supported.

1,547 non-integration tests passed; hooks passed and CodeQL reports no alerts. Existing repository-wide type-check diagnostics remain.

@cloudsmith-iduffy

Copy link
Copy Markdown
Collaborator Author

@copilot can we modify the v1 behaviour slightly? Use the new api but keep the same format adding null fields if the data isn't available.

Copilot AI commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

@copilot can we modify the v1 behaviour slightly? Use the new api but keep the same format adding null fields if the data isn't available.

Implemented in 7c12c8c.

  • Default output and --version 1 now use the Workspace domains API, retaining the v1 JSON fields with primary and created_at set to null.
  • Both versions preserve API ordering; v2 and the default-version notice remain unchanged.
  • Both now use cached domains only without a Workspace and ignore [domains] overrides. Credential helpers are unchanged.

1,552 non-integration tests passed, along with hooks and command type checking. CodeQL reports no alerts.

cloudsmith-iduffy and others added 8 commits October 15, 2026 00:07
Move the v2 HTTP client from core/api/metadata.py to core/api/v2.py so
that other v2 endpoints can use it. Add request_url for absolute URLs
such as pagination links. It refuses a URL on another scheme or host,
so the API key goes only to the configured host. Give every v2 request
a 30-second timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Requests drops the Authorization header on a redirect to another host,
but it sends the X-Api-Key header on. The v2 session now drops that
header under the same rule. Add the copyright header to the new files.

The CHANGELOG now says that the 30-second timeout applies to each
attempt, because the CLI retries a request that times out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntial

The v2 client read the API key and the SSO header back from the SDK
Configuration. That copied the auth logic from initialise_api. Now each
v2 call takes the CredentialResult that resolve_credentials gives, and
uses its auth_headers(). Tests cover the API key, the SAML access token,
no credential, the user agent, extra headers, the proxy and SSL
verification.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared retry setup retries a request after a read timeout for every
method. A slow POST that the server accepts is then sent again, and can
create a second metadata entry. A v2 POST now gets only a connect
timeout. The SAML metadata test now gets its bearer token only from the
credential argument.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
create_requests_session now builds the session that drops X-Api-Key on
a redirect to another host. The v1 SDK client, uploads, downloads and
the v2 client all use it. Requests already drops the Authorization
header in the same case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the built-in table of Cloudsmith hostnames and the v1
custom-domains call with GET /v2/workspaces/{workspace}/domains/. The
configured API host now supplies its own hosts, so the CLI works
against any Cloudsmith API host with no code change.

- Add core/api/workspaces.list_domains, which follows Link pagination.
- Rename credential_helpers/custom_domains.py to workspace_domains.py.
- Keep the domain order that the API returns.
- Keep one domain cache for each API host.
- Remove the [domains] config.ini override.
- Change the domains list JSON to version 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cloudsmith-iduffy <178375997+cloudsmith-iduffy@users.noreply.github.com>
Co-authored-by: cloudsmith-iduffy <178375997+cloudsmith-iduffy@users.noreply.github.com>
Co-authored-by: cloudsmith-iduffy <178375997+cloudsmith-iduffy@users.noreply.github.com>
@cloudsmith-iduffy
cloudsmith-iduffy force-pushed the iduffy/v2-workspace-domains branch from 7c12c8c to 5378e7a Compare October 14, 2026 23:07
Base automatically changed from iduffy/shared-v2-client to master October 14, 2026 23:30

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants