Skip to main content

syncable_cli/wizard/
registry_provisioning.rs

1//! Registry provisioning step for deployment wizard
2
3use 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/// Result of registry provisioning
15#[derive(Debug)]
16pub enum RegistryProvisioningResult {
17    /// Successfully provisioned
18    Success(RegistrySummary),
19    /// User cancelled
20    Cancelled,
21    /// Error during provisioning
22    Error(String),
23}
24
25/// Provision a new artifact registry
26pub 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    // Get registry name from user
42    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    // Build request
62    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    // Start provisioning
73    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    // Poll for completion with progress display
87    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        // Show progress
102        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(), // Will be updated when we fetch actual registry
122                    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                // Continue polling
147            }
148        }
149    }
150}
151
152/// Create a simple progress bar
153fn 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
159/// Sanitize registry name (lowercase, alphanumeric, hyphens)
160fn 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}