syncable_cli/wizard/
registry_provisioning.rs1use crate::platform::api::PlatformApiClient;
4use crate::platform::api::types::{
5 CloudProvider, CreateRegistryRequest, RegistrySummary, RegistryTaskState,
6};
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.overall_message.as_deref().unwrap_or("Processing...");
106 print!(
107 "\r {} {} {}",
108 bar,
109 format!("{}%", progress).cyan(),
110 message.dimmed()
111 );
112 std::io::stdout().flush().ok();
113 last_progress = progress;
114 }
115
116 match status.status {
117 RegistryTaskState::Completed => {
118 println!("\n{} Registry provisioned successfully!", "✓".green());
119
120 let registry = RegistrySummary {
121 id: task_id.clone(), name: status.output.registry_name.unwrap_or(registry_name),
123 region: region.to_string(),
124 is_ready: true,
125 };
126
127 if let Some(url) = status.output.registry_url {
128 println!(" URL: {}", url.cyan());
129 }
130
131 return RegistryProvisioningResult::Success(registry);
132 }
133 RegistryTaskState::Failed => {
134 println!();
135 let error_msg = status
136 .error
137 .map(|e| e.message)
138 .unwrap_or_else(|| "Unknown error".to_string());
139 return RegistryProvisioningResult::Error(error_msg);
140 }
141 RegistryTaskState::Cancelled => {
142 println!();
143 return RegistryProvisioningResult::Cancelled;
144 }
145 RegistryTaskState::Processing | RegistryTaskState::Unknown => {
146 }
148 }
149 }
150}
151
152fn progress_bar(percent: u8) -> String {
154 let filled = (percent as usize * 20) / 100;
155 let empty = 20 - filled;
156 format!("[{}{}]", "█".repeat(filled), "░".repeat(empty))
157}
158
159fn sanitize_registry_name(name: &str) -> String {
161 name.to_lowercase()
162 .chars()
163 .map(|c| {
164 if c.is_alphanumeric() || c == '-' {
165 c
166 } else {
167 '-'
168 }
169 })
170 .collect::<String>()
171 .trim_matches('-')
172 .to_string()
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_sanitize_registry_name() {
181 assert_eq!(sanitize_registry_name("My Registry"), "my-registry");
182 assert_eq!(sanitize_registry_name("test_name"), "test-name");
183 assert_eq!(sanitize_registry_name("--test--"), "test");
184 assert_eq!(sanitize_registry_name("MAIN"), "main");
185 assert_eq!(sanitize_registry_name("prod-123"), "prod-123");
186 }
187
188 #[test]
189 fn test_progress_bar() {
190 assert_eq!(progress_bar(0), "[░░░░░░░░░░░░░░░░░░░░]");
191 assert_eq!(progress_bar(50), "[██████████░░░░░░░░░░]");
192 assert_eq!(progress_bar(100), "[████████████████████]");
193 }
194}