Skip to content

refactor(no-ticket): share one v2 API client - #478

Merged
cloudsmith-iduffy merged 5 commits into
masterfrom
iduffy/shared-v2-client
Oct 14, 2026
Merged

cloudsmith-iduffy merged 5 commits into
masterfrom
iduffy/shared-v2-client

Conversation

@cloudsmith-iduffy

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

Copy link
Copy Markdown
Collaborator

Description

The CLI calls a few v2 API endpoints that the generated cloudsmith_api SDK does not cover. The HTTP code for those calls lived as private helpers in core/api/metadata.py. This PR moves it to core/api/v2.py, so the next v2 endpoint can use the same code without copying it. The metadata functions now call v2.get_client(), v2.request() and v2.response_json().

The v2 client now authenticates with the credential that resolve_credentials gives the command (opts.credential), through CredentialResult.auth_headers(). Before, it read the API key and the SSO Authorization header back from the SDK Configuration, and copied the precedence logic from initialise_api. An API key goes in X-Api-Key. A SAML access token goes in Authorization: Bearer. Each metadata function takes a credential keyword argument, and the metadata and push commands pass opts.credential. The host, proxy, SSL verification, user agent, --api-headers and retry settings still come from the Configuration that initialise_api sets.

The client also gets these changes:

  • request_url() sends a request to an absolute URL, such as a Link: rel="next" pagination link. It refuses a URL on another scheme or host, so the API key or SSO token goes only to the configured API host.
  • A redirect to another host does not get the API key. Requests already drops the Authorization header in that case, but it sends X-Api-Key on. create_requests_session() now builds an ApiKeySafeSession that drops that header under the same rule. The v1 SDK client, uploads, downloads and the v2 client all use it.
  • Every v2 request attempt now has a 30-second timeout. Before, a v2 API that did not respond made the metadata commands wait without a limit. The CLI retries an attempt that times out, as --error-retry-max sets. With the default of 6 retries and the backoff, a stalled API holds a command for some minutes.
  • A POST gets only the 30-second connect timeout, not a read timeout. The shared retry setup retries after a read timeout for every method, so a slow metadata add that the server accepts would be sent again and create a second entry.

The v1 SDK client does not get the timeout in this PR. Its calls need their own timeout values, so I will fix that separately.

When --api-headers sets an Authorization header and an API key also resolves, the v2 client now sends both headers. The v1 SDK client already does this.

Type of Change

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

Additional Notes

core/tests/test_v2.py covers the API key, the SAML access token, no credential, the origin check, the redirect for both credential types, the timeout, the user agent and extra headers, and the proxy and SSL verification settings. core/tests/test_rest.py covers the redirect rule for the shared session.

I tested metadata list against staging with the CLI's credential chain:

  • CLOUDSMITH_NO_KEYRING=1 CLOUDSMITH_API_KEY=<service key> cloudsmith metadata list <workspace>/<repo>/<package> --api-host <staging host> returns 200.
  • With a SAML session in the keyring and no API key, the same call returns 200 with Authorization: Bearer.
  • With no credential, the v2 endpoint returns 401.

🤖 Generated with Claude Code

@cloudsmith-iduffy
cloudsmith-iduffy requested a review from a team as a code owner October 12, 2026 20:59
Copilot AI balanced review requested due to automatic review settings October 12, 2026 20:59
@cloudsmith-iduffy
cloudsmith-iduffy added this pull request to stack #479 October 12, 2026 20:59

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

Redirects can expose API keys, and retries prevent the advertised 30-second overall timeout.

3 open findings
What changed in this PR

Introduces a shared client for unsupported v2 API endpoints and migrates metadata requests to it.

Changes:

  • Adds shared v2 request, authentication, URL validation, and JSON helpers.
  • Applies request timeouts and adds focused tests.
  • Refactors metadata operations to use the shared client.
File Description
cloudsmith_cli/​core/​api/​v2.py Adds the shared v2 API client.
cloudsmith_cli/​core/​api/​metadata.py Delegates metadata requests to the shared client.
cloudsmith_cli/​core/​tests/​test_v2.py Tests origin validation and timeout propagation.
CHANGELOG.md Documents timeout behavior.

🧠 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 thread cloudsmith_cli/core/api/v2.py Outdated
Comment thread cloudsmith_cli/core/api/v2.py

@tigh-latte tigh-latte left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good! one non blocking q

Comment thread cloudsmith_cli/core/api/v2.py Outdated
cloudsmith-iduffy and others added 5 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>
@cloudsmith-iduffy
cloudsmith-iduffy added this pull request to the merge queue Oct 14, 2026
Any commits made after this event will not be merged.
@cloudsmith-iduffy
cloudsmith-iduffy merged commit 71a7b5b into master Oct 14, 2026
34 checks passed
@cloudsmith-iduffy
cloudsmith-iduffy deleted the iduffy/shared-v2-client branch October 14, 2026 23:30
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.

3 participants