Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,8 @@ Use `clickhousectl cloud postgres create --help` for the complete option list. S

`postgres list --filter KEY=VALUE` is applied client-side to the listing and is repeatable; every filter must match. Supported keys are `state`, `region`, `name`, `provider` and `isPrimary` (the `Primary` column; `true`/`false`, or the `yes`/`no` the column shows). Keys are case-insensitive, `state` and `provider` match the wire value case-insensitively, and `region`/`name` match exactly. An unknown key, a missing `=` or an empty value is a usage error (exit 2) listing the valid keys — it never returns an unfiltered list. A field the API omitted matches no filter value, so filtering on it excludes that service. This is unrelated to `cloud service list --filter`, which sends server-side resource-tag filters (`tag:env=production`) to the API.

The human-readable list includes each service's cloud provider; an omitted provider is shown as `-`. JSON output keeps the API response shape.

`postgres promote` and `postgres switchover` change which service is primary. Both are issued as-is, and the API acknowledges them before (or without) applying them, so exit 0 on its own means accepted, not applied:

- `--wait` (optionally `--wait-timeout SECONDS`, default 300) is how you confirm the roles actually changed. It polls the target every 5s until it reports the expected `isPrimary` — `true` for `promote`, the opposite of the value read just before the command for `switchover` — and exits 1 with the last observed role if it never does. stdout then carries the polled state rather than the state-change response, which for `promote` omits `isPrimary` entirely. Without `--wait` neither command reads the service. A `switchover --wait` whose pre-command read omits `isPrimary` is refused before the command is issued, because there is no prior role to compare a swap against.
Expand Down
3 changes: 3 additions & 0 deletions crates/clickhousectl/src/cloud/postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1383,6 +1383,8 @@ pub async fn postgres_list(
id: String,
#[tabled(rename = "State")]
state: String,
#[tabled(rename = "Provider")]
provider: String,
#[tabled(rename = "Region")]
region: String,
#[tabled(rename = "Size")]
Expand All @@ -1401,6 +1403,7 @@ pub async fn postgres_list(
name: or_absent(i.name.as_deref()),
id: or_absent(i.id.as_ref()),
state: state_label(i.state.as_ref()),
provider: enum_label(i.provider.as_ref()),
region: or_absent(i.region.as_deref()),
size: enum_label(i.size.as_ref()),
pg: enum_label(i.postgres_version.as_ref()),
Expand Down
63 changes: 63 additions & 0 deletions crates/clickhousectl/tests/cli_request_shape_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4335,6 +4335,69 @@ async fn postgres_list_applies_supported_filters_client_side() {
assert_eq!(names("name=nope"), Vec::<String>::new());
}

#[tokio::test]
async fn postgres_list_shows_provider_in_human_output_and_preserves_json() {
let mock = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/v1/organizations/org-1/postgres"))
.respond_with(postgres_list_response())
.expect(2)
.mount(&mock)
.await;

let human =
invoke_cli_with_cloud_credentials_human(&mock, &["postgres", "list", "--org-id", "org-1"]);
assert_success(&human);
let human = String::from_utf8(human.stdout).expect("human output should be UTF-8");
let row_cells = |name: &str| -> Vec<&str> {
human
.lines()
.find(|line| line.contains(name))
.unwrap_or_else(|| panic!("missing row for {name}:\n{human}"))
.split('|')
.map(str::trim)
.filter(|cell| !cell.is_empty())
.collect()
};
assert_eq!(
row_cells("Name"),
[
"Name", "ID", "State", "Provider", "Region", "Size", "PG", "HA", "Primary"
]
);
assert_eq!(row_cells("primary-pg")[3], "aws");
assert_eq!(row_cells("unknown-pg")[3], "-");

let json = invoke_cli_with_cloud_credentials(&mock, &["postgres", "list", "--org-id", "org-1"]);
assert_success(&json);
assert_eq!(
serde_json::from_slice::<Value>(&json.stdout).expect("JSON output should be valid"),
serde_json::json!([
{
"id": "11111111-2222-3333-4444-555555555555",
"name": "primary-pg",
"state": "running",
"region": "us-east-1",
"provider": "aws",
"isPrimary": true,
},
{
"id": "66666666-7777-8888-9999-000000000000",
"name": "replica-pg",
"state": "restoring_backup",
"region": "us-east-1",
"provider": "aws",
"isPrimary": false,
},
{
"id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"name": "unknown-pg",
"region": "us-east-1",
},
])
);
}

// ── Postgres promote / switchover role changes (issue #604) ───────────────

const ROLE_TEST_POSTGRES_ID: &str = "11111111-2222-3333-4444-555555555555";
Expand Down