diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 5d25bb4ad..2e1f7bb62 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -16,3 +16,6 @@ e7fcdb1a8f1e48c564717f1ccd0fa654d182aa73 # Move the Query API out of client.rs into client/query.rs (#1173) 8db8f19a0b69ff505c33188414efb85dc50dfd0e + +# Split client/organizations.rs into byoc, invitations, members and rbac (#1173) +9e20dd888a937f5f0b610b22a71aef4ff70b9357 diff --git a/crates/clickhouse-cloud-api/src/client.rs b/crates/clickhouse-cloud-api/src/client.rs index ca6708371..208877278 100644 --- a/crates/clickhouse-cloud-api/src/client.rs +++ b/crates/clickhouse-cloud-api/src/client.rs @@ -5,12 +5,16 @@ use crate::error::Error; mod activity; mod api_keys; mod backups; +mod byoc; mod clickpipes; mod clickstack; +mod invitations; +mod members; mod organizations; mod postgres; mod query; mod query_api_endpoints; +mod rbac; mod saved_queries; mod services; mod udfs; diff --git a/crates/clickhouse-cloud-api/src/client/byoc.rs b/crates/clickhouse-cloud-api/src/client/byoc.rs new file mode 100644 index 000000000..691d1696e --- /dev/null +++ b/crates/clickhouse-cloud-api/src/client/byoc.rs @@ -0,0 +1,212 @@ +use super::Client; +use crate::error::Error; +use crate::models::*; + +impl Client { + /// Create BYOC Infrastructure + pub async fn organization_byoc_infrastructure_create( + &self, + organization_id: &str, + body: &ByocInfrastructurePostRequest, + ) -> Result, Error> { + let path = format!("/v1/organizations/{organization_id}/byocInfrastructure"); + let mut req = self.request(reqwest::Method::POST, &path); + req = req.json(body); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Validate BYOC infrastructure prerequisites + /// + /// Preflight-checks the cloud account's permissions without creating infrastructure. + pub async fn organization_byoc_infrastructure_validate( + &self, + organization_id: &str, + body: &ByocInfrastructureValidatePostRequest, + ) -> Result, Error> { + let path = format!("/v1/organizations/{organization_id}/byocInfrastructure/validate"); + let mut req = self.request(reqwest::Method::POST, &path); + req = req.json(body); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Get BYOC infrastructure details + pub async fn organization_byoc_infrastructure_get( + &self, + organization_id: &str, + byoc_infrastructure_id: &str, + ) -> Result, Error> { + let path = format!( + "/v1/organizations/{organization_id}/byocInfrastructure/{byoc_infrastructure_id}" + ); + let req = self.request(reqwest::Method::GET, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Get BYOC infrastructure provisioning progress + pub async fn organization_byoc_infrastructure_progress_get( + &self, + organization_id: &str, + byoc_infrastructure_id: &str, + ) -> Result, Error> { + let path = format!( + "/v1/organizations/{organization_id}/byocInfrastructure/{byoc_infrastructure_id}/progress" + ); + let req = self.request(reqwest::Method::GET, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Get BYOC infrastructure private endpoint configuration + /// + /// For BYOC organizations this replaces the deprecated organization-level + /// `organization_private_endpoint_config_get_list`. + pub async fn organization_byoc_infrastructure_private_endpoint_config_get( + &self, + organization_id: &str, + byoc_infrastructure_id: &str, + ) -> Result, Error> { + let path = format!( + "/v1/organizations/{organization_id}/byocInfrastructure/{byoc_infrastructure_id}/privateEndpointConfig" + ); + let req = self.request(reqwest::Method::GET, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Get BYOC infrastructure tags + /// + /// Tags are replaced as a whole through `organization_byoc_infrastructure_update`. + pub async fn organization_byoc_infrastructure_tags_get( + &self, + organization_id: &str, + byoc_infrastructure_id: &str, + ) -> Result, Error> { + let path = format!( + "/v1/organizations/{organization_id}/byocInfrastructure/{byoc_infrastructure_id}/tags" + ); + let req = self.request(reqwest::Method::GET, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Remove a BYOC infrastructure + pub async fn organization_byoc_infrastructure_delete( + &self, + organization_id: &str, + byoc_infrastructure_id: &str, + ) -> Result, Error> { + let path = format!( + "/v1/organizations/{organization_id}/byocInfrastructure/{byoc_infrastructure_id}" + ); + let req = self.request(reqwest::Method::DELETE, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Update BYOC Infrastructure + pub async fn organization_byoc_infrastructure_update( + &self, + organization_id: &str, + byoc_infrastructure_id: &str, + body: &ByocInfrastructurePatchRequest, + ) -> Result, Error> { + let path = format!( + "/v1/organizations/{organization_id}/byocInfrastructure/{byoc_infrastructure_id}" + ); + let mut req = self.request(reqwest::Method::PATCH, &path); + req = req.json(body); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } +} diff --git a/crates/clickhouse-cloud-api/src/client/invitations.rs b/crates/clickhouse-cloud-api/src/client/invitations.rs new file mode 100644 index 000000000..79f7732c1 --- /dev/null +++ b/crates/clickhouse-cloud-api/src/client/invitations.rs @@ -0,0 +1,97 @@ +use super::Client; +use crate::error::Error; +use crate::models::*; + +impl Client { + /// List all invitations + pub async fn invitation_get_list( + &self, + organization_id: &str, + ) -> Result>, Error> { + let path = format!("/v1/organizations/{organization_id}/invitations"); + let req = self.request(reqwest::Method::GET, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Create an invitation + pub async fn invitation_create( + &self, + organization_id: &str, + body: &InvitationPostRequest, + ) -> Result, Error> { + let path = format!("/v1/organizations/{organization_id}/invitations"); + let mut req = self.request(reqwest::Method::POST, &path); + req = req.json(body); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Get invitation details + pub async fn invitation_get( + &self, + organization_id: &str, + invitation_id: &str, + ) -> Result, Error> { + let path = format!("/v1/organizations/{organization_id}/invitations/{invitation_id}"); + let req = self.request(reqwest::Method::GET, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Delete organization invitation + pub async fn invitation_delete( + &self, + organization_id: &str, + invitation_id: &str, + ) -> Result, Error> { + let path = format!("/v1/organizations/{organization_id}/invitations/{invitation_id}"); + let req = self.request(reqwest::Method::DELETE, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } +} diff --git a/crates/clickhouse-cloud-api/src/client/members.rs b/crates/clickhouse-cloud-api/src/client/members.rs new file mode 100644 index 000000000..20f9120ff --- /dev/null +++ b/crates/clickhouse-cloud-api/src/client/members.rs @@ -0,0 +1,98 @@ +use super::Client; +use crate::error::Error; +use crate::models::*; + +impl Client { + /// List organization members + pub async fn member_get_list( + &self, + organization_id: &str, + ) -> Result>, Error> { + let path = format!("/v1/organizations/{organization_id}/members"); + let req = self.request(reqwest::Method::GET, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Get member details + pub async fn member_get( + &self, + organization_id: &str, + user_id: &str, + ) -> Result, Error> { + let path = format!("/v1/organizations/{organization_id}/members/{user_id}"); + let req = self.request(reqwest::Method::GET, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Update organization member + pub async fn member_update( + &self, + organization_id: &str, + user_id: &str, + body: &MemberPatchRequest, + ) -> Result, Error> { + let path = format!("/v1/organizations/{organization_id}/members/{user_id}"); + let mut req = self.request(reqwest::Method::PATCH, &path); + req = req.json(body); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Remove an organization member + pub async fn member_delete( + &self, + organization_id: &str, + user_id: &str, + ) -> Result, Error> { + let path = format!("/v1/organizations/{organization_id}/members/{user_id}"); + let req = self.request(reqwest::Method::DELETE, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } +} diff --git a/crates/clickhouse-cloud-api/src/client/organizations.rs b/crates/clickhouse-cloud-api/src/client/organizations.rs index 2405a55b1..4199af53d 100644 --- a/crates/clickhouse-cloud-api/src/client/organizations.rs +++ b/crates/clickhouse-cloud-api/src/client/organizations.rs @@ -178,106 +178,6 @@ impl Client { Ok(serde_json::from_str(&body_text)?) } - /// Create BYOC Infrastructure - pub async fn organization_byoc_infrastructure_create( - &self, - organization_id: &str, - body: &ByocInfrastructurePostRequest, - ) -> Result, Error> { - let path = format!("/v1/organizations/{organization_id}/byocInfrastructure"); - let mut req = self.request(reqwest::Method::POST, &path); - req = req.json(body); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Validate BYOC infrastructure prerequisites - /// - /// Preflight-checks the cloud account's permissions without creating infrastructure. - pub async fn organization_byoc_infrastructure_validate( - &self, - organization_id: &str, - body: &ByocInfrastructureValidatePostRequest, - ) -> Result, Error> { - let path = format!("/v1/organizations/{organization_id}/byocInfrastructure/validate"); - let mut req = self.request(reqwest::Method::POST, &path); - req = req.json(body); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Get BYOC infrastructure details - pub async fn organization_byoc_infrastructure_get( - &self, - organization_id: &str, - byoc_infrastructure_id: &str, - ) -> Result, Error> { - let path = format!( - "/v1/organizations/{organization_id}/byocInfrastructure/{byoc_infrastructure_id}" - ); - let req = self.request(reqwest::Method::GET, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Get BYOC infrastructure provisioning progress - pub async fn organization_byoc_infrastructure_progress_get( - &self, - organization_id: &str, - byoc_infrastructure_id: &str, - ) -> Result, Error> { - let path = format!( - "/v1/organizations/{organization_id}/byocInfrastructure/{byoc_infrastructure_id}/progress" - ); - let req = self.request(reqwest::Method::GET, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - /// Get one page of organization notifications, most recent first (Beta). /// /// Pass `next_cursor` unchanged as `cursor` until it is absent. The server @@ -362,415 +262,6 @@ impl Client { Ok(serde_json::from_str(&body_text)?) } - /// Get BYOC infrastructure private endpoint configuration - /// - /// For BYOC organizations this replaces the deprecated organization-level - /// `organization_private_endpoint_config_get_list`. - pub async fn organization_byoc_infrastructure_private_endpoint_config_get( - &self, - organization_id: &str, - byoc_infrastructure_id: &str, - ) -> Result, Error> { - let path = format!( - "/v1/organizations/{organization_id}/byocInfrastructure/{byoc_infrastructure_id}/privateEndpointConfig" - ); - let req = self.request(reqwest::Method::GET, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Get BYOC infrastructure tags - /// - /// Tags are replaced as a whole through `organization_byoc_infrastructure_update`. - pub async fn organization_byoc_infrastructure_tags_get( - &self, - organization_id: &str, - byoc_infrastructure_id: &str, - ) -> Result, Error> { - let path = format!( - "/v1/organizations/{organization_id}/byocInfrastructure/{byoc_infrastructure_id}/tags" - ); - let req = self.request(reqwest::Method::GET, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Remove a BYOC infrastructure - pub async fn organization_byoc_infrastructure_delete( - &self, - organization_id: &str, - byoc_infrastructure_id: &str, - ) -> Result, Error> { - let path = format!( - "/v1/organizations/{organization_id}/byocInfrastructure/{byoc_infrastructure_id}" - ); - let req = self.request(reqwest::Method::DELETE, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Update BYOC Infrastructure - pub async fn organization_byoc_infrastructure_update( - &self, - organization_id: &str, - byoc_infrastructure_id: &str, - body: &ByocInfrastructurePatchRequest, - ) -> Result, Error> { - let path = format!( - "/v1/organizations/{organization_id}/byocInfrastructure/{byoc_infrastructure_id}" - ); - let mut req = self.request(reqwest::Method::PATCH, &path); - req = req.json(body); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// List all invitations - pub async fn invitation_get_list( - &self, - organization_id: &str, - ) -> Result>, Error> { - let path = format!("/v1/organizations/{organization_id}/invitations"); - let req = self.request(reqwest::Method::GET, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Create an invitation - pub async fn invitation_create( - &self, - organization_id: &str, - body: &InvitationPostRequest, - ) -> Result, Error> { - let path = format!("/v1/organizations/{organization_id}/invitations"); - let mut req = self.request(reqwest::Method::POST, &path); - req = req.json(body); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Get invitation details - pub async fn invitation_get( - &self, - organization_id: &str, - invitation_id: &str, - ) -> Result, Error> { - let path = format!("/v1/organizations/{organization_id}/invitations/{invitation_id}"); - let req = self.request(reqwest::Method::GET, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Delete organization invitation - pub async fn invitation_delete( - &self, - organization_id: &str, - invitation_id: &str, - ) -> Result, Error> { - let path = format!("/v1/organizations/{organization_id}/invitations/{invitation_id}"); - let req = self.request(reqwest::Method::DELETE, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// List organization members - pub async fn member_get_list( - &self, - organization_id: &str, - ) -> Result>, Error> { - let path = format!("/v1/organizations/{organization_id}/members"); - let req = self.request(reqwest::Method::GET, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Get member details - pub async fn member_get( - &self, - organization_id: &str, - user_id: &str, - ) -> Result, Error> { - let path = format!("/v1/organizations/{organization_id}/members/{user_id}"); - let req = self.request(reqwest::Method::GET, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Update organization member - pub async fn member_update( - &self, - organization_id: &str, - user_id: &str, - body: &MemberPatchRequest, - ) -> Result, Error> { - let path = format!("/v1/organizations/{organization_id}/members/{user_id}"); - let mut req = self.request(reqwest::Method::PATCH, &path); - req = req.json(body); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Remove an organization member - pub async fn member_delete( - &self, - organization_id: &str, - user_id: &str, - ) -> Result, Error> { - let path = format!("/v1/organizations/{organization_id}/members/{user_id}"); - let req = self.request(reqwest::Method::DELETE, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// List all available roles for an organization - pub async fn organization_roles_get_list( - &self, - organization_id: &str, - ) -> Result>, Error> { - let path = format!("/v1/organizations/{organization_id}/roles"); - let req = self.request(reqwest::Method::GET, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Create a new role - pub async fn organization_role_post( - &self, - organization_id: &str, - body: &RoleCreateRequest, - ) -> Result, Error> { - let path = format!("/v1/organizations/{organization_id}/roles"); - let mut req = self.request(reqwest::Method::POST, &path); - req = req.json(body); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Get role details - pub async fn organization_role_get( - &self, - organization_id: &str, - role_id: &str, - ) -> Result, Error> { - let path = format!("/v1/organizations/{organization_id}/roles/{role_id}"); - let req = self.request(reqwest::Method::GET, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Update a role - pub async fn organization_role_patch( - &self, - organization_id: &str, - role_id: &str, - body: &RoleUpdateRequest, - ) -> Result, Error> { - let path = format!("/v1/organizations/{organization_id}/roles/{role_id}"); - let mut req = self.request(reqwest::Method::PATCH, &path); - req = req.json(body); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - - /// Delete a role - pub async fn organization_role_delete( - &self, - organization_id: &str, - role_id: &str, - ) -> Result, Error> { - let path = format!("/v1/organizations/{organization_id}/roles/{role_id}"); - let req = self.request(reqwest::Method::DELETE, &path); - let resp = req.send().await?; - let status = resp.status(); - let body_text = resp.text().await?; - if !status.is_success() { - return Err(Error::Api { - status: status.as_u16(), - message: serde_json::from_str::>(&body_text) - .ok() - .and_then(|r| r.error) - .unwrap_or(body_text.clone()), - }); - } - Ok(serde_json::from_str(&body_text)?) - } - /// Get private endpoint configuration for region within cloud provider for an organization #[deprecated] #[allow(deprecated)] diff --git a/crates/clickhouse-cloud-api/src/client/rbac.rs b/crates/clickhouse-cloud-api/src/client/rbac.rs new file mode 100644 index 000000000..396efe4cd --- /dev/null +++ b/crates/clickhouse-cloud-api/src/client/rbac.rs @@ -0,0 +1,122 @@ +use super::Client; +use crate::error::Error; +use crate::models::*; + +impl Client { + /// List all available roles for an organization + pub async fn organization_roles_get_list( + &self, + organization_id: &str, + ) -> Result>, Error> { + let path = format!("/v1/organizations/{organization_id}/roles"); + let req = self.request(reqwest::Method::GET, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Create a new role + pub async fn organization_role_post( + &self, + organization_id: &str, + body: &RoleCreateRequest, + ) -> Result, Error> { + let path = format!("/v1/organizations/{organization_id}/roles"); + let mut req = self.request(reqwest::Method::POST, &path); + req = req.json(body); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Get role details + pub async fn organization_role_get( + &self, + organization_id: &str, + role_id: &str, + ) -> Result, Error> { + let path = format!("/v1/organizations/{organization_id}/roles/{role_id}"); + let req = self.request(reqwest::Method::GET, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Update a role + pub async fn organization_role_patch( + &self, + organization_id: &str, + role_id: &str, + body: &RoleUpdateRequest, + ) -> Result, Error> { + let path = format!("/v1/organizations/{organization_id}/roles/{role_id}"); + let mut req = self.request(reqwest::Method::PATCH, &path); + req = req.json(body); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } + + /// Delete a role + pub async fn organization_role_delete( + &self, + organization_id: &str, + role_id: &str, + ) -> Result, Error> { + let path = format!("/v1/organizations/{organization_id}/roles/{role_id}"); + let req = self.request(reqwest::Method::DELETE, &path); + let resp = req.send().await?; + let status = resp.status(); + let body_text = resp.text().await?; + if !status.is_success() { + return Err(Error::Api { + status: status.as_u16(), + message: serde_json::from_str::>(&body_text) + .ok() + .and_then(|r| r.error) + .unwrap_or(body_text.clone()), + }); + } + Ok(serde_json::from_str(&body_text)?) + } +} diff --git a/crates/clickhouse-cloud-api/src/convert.rs b/crates/clickhouse-cloud-api/src/convert.rs index 2f2a00594..94d8e7d7e 100644 --- a/crates/clickhouse-cloud-api/src/convert.rs +++ b/crates/clickhouse-cloud-api/src/convert.rs @@ -22,7 +22,7 @@ use std::fmt; mod clickpipes; mod clickstack; mod postgres; -mod service; +mod services; mod shared; /// The response omitted fields that the matching request model requires. diff --git a/crates/clickhouse-cloud-api/src/convert/service.rs b/crates/clickhouse-cloud-api/src/convert/services.rs similarity index 100% rename from crates/clickhouse-cloud-api/src/convert/service.rs rename to crates/clickhouse-cloud-api/src/convert/services.rs diff --git a/scripts/classify-cloud-integration.py b/scripts/classify-cloud-integration.py index daf17b75d..c28f4806f 100644 --- a/scripts/classify-cloud-integration.py +++ b/scripts/classify-cloud-integration.py @@ -27,10 +27,13 @@ {"service", "organization"} ), "crates/clickhouse-cloud-api/src/client/backups.rs": frozenset({"service"}), + "crates/clickhouse-cloud-api/src/client/byoc.rs": frozenset({"organization"}), "crates/clickhouse-cloud-api/src/client/clickpipes.rs": frozenset( {"clickpipes"} ), "crates/clickhouse-cloud-api/src/client/clickstack.rs": NO_SUITES, + "crates/clickhouse-cloud-api/src/client/invitations.rs": frozenset({"organization"}), + "crates/clickhouse-cloud-api/src/client/members.rs": frozenset({"organization"}), "crates/clickhouse-cloud-api/src/client/organizations.rs": frozenset( {"service", "organization"} ), @@ -39,6 +42,7 @@ ), "crates/clickhouse-cloud-api/src/client/query.rs": frozenset({"service"}), "crates/clickhouse-cloud-api/src/client/query_api_endpoints.rs": frozenset({"service"}), + "crates/clickhouse-cloud-api/src/client/rbac.rs": frozenset({"organization"}), "crates/clickhouse-cloud-api/src/client/saved_queries.rs": frozenset({"service"}), "crates/clickhouse-cloud-api/src/client/services.rs": frozenset( {"service", "clickpipes"} @@ -56,6 +60,9 @@ "crates/clickhouse-cloud-api/src/convert/service.rs": frozenset( {"service", "clickpipes"} ), + "crates/clickhouse-cloud-api/src/convert/services.rs": frozenset( + {"service", "clickpipes"} + ), "crates/clickhouse-cloud-api/src/convert/shared.rs": ALL_SUITES, "crates/clickhouse-cloud-api/src/error.rs": ALL_SUITES, "crates/clickhouse-cloud-api/src/lib.rs": ALL_SUITES, diff --git a/scripts/tests/test_classify_cloud_integration.py b/scripts/tests/test_classify_cloud_integration.py index 466136364..e839cc574 100644 --- a/scripts/tests/test_classify_cloud_integration.py +++ b/scripts/tests/test_classify_cloud_integration.py @@ -75,7 +75,7 @@ def test_current_source_mapping_values(self): }, frozenset({"service", "clickpipes"}): { "crates/clickhouse-cloud-api/src/client/services.rs", - "crates/clickhouse-cloud-api/src/convert/service.rs", + "crates/clickhouse-cloud-api/src/convert/services.rs", "crates/clickhouse-cloud-api/src/models/services.rs", }, frozenset({"postgres", "clickpipes"}): { @@ -99,6 +99,10 @@ def test_current_source_mapping_values(self): frozenset({"organization"}): { "crates/clickhouse-cloud-api/src/client/activity.rs", "crates/clickhouse-cloud-api/src/models/activity.rs", + "crates/clickhouse-cloud-api/src/client/byoc.rs", + "crates/clickhouse-cloud-api/src/client/invitations.rs", + "crates/clickhouse-cloud-api/src/client/members.rs", + "crates/clickhouse-cloud-api/src/client/rbac.rs", "crates/clickhouse-cloud-api/src/models/invitations.rs", "crates/clickhouse-cloud-api/src/models/members.rs", "crates/clickhouse-cloud-api/src/models/rbac.rs",