Add cloud service query with auto-provisioned read-only key#142
Open
sdairs wants to merge 6 commits into
Open
Conversation
Runs SQL over HTTP via the ClickHouse Cloud Query API — no local clickhouse binary and no service password required. On `cloud service create` we now also create a dedicated read-only API key, bind it to the service's query endpoint with role `sql_console_read_only`, and persist key_id/key_secret/endpoint_id in `.clickhouse/credentials.json` under a new `service_query_keys` map. `cloud service query` looks up the stored key and posts to queries.clickhouse.cloud (overridable via `CLICKHOUSECTL_QUERY_HOST`). Missing-key calls auto-provision lazily; `--no-auto-enable` opts out, and `cloud service create --no-enable-query` skips the create-time hook. SQL precedence is `--query` > `--queries-file` > stdin; default format is PrettyCompact on a TTY, TabSeparated when piped. `cloud service delete` clears the stored key. The credentials struct now treats `api_key`/`api_secret` as optional so OAuth-only users can still hold per-service query keys. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moves the data-plane `run_query` call from clickhousectl into the shared `clickhouse-cloud-api` crate (as `Client::run_query`) so the library integration test can exercise it directly without depending on the CLI binary. The CLI now delegates to it; `RunQueryRequest` and `CloudClient::run_service_query` are removed. Adds a new "Query API Endpoint" phase to the live cloud lifecycle test, sitting between Provision and Stop/Start. The phase creates a read-only API key, upserts the query endpoint with `sql_console_read_only`, runs `SELECT 1` over HTTP, asserts the response, and deletes the key. This is the same flow `cloud service query` uses end-to-end — `cloud service client` is on a deprecation path. Polling tolerates a brief endpoint propagation lag. `CleanupRegistry` gains api-key tracking so leaked keys can't outlive a failed run. The spec-coverage test gets a small allowlist for client methods (currently just `run_query`) that intentionally don't map to an OpenAPI operation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The cloud API enforces `minLength=1` on `ApiKeyPostRequest.roles` even though the field is marked deprecated in the OpenAPI spec. With `roles=[]` and an empty `assignedRoleIds`, key creation fails with `request body.roles array length < minLength`. Set `roles=["query_endpoints"]` — the legacy role intended for keys scoped to Query API endpoint access — in both `service_query:: ensure_service_query_setup` and the integration test's key-create step. The functional read-only constraint still comes from the `sql_console_read_only` role on the query endpoint binding. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three CI integration-test failures shared a single theme — the
OpenAPI spec's description-heuristic marks fields as "required" that
the live API treats as optional, and the credential `keyId` is not
the same value as the API key resource UUID. All three are fixed at
the model level so callers don't have to work around them.
1. `ApiKeyPostRequest.roles` is deprecated but still enforces
`minLength=1`, so the bare `Vec<String>` default serialized as
`"roles":[]` and was rejected. Changed to `Option<Vec<String>>`
with an `OPTIONALITY_EXEMPTIONS` entry; callers using
`assignedRoleIds` now pass `None` and the field is omitted.
2. `ApiKeyPostRequest.hashData` is opt-in (the API generates the key
when omitted, per the spec's response-side description) but the
model required it, yielding `Not a sha256sum: keyIdHash` for the
default value. Changed to `Option<ApiKeyHashData>` with an
exemption; the CLI's `parse_api_key_hash_data` already returned
`Option`, so the `.unwrap_or_default()` workaround is dropped.
3. `openApiKeys` in the endpoint binding and the management endpoints
(GET/DELETE `/keys/{id}`) accept the API key's resource UUID
(`key.id`), not the short credential id (`keyId`) used for query
auth. `ensure_service_query_setup` and the integration test now
send the UUID for binding and cleanup, and keep the credential
pair only for `run_query`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two regressions in the Query API contract would slip past the existing integration coverage: 1. The control plane silently honouring queries against a service with no endpoint binding. Adds `query before endpoint enabled fails` between key creation and the first upsert: `run_query` must return a 4xx (not pinned to a specific status — 401/403/404 all evidence "not enabled"). A 2xx fails the test. 2. A re-upsert with the same body rotating the endpoint resource id or stripping the binding. Captures the first upsert result, then adds `re-upsert query endpoint is idempotent`, which posts the same body again and asserts `endpoint.id`, the `openApiKeys` entry and the `sql_console_read_only` role all survive. A follow-up `SELECT 1 still works after re-upsert` confirms the original credentials remain valid afterwards. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collaborator
Author
Switch the endpoint binding role from sql_console_read_only to sql_console_admin so users can INSERT and run DDL through `cloud service query`. Scoping stays per-service: the binding (not the API key's org roles) is what grants access, so the key still cannot reach any other service in the org. Integration test now CREATEs a table, INSERTs rows, and verifies the SELECT sum to catch a regression if the binding ever silently downgrades to read-only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
iskakaushik
approved these changes
May 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
cloud service query— runs SQL over HTTP via the ClickHouse Cloud Query API, no localclickhousebinary or service password required.cloud service create, auto-provisions a dedicated read-only API key, binds it to the service's query endpoint withsql_console_read_only, and persistskey_id/key_secret/endpoint_idin.clickhouse/credentials.json. Opt out via--no-enable-query; lazy provisioning is on by default forcloud service query(disable with--no-auto-enable).cloud service deleteclears the stored key.run_queryintoclickhouse-cloud-apiso the library's live integration test exercises the full Query API flow (create key → upsert endpoint with read-only role →SELECT 1over HTTP → delete key). Adds api-key tracking toCleanupRegistryso leaks can't outlive a failed run.ApiKeyPostRequest.rolesandApiKeyPostRequest.hashDataasOption<T>withOPTIONALITY_EXEMPTIONSentries — the spec's description-heuristic classifies them as required, but the live API treats them as opt-in (rolesis deprecated withminLength=1;hashDatais opt-in per its own response-side spec note). Resolves three CI failures uncovered by the new integration phase.key.id) for endpoint binding and management calls; the credential id (keyId) is reserved for query-time HTTP auth. This was the third CI failure: management endpoints reject the credential id withInvalid API key id.Test plan
cargo buildworkspacecargo test(all unit + spec-coverage tests pass;field_optionality_matches_specaccepts the two new exemptions and reports no stale entries)cloud_service_crud_lifecycle) — Query API phase now passes end-to-end against ClickHouse Cloudclickhousectl cloud service create→clickhousectl cloud service query --query 'SELECT 1'against a fresh serviceclickhousectl cloud service create --no-enable-query→clickhousectl cloud service querytriggers lazy provisioning;--no-auto-enableshort-circuits with a helpful error🤖 Generated with Claude Code