Skip to content
Open
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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,10 +362,11 @@ clickhousectl cloud org update <org-id> --name "Renamed Org"
clickhousectl cloud org update <org-id> \
--remove-private-endpoint pe-1,cloud-provider=aws,region=us-east-1 \
--enable-core-dumps false
clickhousectl cloud org prometheus <org-id> --filtered-metrics true
clickhousectl cloud org usage <org-id> \
clickhousectl cloud org prometheus --filtered-metrics true
clickhousectl cloud org usage \
--from-date 2024-01-01 \
--to-date 2024-01-31
# Add --org-id <org-id> to either command when your credentials access multiple organizations.
```

### Services
Expand Down Expand Up @@ -959,4 +960,4 @@ export CONTINUE_ON_NON_BLOCKING_FAILURES=1
## Requirements

- macOS (aarch64, x86_64) or Linux (aarch64, x86_64)
- Cloud commands require a [ClickHouse Cloud API key](https://clickhouse.com/docs/en/cloud/manage/api)
- Cloud commands require a [ClickHouse Cloud API key](https://clickhouse.com/docs/en/cloud/manage/api)
168 changes: 155 additions & 13 deletions crates/clickhousectl/src/cloud/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,13 @@ CONTEXT FOR AGENTS:

/// Get organization Prometheus configuration
Prometheus {
/// Organization ID
org_id: String,
/// Organization ID (auto-detected if not specified)
#[arg(long)]
org_id: Option<String>,

Comment thread
sdairs marked this conversation as resolved.
/// Organization ID (deprecated positional form; use --org-id)
#[arg(value_name = "ORG_ID", hide = true, conflicts_with = "org_id")]
legacy_org_id: Option<String>,

/// Whether to request filtered metrics
#[arg(long)]
Expand All @@ -425,8 +430,13 @@ CONTEXT FOR AGENTS:

/// Get organization usage/billing information
Usage {
/// Organization ID
org_id: String,
/// Organization ID (auto-detected if not specified)
#[arg(long)]
org_id: Option<String>,

/// Organization ID (deprecated positional form; use --org-id)
#[arg(value_name = "ORG_ID", hide = true, conflicts_with = "org_id")]
legacy_org_id: Option<String>,

/// Start date filter in UTC (YYYY-MM-DD, e.g. 2024-01-01)
#[arg(long, value_parser = parse_date_only)]
Expand Down Expand Up @@ -2801,7 +2811,6 @@ mod tests {
"cloud",
"org",
"usage",
"org-1",
"--from-date",
"2025-01-01",
"--to-date",
Expand All @@ -2816,23 +2825,161 @@ mod tests {
panic!("expected org command");
};
let OrgCommands::Usage {
from_date, to_date, ..
org_id,
legacy_org_id,
from_date,
to_date,
..
} = command
else {
panic!("expected org usage");
};
assert_eq!(org_id, None);
assert_eq!(legacy_org_id, None);
assert_eq!(from_date, "2025-01-01");
assert_eq!(to_date, "2025-01-31");
}

#[test]
fn parses_org_prometheus_and_usage_org_id_flags() {
let prometheus = Cli::try_parse_from([
"clickhousectl",
"cloud",
"org",
"prometheus",
"--org-id",
"org-1",
])
.unwrap();
let Commands::Cloud(args) = prometheus.command else {
panic!("expected cloud command");
};
let CloudCommands::Org { command } = args.command else {
panic!("expected org command");
};
let OrgCommands::Prometheus { org_id, .. } = command else {
panic!("expected org prometheus");
};
assert_eq!(org_id.as_deref(), Some("org-1"));

let usage = Cli::try_parse_from([
"clickhousectl",
"cloud",
"org",
"usage",
"--org-id",
"org-1",
"--from-date",
"2025-01-01",
"--to-date",
"2025-01-31",
])
.unwrap();
let Commands::Cloud(args) = usage.command else {
panic!("expected cloud command");
};
let CloudCommands::Org { command } = args.command else {
panic!("expected org command");
};
let OrgCommands::Usage { org_id, .. } = command else {
panic!("expected org usage");
};
assert_eq!(org_id.as_deref(), Some("org-1"));
}

#[test]
fn parses_legacy_org_id_positionals() {
let prometheus =
Cli::try_parse_from(["clickhousectl", "cloud", "org", "prometheus", "org-1"]).unwrap();
let Commands::Cloud(args) = prometheus.command else {
panic!("expected cloud command");
};
let CloudCommands::Org { command } = args.command else {
panic!("expected org command");
};
let OrgCommands::Prometheus {
org_id,
legacy_org_id,
..
} = command
else {
panic!("expected org prometheus");
};
assert_eq!(org_id, None);
assert_eq!(legacy_org_id.as_deref(), Some("org-1"));

let usage = Cli::try_parse_from([
"clickhousectl",
"cloud",
"org",
"usage",
"org-1",
"--from-date",
"2025-01-01",
"--to-date",
"2025-01-31",
])
.unwrap();
let Commands::Cloud(args) = usage.command else {
panic!("expected cloud command");
};
let CloudCommands::Org { command } = args.command else {
panic!("expected org command");
};
let OrgCommands::Usage {
org_id,
legacy_org_id,
..
} = command
else {
panic!("expected org usage");
};
assert_eq!(org_id, None);
assert_eq!(legacy_org_id.as_deref(), Some("org-1"));
}

#[test]
fn rejects_org_id_flag_with_legacy_positional() {
let prometheus = Cli::try_parse_from([
"clickhousectl",
"cloud",
"org",
"prometheus",
"org-1",
"--org-id",
"org-2",
]);
match prometheus {
Ok(_) => panic!("expected conflicting org IDs to be rejected"),
Err(err) => assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict),
}

let usage = Cli::try_parse_from([
"clickhousectl",
"cloud",
"org",
"usage",
"org-1",
"--org-id",
"org-2",
"--from-date",
"2025-01-01",
"--to-date",
"2025-01-31",
]);
match usage {
Ok(_) => panic!("expected conflicting org IDs to be rejected"),
Err(err) => assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict),
}
}

#[test]
fn rejects_org_usage_timestamps() {
let result = Cli::try_parse_from([
"clickhousectl",
"cloud",
"org",
"usage",
"org-1",
"--from-date",
"2025-01-01T00:00:00Z",
"--to-date",
Expand All @@ -2852,7 +2999,6 @@ mod tests {
"cloud",
"org",
"usage",
"org-1",
"--from-date",
"2025-02-31",
"--to-date",
Expand Down Expand Up @@ -2952,17 +3098,13 @@ mod tests {
// Org reads
assert_write(&["clickhousectl", "cloud", "org", "list"], false);
assert_write(&["clickhousectl", "cloud", "org", "get", "org-1"], false);
assert_write(
&["clickhousectl", "cloud", "org", "prometheus", "org-1"],
false,
);
assert_write(&["clickhousectl", "cloud", "org", "prometheus"], false);
assert_write(
&[
"clickhousectl",
"cloud",
"org",
"usage",
"org-1",
"--from-date",
"2025-01-01",
"--to-date",
Expand Down
10 changes: 6 additions & 4 deletions crates/clickhousectl/src/cloud/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2641,11 +2641,12 @@ pub async fn org_update(

pub async fn org_prometheus(
client: &CloudClient,
org_id: &str,
org_id: Option<&str>,
filtered_metrics: Option<bool>,
_json: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let prom = client.get_org_prometheus(org_id, filtered_metrics).await?;
let org_id = resolve_org_id(client, org_id).await?;
let prom = client.get_org_prometheus(&org_id, filtered_metrics).await?;
println!("{}", prom);
Ok(())
}
Expand All @@ -2666,14 +2667,15 @@ pub async fn service_prometheus(

pub async fn org_usage(
client: &CloudClient,
org_id: &str,
org_id: Option<&str>,
from_date: &str,
to_date: &str,
filters: &[String],
json: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let org_id = resolve_org_id(client, org_id).await?;
let usage = client
.get_org_usage(org_id, from_date, to_date, filters)
.get_org_usage(&org_id, from_date, to_date, filters)
.await?;

if json {
Expand Down
10 changes: 8 additions & 2 deletions crates/clickhousectl/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,15 +532,21 @@ async fn run_cloud(args: CloudArgs) -> Result<()> {
}
OrgCommands::Prometheus {
org_id,
legacy_org_id,
filtered_metrics,
} => cloud::commands::org_prometheus(&client, &org_id, filtered_metrics, json).await,
} => {
let org_id = org_id.as_deref().or(legacy_org_id.as_deref());
cloud::commands::org_prometheus(&client, org_id, filtered_metrics, json).await
}
OrgCommands::Usage {
org_id,
legacy_org_id,
from_date,
to_date,
filter,
} => {
cloud::commands::org_usage(&client, &org_id, &from_date, &to_date, &filter, json)
let org_id = org_id.as_deref().or(legacy_org_id.as_deref());
cloud::commands::org_usage(&client, org_id, &from_date, &to_date, &filter, json)
.await
}
},
Expand Down
Loading