systemprompt_cli/commands/cloud/deploy/
progress.rs1use std::sync::Mutex;
13
14use indicatif::ProgressBar;
15use systemprompt_logging::CliService;
16
17use super::pipeline::{DeployEvent, DeployProgress};
18
19pub struct CliDeployProgress {
20 spinner: Mutex<Option<ProgressBar>>,
21}
22
23impl std::fmt::Debug for CliDeployProgress {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 f.debug_struct("CliDeployProgress").finish_non_exhaustive()
26 }
27}
28
29impl CliDeployProgress {
30 pub const fn new() -> Self {
31 Self {
32 spinner: Mutex::new(None),
33 }
34 }
35
36 fn start_spinner(&self, message: &str) {
37 if let Ok(mut slot) = self.spinner.lock() {
38 *slot = Some(CliService::spinner(message));
39 }
40 }
41
42 fn clear_spinner(&self) {
43 if let Ok(mut slot) = self.spinner.lock()
44 && let Some(spinner) = slot.take()
45 {
46 spinner.finish_and_clear();
47 }
48 }
49}
50
51impl Default for CliDeployProgress {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57impl DeployProgress for CliDeployProgress {
58 fn event(&self, event: &DeployEvent<'_>) {
59 self.clear_spinner();
60 if let Some(message) = spinner_message(event) {
61 self.start_spinner(message);
62 return;
63 }
64 render_event(event);
65 }
66}
67
68pub const fn spinner_message(event: &DeployEvent<'_>) -> Option<&'static str> {
69 match event {
70 DeployEvent::RegistryAuthStarted => Some("Fetching registry credentials..."),
71 DeployEvent::BuildStarted => Some("Building Docker image..."),
72 DeployEvent::PushStarted => Some("Pushing to registry..."),
73 DeployEvent::SecretsSyncStarted => Some("Syncing secrets..."),
74 DeployEvent::CredentialsSyncStarted => Some("Syncing cloud credentials..."),
75 DeployEvent::DeployStarted => Some("Deploying..."),
76 _ => None,
77 }
78}
79
80fn render_event(event: &DeployEvent<'_>) {
81 match event {
82 DeployEvent::ArtifactsResolved {
83 tenant_name,
84 binary,
85 dockerfile,
86 } => {
87 CliService::key_value("Tenant", tenant_name);
88 CliService::key_value("Binary", &binary.display().to_string());
89 CliService::key_value("Dockerfile", &dockerfile.display().to_string());
90 },
91 DeployEvent::ImageResolved { image } => CliService::key_value("Image", image),
92 DeployEvent::BuildFinished => CliService::success("Docker image built"),
93 DeployEvent::PushSkipped => CliService::info("Push skipped (--skip-push)"),
94 DeployEvent::PushFinished => CliService::success("Image pushed"),
95 DeployEvent::SecretsPhaseStarted => CliService::section("Provisioning Secrets"),
96 DeployEvent::SecretsFileMissing => {
97 CliService::warning("No secrets.json found - skipping secrets sync");
98 },
99 DeployEvent::SecretsSynced { count } => {
100 CliService::success(&format!("Synced {} secrets", count));
101 },
102 DeployEvent::CredentialsSynced { count } => {
103 CliService::success(&format!("Synced {} cloud credentials", count));
104 },
105 DeployEvent::ProfilePathConfigured => CliService::success("Profile path configured"),
106 DeployEvent::Deployed { status, app_url } => {
107 CliService::success("Deployed!");
108 CliService::key_value("Status", status);
109 if let Some(url) = app_url {
110 CliService::key_value("URL", url);
111 }
112 },
113 _ => {},
114 }
115}