mz_cloud_api/client/
region.rs1use std::time::Duration;
19
20use chrono::{DateTime, Utc};
21use reqwest::Method;
22use serde::{Deserialize, Deserializer, Serialize};
23
24use crate::client::cloud_provider::CloudProvider;
25use crate::client::{Client, Error};
26
27#[derive(Debug, Deserialize, Clone)]
29#[serde(rename_all = "camelCase")]
30pub struct Region {
31 pub region_info: Option<RegionInfo>,
35
36 pub region_state: RegionState,
38}
39
40#[derive(Debug, Deserialize, Clone)]
42#[serde(rename_all = "camelCase")]
43pub struct RegionInfo {
44 pub sql_address: String,
48 pub http_address: String,
52 pub resolvable: bool,
54 pub enabled_at: Option<DateTime<Utc>>,
56}
57
58#[derive(Debug, Deserialize, Clone)]
60#[serde(rename_all = "kebab-case")]
61pub enum RegionState {
62 Enabled,
64
65 EnablementPending,
68
69 DeletionPending,
72
73 SoftDeleted,
76}
77
78impl Client {
79 pub async fn get_region(&self, provider: CloudProvider) -> Result<Region, Error> {
81 let req = self
83 .build_region_request(Method::GET, ["api", "region"], None, &provider, Some(1))
84 .await?;
85
86 match self.send_request::<Region>(req).await {
87 Ok(region) => match region.region_state {
88 RegionState::SoftDeleted => Err(Error::EmptyRegion),
89 RegionState::DeletionPending => Err(Error::EmptyRegion),
90 RegionState::Enabled => Ok(region),
91 RegionState::EnablementPending => Ok(region),
92 },
93 Err(Error::SuccesfullButNoContent) => Err(Error::EmptyRegion),
94 Err(e) => Err(e),
95 }
96 }
97
98 pub async fn get_all_regions(&self) -> Result<Vec<Region>, Error> {
100 let cloud_providers: Vec<CloudProvider> = self.list_cloud_regions().await?;
101 let mut regions: Vec<Region> = vec![];
102
103 for cloud_provider in cloud_providers {
104 match self.get_region(cloud_provider).await {
105 Ok(region) => {
106 regions.push(region);
107 }
108 Err(Error::EmptyRegion) => {}
110 Err(e) => return Err(e),
111 }
112 }
113
114 Ok(regions)
115 }
116
117 pub async fn create_region(
119 &self,
120 version: Option<String>,
121 environmentd_extra_args: Vec<String>,
122 environmentd_cpu_allocation: Option<String>,
123 environmentd_memory_allocation: Option<String>,
124 cloud_provider: CloudProvider,
125 ) -> Result<Region, Error> {
126 #[derive(Serialize)]
127 #[serde(rename_all = "camelCase")]
128 struct Body {
129 #[serde(skip_serializing_if = "Option::is_none")]
130 environmentd_image_ref: Option<String>,
131 #[serde(skip_serializing_if = "Vec::is_empty")]
132 environmentd_extra_args: Vec<String>,
133 #[serde(skip_serializing_if = "Option::is_none")]
134 environmentd_cpu_allocation: Option<String>,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 environmentd_memory_allocation: Option<String>,
137 }
138
139 let body = Body {
140 environmentd_image_ref: version.map(|v| match v.split_once(':') {
141 None => format!("materialize/environmentd:{v}"),
142 Some((user, v)) => format!("{user}/environmentd:{v}"),
143 }),
144 environmentd_extra_args,
145 environmentd_cpu_allocation,
146 environmentd_memory_allocation,
147 };
148
149 let req = self
150 .build_region_request(
151 Method::PATCH,
152 ["api", "region"],
153 None,
154 &cloud_provider,
155 Some(1),
156 )
157 .await?;
158 let req = req.json(&body);
159 let req = req.timeout(Duration::from_secs(60));
161 self.send_request(req).await
162 }
163
164 pub async fn delete_region(
172 &self,
173 cloud_provider: CloudProvider,
174 hard: bool,
175 ) -> Result<(), Error> {
176 struct Empty;
180
181 impl<'de> Deserialize<'de> for Empty {
182 fn deserialize<D>(_: D) -> Result<Empty, D::Error>
183 where
184 D: Deserializer<'de>,
185 {
186 Ok(Empty)
187 }
188 }
189
190 let query = if hard {
191 Some([("hardDelete", "true")].as_slice())
192 } else {
193 None
194 };
195
196 let req = self
197 .build_region_request(
198 Method::DELETE,
199 ["api", "region"],
200 query,
201 &cloud_provider,
202 Some(1),
203 )
204 .await?;
205 self.send_request::<Empty>(req).await?;
206
207 for _ in 0..600 {
209 let req = self
210 .build_region_request(
211 Method::GET,
212 ["api", "region"],
213 None,
214 &cloud_provider,
215 Some(1),
216 )
217 .await?;
218 let res = self.send_request::<Region>(req).await;
219 if hard {
220 if let Err(Error::SuccesfullButNoContent) = res {
221 return Ok(());
222 }
223 } else {
224 if let Ok(Region {
225 region_state: RegionState::SoftDeleted,
226 ..
227 }) = res
228 {
229 return Ok(());
230 }
231 }
232
233 tokio::time::sleep(Duration::from_secs(1)).await;
234 }
235 Err(Error::TimeoutError)
236 }
237}