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_global_request(
84 Method::GET,
85 ["api", "region", &provider.cloud_provider, &provider.name],
86 None,
87 )
88 .await?;
89
90 match self.send_request::<Region>(req).await {
91 Ok(region) => match region.region_state {
92 RegionState::SoftDeleted => Err(Error::EmptyRegion),
93 RegionState::DeletionPending => Err(Error::EmptyRegion),
94 RegionState::Enabled => Ok(region),
95 RegionState::EnablementPending => Ok(region),
96 },
97 Err(Error::SuccesfullButNoContent) => Err(Error::EmptyRegion),
98 Err(e) => Err(e),
99 }
100 }
101
102 pub async fn get_all_regions(&self) -> Result<Vec<Region>, Error> {
104 let cloud_providers: Vec<CloudProvider> = self.list_cloud_regions().await?;
105 let mut regions: Vec<Region> = vec![];
106
107 for cloud_provider in cloud_providers {
108 match self.get_region(cloud_provider).await {
109 Ok(region) => {
110 regions.push(region);
111 }
112 Err(Error::EmptyRegion) => {}
114 Err(e) => return Err(e),
115 }
116 }
117
118 Ok(regions)
119 }
120
121 pub async fn create_region(
123 &self,
124 version: Option<String>,
125 environmentd_extra_args: Vec<String>,
126 environmentd_cpu_allocation: Option<String>,
127 environmentd_memory_allocation: Option<String>,
128 cloud_provider: CloudProvider,
129 ) -> Result<Region, Error> {
130 #[derive(Serialize)]
131 #[serde(rename_all = "camelCase")]
132 struct Body {
133 #[serde(skip_serializing_if = "Option::is_none")]
134 environmentd_image_ref: Option<String>,
135 #[serde(skip_serializing_if = "Vec::is_empty")]
136 environmentd_extra_args: Vec<String>,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 environmentd_cpu_allocation: Option<String>,
139 #[serde(skip_serializing_if = "Option::is_none")]
140 environmentd_memory_allocation: Option<String>,
141 }
142
143 let body = Body {
144 environmentd_image_ref: version.map(|v| match v.split_once(':') {
145 None => format!("materialize/environmentd:{v}"),
146 Some((user, v)) => format!("{user}/environmentd:{v}"),
147 }),
148 environmentd_extra_args,
149 environmentd_cpu_allocation,
150 environmentd_memory_allocation,
151 };
152
153 let req = self
154 .build_global_request(
155 Method::PATCH,
156 [
157 "api",
158 "region",
159 &cloud_provider.cloud_provider,
160 &cloud_provider.name,
161 ],
162 None,
163 )
164 .await?;
165 let req = req.json(&body);
166 let req = req.timeout(Duration::from_secs(60));
168 self.send_request(req).await
169 }
170
171 pub async fn delete_region(
179 &self,
180 cloud_provider: CloudProvider,
181 hard: bool,
182 ) -> Result<(), Error> {
183 struct Empty;
187
188 impl<'de> Deserialize<'de> for Empty {
189 fn deserialize<D>(_: D) -> Result<Empty, D::Error>
190 where
191 D: Deserializer<'de>,
192 {
193 Ok(Empty)
194 }
195 }
196
197 let query = if hard {
198 Some([("hardDelete", "true")].as_slice())
199 } else {
200 None
201 };
202
203 let req = self
204 .build_global_request(
205 Method::DELETE,
206 [
207 "api",
208 "region",
209 &cloud_provider.cloud_provider,
210 &cloud_provider.name,
211 ],
212 query,
213 )
214 .await?;
215 self.send_request::<Empty>(req).await?;
216
217 for _ in 0..600 {
219 let req = self
220 .build_global_request(
221 Method::GET,
222 [
223 "api",
224 "region",
225 &cloud_provider.cloud_provider,
226 &cloud_provider.name,
227 ],
228 None,
229 )
230 .await?;
231 let res = self.send_request::<Region>(req).await;
232 if hard {
233 if let Err(Error::SuccesfullButNoContent) = res {
234 return Ok(());
235 }
236 } else {
237 if let Ok(Region {
238 region_state: RegionState::SoftDeleted,
239 ..
240 }) = res
241 {
242 return Ok(());
243 }
244 }
245
246 tokio::time::sleep(Duration::from_secs(1)).await;
247 }
248 Err(Error::TimeoutError)
249 }
250}