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