Skip to content

fix(cli): route Cloud Postgres handlers through CloudClient wrappers (#1156) - #1165

Open
sdairs wants to merge 3 commits into
fix/latest-version-from-buildsfrom
claude/1156-postgres-cloudclient-wrappers
Open

sdairs wants to merge 3 commits into
fix/latest-version-from-buildsfrom
claude/1156-postgres-cloudclient-wrappers

Conversation

@sdairs

@sdairs sdairs commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Closes #1156.

Summary

  • Postgres handlers now go through CloudClient wrappers. Four handlers reuse wrappers that already existed (list, get, patch, state). Nine new wrappers cover the remaining call sites, and the local unwrap_api is gone. No handler calls client.api() any more.
  • Missing services are reported consistently on by-id commands (the bug fix). postgres update's tag pre-fetch now goes through get_postgres_service, so a missing service gives "No such Postgres service", the resource_not_found JSON code and the list hint. No PATCH is sent.
    • Other by-id writes, and the certs and config get reads, can get a 400 that is a real validation error. On a 400/404 they send one confirming GET of the service, and report not-found only if that GET also rejects the ids. Otherwise the server's own error stands.
    • The extra GET is declared as a conditional permission on each affected command.
  • The service query calls are wrapped too: run_service_query, run_service_query_bearer and configured_query_host in services.rs. They pass the raw library error and the unread response through, so query error classification and telemetry stages are unchanged.
  • Docs: AGENTS.md and add-cli-command/references/cloud.md now name the query wrappers as the one deliberate raw-error exception, replacing the old "postgres.rs exception" note.

Commits

  1. refactor(cli): route handlers through wrappers. No behaviour change.
  2. fix(cli): by-id not-found reporting.
  3. refactor(cli): wrap the service query calls, plus the docs updates.

Tests

New wiremock tests in tests/cli_request_shape_test.rs:

  • update on a missing service, human and --json. No PATCH is sent.
  • update --clear-tags on a missing service: PATCH, then the confirming GET, then not-found.
  • config patch on a deleted service, human and --json.
  • config patch getting a validation 400 while the service exists. The server's message is kept and the code stays http_4xx.

cargo fmt, both clippy configurations, cargo check --no-default-features and cargo test -p clickhousectl (2419 passed) were all run locally.

Not changed

Metrics, logs, slow-queries, prometheus and backup list. Their query parameters make a 400 ambiguous, and they weren't in the issue's scope.

🤖 Generated with Claude Code


Note

Medium Risk
Postgres error classification and the new latest.txt release path affect every cloud postgres by-id command and all update/install flows; changes are heavily wiremock-tested but a bad publish would block version discovery.

Overview
Cloud Postgres (#1156) moves every handler off direct client.api() calls onto CloudClient wrappers (including new ones for create/delete/certs/config/password/replica/restore/state). By-id subresource commands use convert_postgres_by_id_result: on 400/404 they issue a confirming service GET before mapping to “missing Postgres service,” so validation errors (e.g. config patch) keep the server message. Permissions add a CONFIRM_POSTGRES_SERVICE conditional on affected commands. services.rs wraps query calls in run_service_query{,_bearer} / configured_query_host while still returning raw library errors for streaming/classification.

Release & self-update: release.yml adds a publish-latest job that writes monotonic latest.txt to S3 and verifies the CDN; clickhousectl update, install.sh, and release docs now prefer that manifest (GitHub releases/latest redirect as fallback, not the rate-limited REST API).

clickhouse-cloud-api: OpenAPI snapshot adds ClickStack log eventNameExpression / severityNumberExpression, MongoDB table mapping mongoConfig.persistDeletedDocuments, plus tests and README notes. The CLI accepts mongoConfig in --table-mapping-json and documents it.

Agent/release reference docs are updated to match (Postgres as the wrapper pattern, query wrappers as the sole raw-error exception, latest.txt checklist).

Reviewed by Cursor Bugbot for commit 3588448. Bugbot is set up for automated code reviews on this repo. Configure here.

sdairs and others added 3 commits October 30, 2026 16:38
…pers (#1156)

Point postgres list, update (PATCH), restart and the state change at the
existing list/patch/state wrappers, and add CloudClient wrappers for
create, delete, certs get, config get/replace/patch, reset-password,
read-replica create and restore. Every wrapper keeps the error
conversion its handler used, so no output or exit code changes. The
local unwrap_api helper is gone.

The update tag pre-fetch still calls the library directly; it moves onto
get_postgres_service in the follow-up commit, which changes how a missing
service is reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ands (#1156)

postgres update read the service's tags with organization-only error
conversion, so a missing service surfaced the raw API message and the
JSON error had no resource_not_found code. The read now goes through
get_postgres_service, which gives the same "No such Postgres service"
answer and list hint as postgres get and delete.

The other by-id commands (update with --clear-tags, certs get, config
get/replace/patch, reset-password, read-replica create, restore,
restart, promote, switchover) cannot use lookup conversion directly: it
reads any 400 or 404 over well-formed ids as a missing service, but
their paths name a subresource (a 404 may mean it is absent) or their
bodies carry user input (a 400 may be validation). When such a request
is rejected that way, a GET of the service itself now decides: if it
also rejects the ids, the original error is reported as a missing
service; otherwise the original error stands. The GET is declared as a
conditional permission on each command.

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

The service query paths were the last handlers calling client.api()
directly. They now call CloudClient::run_service_query,
run_service_query_bearer and configured_query_host, which pass the
library result through unchanged: the callers stream the response and
classify the raw error (idle, stopped, wake and readiness handling, key
rejections) before convert_query_error records the telemetry failure,
so stages and failure kinds are identical.

AGENTS.md and the add-cli-command Cloud reference now name these two
wrappers as the one deliberate raw-error exception, drop the stale
postgres.rs and unwrap_api note, and say when lookup conversion is
sound and when to confirm the parent with a GET instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sdairs
sdairs force-pushed the claude/1156-postgres-cloudclient-wrappers branch from 2af970f to 3588448 Compare October 30, 2026 17:27
@sdairs
sdairs changed the base branch from main to fix/latest-version-from-builds October 30, 2026 17:27
@sdairs
sdairs added this pull request to stack #1153 October 30, 2026 17:28

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Route Cloud Postgres handlers through CloudClient wrappers

1 participant