syncable_cli/wizard/
registry_provisioning.rs

1//! Registry provisioning step for deployment wizard
2
3use 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/// 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
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(), // Will be updated when we fetch actual registry
125                    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                // Continue polling
150            }
151        }
152    }
153}
154
155/// Create a simple progress bar
156fn 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
162/// Sanitize registry name (lowercase, alphanumeric, hyphens)
163fn 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}