smbcloud_cli/deploy/
mod.rs1pub mod config;
2mod git;
3mod remote_messages;
4mod setup;
5
6use crate::{
7 account::{lib::is_logged_in, login::process_login, me::me},
8 cli::CommandResult,
9 deploy::config::check_project,
10 project::runner::detect_runner,
11 ui::{fail_message, succeed_message, succeed_symbol},
12};
13use anyhow::{anyhow, Result};
14use config::check_config;
15use git::remote_deployment_setup;
16use git2::{PushOptions, RemoteCallbacks, Repository};
17use remote_messages::{build_next_app, start_server};
18use smbcloud_model::project::{DeploymentPayload, DeploymentStatus};
19use smbcloud_networking::{environment::Environment, get_smb_token};
20use smbcloud_networking_project::{
21 crud_project_deployment_create::create_deployment, crud_project_deployment_update::update,
22};
23use spinners::Spinner;
24use std::sync::atomic::AtomicBool;
25use std::sync::atomic::Ordering;
26use std::sync::Arc;
27
28pub async fn process_deploy(env: Environment) -> Result<CommandResult> {
29 if !is_logged_in(env) {
31 let _ = process_login(env).await;
32 }
33
34 let access_token = get_smb_token(env).await?;
36
37 let config = check_config(env).await?;
39
40 let runner = detect_runner().await?;
42
43 check_project(env, &access_token, config.project.id).await?;
45
46 let repo = match Repository::open(".") {
48 Ok(repo) => repo,
49 Err(_) => {
50 return Err(anyhow!(fail_message(
51 "No git repository found. Init with `git init` command."
52 )))
53 }
54 };
55
56 let main_branch = match repo.head() {
57 Ok(branch) => branch,
58 Err(_) => {
59 return Err(anyhow!(fail_message(
60 "No main branch found. Create with `git checkout -b <branch>` command."
61 )))
62 }
63 };
64
65 let mut origin = remote_deployment_setup(&runner, &repo, &config.project.repository).await?;
66
67 let commit_hash = match main_branch.resolve() {
68 Ok(result) => match result.target() {
69 Some(hash_id) => hash_id,
70 None => return Err(anyhow!("Should have at least one commit.")),
71 },
72 Err(_) => return Err(anyhow!("Cannot resolve main branch.")),
73 };
74 let payload = DeploymentPayload {
75 commit_hash: commit_hash.to_string(),
76 status: DeploymentStatus::Started,
77 };
78
79 let created_deployment =
80 create_deployment(env, &access_token, config.project.id, payload).await?;
81 let user = me(env).await?;
82
83 let mut push_opts = PushOptions::new();
84 let mut callbacks = RemoteCallbacks::new();
85
86 let deployment_failed_flag = Arc::new(AtomicBool::new(false));
88 let update_env = env; let update_access_token = access_token.clone();
90 let update_project_id = config.project.id;
91 let update_deployment_id = created_deployment.id;
92
93 callbacks.credentials(config.credentials(user));
95 callbacks.sideband_progress(|data| {
96 if let Ok(text) = std::str::from_utf8(data) {
98 for line in text.lines() {
99 if line.contains(&build_next_app()) {
100 println!("Building the app {}", succeed_symbol());
101 }
102 if line.contains(&start_server(&config.project.repository)) {
103 println!("App restart {}", succeed_symbol());
104 }
105 }
106 }
107 true });
109 callbacks.push_update_reference({
110 let flag_clone = deployment_failed_flag.clone();
111 let access_token_for_update_cb = update_access_token.clone();
112 let project_id_for_update_cb = update_project_id;
113 let deployment_id_for_update_cb = update_deployment_id;
114
115 move |_refname, status_message| {
116 if let Some(e) = status_message {
117 if !flag_clone.swap(true, Ordering::SeqCst) {
119 println!(
120 "Deployment ref update failed: {}. Marking deployment as Failed.",
121 e
122 );
123
124 let update_payload = DeploymentPayload {
125 commit_hash: commit_hash.to_string(),
126 status: DeploymentStatus::Failed,
127 };
128
129 let handle = tokio::runtime::Handle::current();
131 let result = handle.block_on(async {
132 update(
133 update_env, access_token_for_update_cb.clone(),
135 project_id_for_update_cb,
136 deployment_id_for_update_cb,
137 update_payload,
138 )
139 .await
140 });
141
142 match result {
143 Ok(_) => println!("Deployment status successfully updated to Failed."),
144 Err(update_err) => {
145 eprintln!("Error updating deployment status to Failed: {}", update_err)
146 }
147 }
148 }
149 }
150 Ok(()) }
152 });
153 push_opts.remote_callbacks(callbacks);
154
155 let spinner = Spinner::new(
156 spinners::Spinners::Hamburger,
157 succeed_message("Deploying > "),
158 );
159
160 match origin.push(&["refs/heads/main:refs/heads/main"], Some(&mut push_opts)) {
161 Ok(_) => {
162 let update_payload = DeploymentPayload {
164 commit_hash: commit_hash.to_string(),
165 status: DeploymentStatus::Done,
166 };
167 let result = update(
168 env,
169 access_token.clone(),
170 config.project.id,
171 created_deployment.id,
172 update_payload,
173 )
174 .await;
175 match result {
176 Ok(_) => println!("App is running {}", succeed_symbol()),
177 Err(update_err) => {
178 eprintln!("Error updating deployment status to Done: {}", update_err)
179 }
180 }
181 Ok(CommandResult {
182 spinner,
183 symbol: succeed_symbol(),
184 msg: succeed_message("Deployment complete."),
185 })
186 }
187 Err(e) => Err(anyhow!(fail_message(&e.to_string()))),
188 }
189}