use crate::cloud::client::CloudClient;
use crate::cloud::credentials::{self, ServiceQueryKey};
use chrono::Utc;
use clickhouse_cloud_api::models::{
ApiKeyPostRequest, ApiKeyPostRequestState,
InstanceServiceQueryApiEndpointsPostRequest, IpAccessListEntry,
};
const QUERY_ENDPOINT_ROLE: &str = "sql_console_admin";
const ALLOWED_ORIGINS: &str = "*";
pub async fn ensure_service_query_setup(
client: &CloudClient,
org_id: &str,
service_id: &str,
service_name: &str,
) -> Result<ServiceQueryKey, Box<dyn std::error::Error>> {
if let Some(existing) = credentials::get_service_query_key(service_id) {
return Ok(existing);
}
let key_request = ApiKeyPostRequest {
name: format!("clickhousectl-query-{service_name}"),
assigned_role_ids: vec![],
expire_at: None,
hash_data: None,
ip_access_list: vec![IpAccessListEntry {
source: "0.0.0.0/0".to_string(),
description: Some(format!(
"clickhousectl auto-provisioned key for service {service_name}"
)),
}],
#[cfg(feature = "deprecated-fields")]
roles: None,
state: ApiKeyPostRequestState::Enabled,
};
let key_response = client.create_api_key(org_id, &key_request).await?;
let key_id = key_response.key_id.clone();
let key_secret = key_response.key_secret.clone();
let api_key_uuid = key_response.key.id.to_string();
let endpoint = match bind_query_endpoint(client, org_id, service_id, &api_key_uuid).await {
Ok(endpoint) => endpoint,
Err(e) => {
let _ = client.delete_api_key(org_id, &api_key_uuid).await;
return Err(e);
}
};
let stored = ServiceQueryKey {
key_id,
key_secret,
endpoint_id: endpoint.id,
service_name: service_name.to_string(),
created_at: Utc::now(),
};
credentials::set_service_query_key(service_id, stored.clone())?;
Ok(stored)
}
async fn bind_query_endpoint(
client: &CloudClient,
org_id: &str,
service_id: &str,
api_key_uuid: &str,
) -> Result<clickhouse_cloud_api::models::ServiceQueryAPIEndpoint, Box<dyn std::error::Error>> {
let mut open_api_keys = match client
.api()
.instance_query_endpoint_get(org_id, service_id)
.await
{
Ok(resp) => resp.result.map(|ep| ep.open_api_keys).unwrap_or_default(),
Err(clickhouse_cloud_api::Error::Api { status: 404, .. }) => Vec::new(),
Err(e) => return Err(client.convert_error(e).into()),
};
if !open_api_keys.iter().any(|k| k == api_key_uuid) {
open_api_keys.push(api_key_uuid.to_string());
}
let endpoint_request = InstanceServiceQueryApiEndpointsPostRequest {
roles: vec![QUERY_ENDPOINT_ROLE.to_string()],
open_api_keys,
allowed_origins: ALLOWED_ORIGINS.to_string(),
};
Ok(client
.create_query_endpoint(org_id, service_id, &endpoint_request)
.await?)
}