smbcloud_cli/deploy/
mod.rs

1mod git;
2
3use std::{fs::File, io::BufReader};
4
5use crate::cli::CommandResult;
6use anyhow::Result;
7use console::style;
8use git2::{Cred, PushOptions, RemoteCallbacks, Repository};
9use git_url_parse::{GitUrl, Scheme};
10use spinners::Spinner;
11use ssh2_config::{ParseRule, SshConfig};
12
13pub async fn process_deploy() -> Result<CommandResult> {
14    println!("Deploying your app...");
15    let mut spinner = Spinner::new(
16        spinners::Spinners::SimpleDotsScrolling,
17        style("Deploying...").green().bold().to_string(),
18    );
19
20    let repo = match Repository::open(".") {
21        Ok(repo) => repo,
22        Err(_) => {
23            spinner.stop_and_persist("😩", "No git repository found.".to_owned());
24            return Ok(CommandResult {
25                spinner,
26                symbol: "😩".to_owned(),
27                msg: "No git repository found. Init with `git init` command.".to_owned(),
28            });
29        }
30    };
31
32    let _main_branch = match repo.head() {
33        Ok(branch) => branch,
34        Err(_) => {
35            spinner.stop_and_persist("😩", "No main branch found.".to_owned());
36            return Ok(CommandResult {
37                spinner,
38                symbol: "😩".to_owned(),
39                msg: "No main branch found. Create with `git checkout -b <branch>` command."
40                    .to_owned(),
41            });
42        }
43    };
44    let mut origin = match repo.find_remote("origin") {
45        Ok(remote) => remote,
46        Err(_) => {
47            spinner.stop_and_persist("😩", "No remote repository found.".to_owned());
48            return Ok(CommandResult {
49                spinner,
50                symbol: "😩".to_owned(),
51                msg: "No remote repository found. Add with `git remote add origin <url>` command."
52                    .to_owned(),
53            });
54        }
55    };
56    let remote_url = match origin.url() {
57        Some(url) => url,
58        None => {
59            spinner.stop_and_persist("😩", "No remote URL found.".to_owned());
60            return Ok(CommandResult {
61                spinner,
62                symbol: "😩".to_owned(),
63                msg: "No remote URL found. Add with `git remote add origin <url>` command."
64                    .to_owned(),
65            });
66        }
67    };
68    //println!("Remote URL: {:#?}", remote_url);
69    let parsed_url = match GitUrl::parse(remote_url) {
70        Ok(url) => url,
71        Err(e) => {
72            spinner.stop_and_persist("😩", e.to_string());
73            return Ok(CommandResult {
74                spinner,
75                symbol: "😩".to_owned(),
76                msg: "Invalid remote URL.".to_owned(),
77            });
78        }
79    };
80    //println!("Parsed URL: {:#?}", parsed_url);
81    match parsed_url.scheme {
82        Scheme::Ssh => {
83            println!("SSH URL: {:#?}", parsed_url);
84        }
85        _ => {
86            // Only support ssh for now
87            return Ok(CommandResult {
88                spinner,
89                symbol: "😩".to_owned(),
90                msg: "Only ssh is supported.".to_owned(),
91            });
92        }
93    };
94
95    // Get ssh config from host
96    let host = match parsed_url.host {
97        Some(host) => host,
98        None => {
99            spinner.stop_and_persist("😩", "No host found.".to_owned());
100            return Ok(CommandResult {
101                spinner,
102                symbol: "😩".to_owned(),
103                msg: "No host found.".to_owned(),
104            });
105        }
106    };
107
108    // get ssh_config
109    let ssh_config_file = match home::home_dir() {
110        Some(home) => {
111            let ssh_config_path = home.join(".ssh/config");
112            if ssh_config_path.exists() {
113                // println!("SSH config path: {:#?}", ssh_config_path);
114                // Open the file and read it
115                let file =
116                    File::open(ssh_config_path.clone()).expect("Unable to open ssh config file");
117                file
118            } else {
119                spinner.stop_and_persist("😩", "No ssh config found.".to_owned());
120                return Ok(CommandResult {
121                    spinner,
122                    symbol: "😩".to_owned(),
123                    msg: "No ssh config found.".to_owned(),
124                });
125            }
126        }
127        None => {
128            spinner.stop_and_persist("😩", "No home".to_owned());
129            return Ok(CommandResult {
130                spinner,
131                symbol: "😩".to_owned(),
132                msg: "No home directory found.".to_owned(),
133            });
134        }
135    };
136    //println!("SSH config path: {:#?}", ssh_config_file);
137    let mut reader = BufReader::new(ssh_config_file);
138    let config = SshConfig::default()
139        .parse(&mut reader, ParseRule::STRICT)
140        .expect("Failed to parse ssh config file");
141    //println!("SSH config: {:#?}", config);
142
143    // Get the host config
144    let host_config = config.query(host);
145    //println!("Host config: {:#?}", host_config);
146    // get identity_file
147    let identity_files = match host_config.identity_file {
148        Some(identity_files) => {
149            println!("Identity file: {:#?}", identity_files);
150            identity_files
151        }
152        None => {
153            spinner.stop_and_persist("😩", "No identity file found.".to_owned());
154            return Ok(CommandResult {
155                spinner,
156                symbol: "😩".to_owned(),
157                msg: "No identity files found.".to_owned(),
158            });
159        }
160    };
161    //println!("Identity files: {:#?}", identity_files);
162    // get identity_file
163    let identity_file = match identity_files.first() {
164        Some(identity_file) => {
165            //println!("Identity file: {:#?}", identity_file);
166            identity_file
167        }
168        None => {
169            spinner.stop_and_persist("😩", "No identity file found.".to_owned());
170            return Ok(CommandResult {
171                spinner,
172                symbol: "😩".to_owned(),
173                msg: "No identity file found.".to_owned(),
174            });
175        }
176    };
177    let mut push_opts = PushOptions::new();
178    let mut callbacks = RemoteCallbacks::new();
179    // Set the credentials
180    callbacks.credentials(|_url, _username_from_url, _allowed_types| {
181        Cred::ssh_key("git", None, identity_file, None)
182    });
183    push_opts.remote_callbacks(callbacks);
184
185    let _push = match origin.push(&["refs/heads/main:refs/heads/main"], Some(&mut push_opts)) {
186        Ok(_) => {}
187        Err(e) => {
188            println!("Failed to push to remote: {:#?}", e);
189            spinner.stop_and_persist("😩", e.to_string());
190            return Ok(CommandResult {
191                spinner,
192                symbol: "😩".to_owned(),
193                msg: e.to_string(),
194            });
195        }
196    };
197
198    spinner.stop_and_persist("✅", "Your app has been deployed successfully.".to_owned());
199
200    Ok(CommandResult {
201        spinner,
202        symbol: "🚀".to_owned(),
203        msg: "Your app has been deployed successfully.".to_owned(),
204    })
205}