syncable_cli/wizard/
registry_provisioning.rs1use crate::platform::api::types::{
4 CloudProvider, CreateRegistryRequest, RegistrySummary, RegistryTaskState,
5};
6use crate::platform::api::PlatformApiClient;
7use crate::wizard::render::{display_step_header, wizard_render_config};
8use colored::Colorize;
9use inquire::{InquireError, Text};
10use std::io::Write;
11use std::time::Duration;
12use tokio::time::sleep;
13
14#[derive(Debug)]
16pub enum RegistryProvisioningResult {
17 Success(RegistrySummary),
19 Cancelled,
21 Error(String),
23}
24
25pub async fn provision_registry(
27 client: &PlatformApiClient,
28 project_id: &str,
29 cluster_id: &str,
30 cluster_name: &str,
31 provider: CloudProvider,
32 region: &str,
33 gcp_project_id: Option<&str>,
34) -> RegistryProvisioningResult {
35 display_step_header(
36 4,
37 "Provision Registry",
38 "Create a new container registry for storing images.",
39 );
40
41 let registry_name = match Text::new("Registry name:")
43 .with_default("main")
44 .with_help_message("Lowercase alphanumeric with hyphens (e.g., main, staging)")
45 .with_render_config(wizard_render_config())
46 .prompt()
47 {
48 Ok(name) => sanitize_registry_name(&name),
49 Err(InquireError::OperationCanceled) | Err(InquireError::OperationInterrupted) => {
50 return RegistryProvisioningResult::Cancelled;
51 }
52 Err(_) => return RegistryProvisioningResult::Cancelled,
53 };
54
55 println!(
56 "\n{} Provisioning registry: {}",
57 "⏳".yellow(),
58 registry_name.cyan()
59 );
60
61 let request = CreateRegistryRequest {
63 project_id: project_id.to_string(),
64 cluster_id: cluster_id.to_string(),
65 cluster_name: cluster_name.to_string(),
66 registry_name: registry_name.clone(),
67 cloud_provider: provider.as_str().to_string(),
68 region: region.to_string(),
69 gcp_project_id: gcp_project_id.map(|s| s.to_string()),
70 };
71
72 let response = match client.create_registry(project_id, &request).await {
74 Ok(r) => r,
75 Err(e) => {
76 return RegistryProvisioningResult::Error(format!(
77 "Failed to start registry provisioning: {}",
78 e
79 ));
80 }
81 };
82
83 let task_id = response.task_id;
84 println!(" Task started: {}", task_id.dimmed());
85
86 let mut last_progress = 0;
88 loop {
89 sleep(Duration::from_secs(3)).await;
90
91 let status = match client.get_registry_task_status(&task_id).await {
92 Ok(s) => s,
93 Err(e) => {
94 return RegistryProvisioningResult::Error(format!(
95 "Failed to get task status: {}",
96 e
97 ));
98 }
99 };
100
101 let progress = status.progress.unwrap_or(0);
103 if progress > last_progress {
104 let bar = progress_bar(progress);
105 let message = status
106 .overall_message
107 .as_deref()
108 .unwrap_or("Processing...");
109 print!(
110 "\r {} {} {}",
111 bar,
112 format!("{}%", progress).cyan(),
113 message.dimmed()
114 );
115 std::io::stdout().flush().ok();
116 last_progress = progress;
117 }
118
119 match status.status {
120 RegistryTaskState::Completed => {
121 println!("\n{} Registry provisioned successfully!", "✓".green());
122
123 let registry = RegistrySummary {
124 id: task_id.clone(), name: status.output.registry_name.unwrap_or(registry_name),
126 region: region.to_string(),
127 is_ready: true,
128 };
129
130 if let Some(url) = status.output.registry_url {
131 println!(" URL: {}", url.cyan());
132 }
133
134 return RegistryProvisioningResult::Success(registry);
135 }
136 RegistryTaskState::Failed => {
137 println!();
138 let error_msg = status
139 .error
140 .map(|e| e.message)
141 .unwrap_or_else(|| "Unknown error".to_string());
142 return RegistryProvisioningResult::Error(error_msg);
143 }
144 RegistryTaskState::Cancelled => {
145 println!();
146 return RegistryProvisioningResult::Cancelled;
147 }
148 RegistryTaskState::Processing | RegistryTaskState::Unknown => {
149 }
151 }
152 }
153}
154
155fn progress_bar(percent: u8) -> String {
157 let filled = (percent as usize * 20) / 100;
158 let empty = 20 - filled;
159 format!("[{}{}]", "█".repeat(filled), "░".repeat(empty))
160}
161
162fn sanitize_registry_name(name: &str) -> String {
164 name.to_lowercase()
165 .chars()
166 .map(|c| if c.is_alphanumeric() || c == '-' { c } else { '-' })
167 .collect::<String>()
168 .trim_matches('-')
169 .to_string()
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_sanitize_registry_name() {
178 assert_eq!(sanitize_registry_name("My Registry"), "my-registry");
179 assert_eq!(sanitize_registry_name("test_name"), "test-name");
180 assert_eq!(sanitize_registry_name("--test--"), "test");
181 assert_eq!(sanitize_registry_name("MAIN"), "main");
182 assert_eq!(sanitize_registry_name("prod-123"), "prod-123");
183 }
184
185 #[test]
186 fn test_progress_bar() {
187 assert_eq!(progress_bar(0), "[░░░░░░░░░░░░░░░░░░░░]");
188 assert_eq!(progress_bar(50), "[██████████░░░░░░░░░░]");
189 assert_eq!(progress_bar(100), "[████████████████████]");
190 }
191}