1use std::time::Duration;
21
22use crate::{context::RegionContext, error::Error};
23
24use mz_cloud_api::client::{cloud_provider::CloudProvider, region::RegionState};
25use mz_ore::retry::Retry;
26use serde::{Deserialize, Serialize};
27use tabled::Tabled;
28
29pub async fn enable(
35 cx: RegionContext,
36 version: Option<String>,
37 environmentd_extra_arg: Option<Vec<String>>,
38 environmentd_cpu_allocation: Option<String>,
39 environmentd_memory_allocation: Option<String>,
40) -> Result<(), Error> {
41 let loading_spinner = cx
42 .output_formatter()
43 .loading_spinner("Retrieving information...");
44 let cloud_provider = cx.get_cloud_provider().await?;
45
46 loading_spinner.set_message("Enabling the region...");
47
48 let environmentd_extra_arg: Vec<String> = environmentd_extra_arg.unwrap_or_else(Vec::new);
49
50 let _ = Retry::default()
53 .max_duration(Duration::from_secs(720))
54 .clamp_backoff(Duration::from_secs(1))
55 .retry_async(|_| async {
56 let _ = cx
57 .cloud_client()
58 .create_region(
59 version.clone(),
60 environmentd_extra_arg.clone(),
61 environmentd_cpu_allocation.clone(),
62 environmentd_memory_allocation.clone(),
63 cloud_provider.clone(),
64 )
65 .await?;
66 Ok(())
67 })
68 .await
69 .map_err(|e| Error::TimeoutError(Box::new(e)))?;
70
71 loading_spinner.set_message("Waiting for the region to be online...");
72
73 let _ = Retry::default()
76 .max_duration(Duration::from_secs(720))
77 .clamp_backoff(Duration::from_secs(1))
78 .retry_async(|_| async {
79 let region = cx.get_region().await?;
80
81 match region.region_state {
82 RegionState::EnablementPending => {
83 loading_spinner.set_message("Waiting for the region to be ready...");
84 Err(Error::NotReadyRegion)
85 }
86 RegionState::DeletionPending => Err(Error::CommandExecutionError(
87 "This region is pending deletion!".to_string(),
88 )),
89 RegionState::SoftDeleted => Err(Error::CommandExecutionError(
90 "This region has been marked soft-deleted!".to_string(),
91 )),
92 RegionState::Enabled => match region.region_info {
93 Some(region_info) => {
94 loading_spinner.set_message("Waiting for the region to be resolvable...");
95 if region_info.resolvable {
96 let claims = cx.admin_client().claims().await?;
97 let user = claims.user()?;
98 if cx.sql_client().is_ready(®ion_info, user)? {
99 return Ok(());
100 }
101 Err(Error::NotPgReadyError)
102 } else {
103 Err(Error::NotResolvableRegion)
104 }
105 }
106 None => Err(Error::NotReadyRegion),
107 },
108 }
109 })
110 .await
111 .map_err(|e| Error::TimeoutError(Box::new(e)))?;
112
113 loading_spinner.finish_with_message(format!("Region in {} is now online", cloud_provider.id));
114
115 Ok(())
116}
117
118pub async fn disable(cx: RegionContext, hard: bool) -> Result<(), Error> {
122 let loading_spinner = cx
123 .output_formatter()
124 .loading_spinner("Retrieving information...");
125
126 let cloud_provider = cx.get_cloud_provider().await?;
127
128 Retry::default()
133 .max_duration(Duration::from_secs(720))
134 .clamp_backoff(Duration::from_secs(1))
135 .retry_async(|_| async {
136 loading_spinner.set_message("Disabling region...");
137 cx.cloud_client()
138 .delete_region(cloud_provider.clone(), hard)
139 .await?;
140
141 loading_spinner.finish_with_message("Region disabled.");
142 Ok(())
143 })
144 .await
145}
146
147pub async fn list(cx: RegionContext) -> Result<(), Error> {
149 let output_formatter = cx.output_formatter();
150 let loading_spinner = output_formatter.loading_spinner("Retrieving regions...");
151
152 #[derive(Deserialize, Serialize, Tabled)]
153 pub struct Region<'a> {
154 #[tabled(rename = "Region")]
155 region: String,
156 #[tabled(rename = "Status")]
157 status: &'a str,
158 }
159
160 let cloud_providers: Vec<CloudProvider> = cx.cloud_client().list_cloud_regions().await?;
161 let mut regions: Vec<Region> = vec![];
162
163 for cloud_provider in cloud_providers {
164 match cx.cloud_client().get_region(cloud_provider.clone()).await {
165 Ok(_) => regions.push(Region {
166 region: cloud_provider.id,
167 status: "enabled",
168 }),
169 Err(mz_cloud_api::error::Error::EmptyRegion) => regions.push(Region {
170 region: cloud_provider.id,
171 status: "disabled",
172 }),
173 Err(err) => {
174 println!("Error: {:?}", err)
175 }
176 }
177 }
178
179 loading_spinner.finish_and_clear();
180 output_formatter.output_table(regions)?;
181 Ok(())
182}
183
184pub async fn show(cx: RegionContext) -> Result<(), Error> {
186 let output_formatter = cx.output_formatter();
190 let loading_spinner = output_formatter.loading_spinner("Retrieving region...");
191
192 let region_info = cx.get_region_info().await?;
193
194 loading_spinner.set_message("Checking environment health...");
195 let claims = cx.admin_client().claims().await?;
196 let sql_client = cx.sql_client();
197 let environment_health = match sql_client.is_ready(®ion_info, claims.user()?) {
198 Ok(healthy) => match healthy {
199 true => "yes",
200 _ => "no",
201 },
202 Err(_) => "no",
203 };
204
205 loading_spinner.finish_and_clear();
206 output_formatter.output_scalar(Some(&format!("Healthy: \t{}", environment_health)))?;
207 output_formatter.output_scalar(Some(&format!("SQL address: \t{}", region_info.sql_address)))?;
208 output_formatter.output_scalar(Some(&format!("HTTP URL: \t{}", region_info.http_address)))?;
209
210 Ok(())
211}