1use crate::{command::*, result::Result};
2use anyhow::Ok;
3use bollard::container::{Config, CreateContainerOptions, LogsOptions, StartContainerOptions};
4use bollard::image::CreateImageOptions;
5use bollard::models::{HostConfig, Mount, MountTypeEnum};
6use bollard::service::PortBinding;
7use bollard::{service::CreateImageInfo, Docker};
8use clap::{ArgAction::SetTrue, ArgGroup, Args};
9use console::style;
10use futures_util::{StreamExt, TryStreamExt};
11use indicatif::{HumanDuration, MultiProgress, ProgressBar, ProgressStyle};
12use std::collections::HashMap;
13use std::fmt::format;
14use std::hash::Hash;
15use std::thread;
16use std::time::{Duration, Instant};
17use std::{env, process, string};
18use uuid::Uuid;
19
20const ILLA_BUILDER_IMAGE: &str = "illasoft/illa-builder";
21const ILLA_BUILDER_VERSION: &str = "latest";
22
23#[derive(Debug, Args)]
26#[clap(group(
27 ArgGroup::new("install")
28 .required(true)
29 .args(&["self_host", "cloud"]),
30))]
31pub struct Cmd {
33 #[clap(short = 'S', long = "self", action = SetTrue)]
35 self_host: bool,
36
37 #[clap(short = 'C', long = "cloud", action = SetTrue)]
39 cloud: bool,
40
41 #[clap(short = 'V', long = "builder-version", value_name = "X.Y.Z")]
43 builder_version: Option<String>,
44
45 #[clap(short = 'p', long = "port", default_value = "80")]
47 port: u16,
48
49 #[clap(short = 'm', long = "mount", value_name = "/TEMP/DIR/ILLA-BUILDER")]
51 mount_path: Option<String>,
52}
53
54impl Cmd {
55 pub async fn run(&self) -> Result {
56 let spinner_style = ProgressStyle::with_template("{spinner} {wide_msg}")
57 .unwrap()
58 .tick_strings(&["🔸 ", "🔶 ", "🟠", "🟠", "🔶 "]);
59
60 let (self_host, cloud) = (self.self_host, self.cloud);
61 match (self_host, cloud) {
62 (true, _) => {
63 deploy_self_host(
64 self.builder_version.as_ref(),
65 self.port,
66 self.mount_path.as_ref(),
67 spinner_style,
68 )
69 .await?
70 }
71 (_, true) => deploy_cloud(spinner_style).await?,
72 _ => unreachable!(),
73 };
74 Ok(())
75 }
76}
77
78async fn deploy_self_host(
79 version: Option<&String>,
80 port: u16,
81 mount_path: Option<&String>,
82 progress_style: ProgressStyle,
83) -> Result {
84 println!("{} Running a self-hosted installation...", ui::emoji::BUILD);
85
86 let _docker = Docker::connect_with_local_defaults().unwrap();
87 if (_docker.ping().await).is_err() {
88 println!(
89 "{} {}\n{} {}",
90 ui::emoji::FAIL,
91 String::from("No running docker found."),
92 ui::emoji::WARN,
93 style("Please check the status of docker.").red(),
94 );
95 process::exit(1);
96 }
97
98 let m = MultiProgress::new();
99 let pb_download = m.add(ProgressBar::new(0));
100 pb_download.set_style(progress_style.clone());
101 let finish_spinner_style = ProgressStyle::with_template("{wide_msg}").unwrap();
102
103 let default_version = ILLA_BUILDER_VERSION.to_owned();
104 let builder_version = version.unwrap_or(&default_version);
105 let builder_image = ILLA_BUILDER_IMAGE.to_owned() + ":" + builder_version;
106
107 let default_mount_path = utils::get_default_mount();
108 let mount_path = mount_path.unwrap_or(&default_mount_path);
109
110 let download_started = Instant::now();
111 let stream_list = &mut _docker.create_image(
112 Some(CreateImageOptions {
113 from_image: builder_image.clone(),
114 ..Default::default()
115 }),
116 None,
117 None,
118 );
119
120 while let Some(value) = stream_list.next().await {
121 pb_download.set_message(format!("Downloading {}...", builder_image.clone()));
122 pb_download.inc(1);
123 thread::sleep(Duration::from_millis(100));
124 if value.is_err() {
125 pb_download.set_style(finish_spinner_style.clone());
126 pb_download.finish_with_message(format!(
127 "{} {} {}",
128 ui::emoji::FAIL,
129 String::from("Download image error:"),
130 style(value.err().unwrap()).red(),
131 ));
132 process::exit(1);
133 };
134 }
135 pb_download.set_style(finish_spinner_style.clone());
136 pb_download.finish_with_message(format!(
137 "{} Downloaded in {}",
138 ui::emoji::SUCCESS,
139 HumanDuration(download_started.elapsed())
140 ));
141
142 let pb_deploy = m.add(ProgressBar::new(0));
143 pb_deploy.set_style(progress_style.clone());
144
145 let pg_pwd = Uuid::new_v4();
146 let builder_env = vec![
147 "ILLA_SERVER_MODE=release".to_string(),
148 "ILLA_DEPLOY_MODE=self-host".to_string(),
149 format!("POSTGRES_PASSWORD={pg_pwd}"),
150 ];
151 let mut builder_labels = HashMap::new();
152 builder_labels.insert(
153 "maintainer".to_string(),
154 "opensource@illasoft.com".to_string(),
155 );
156 builder_labels.insert("license".to_string(), "Apache-2.0".to_string());
157 let mut builder_port_bindings = HashMap::new();
158 builder_port_bindings.insert(
159 "2022/tcp".to_string(),
160 Some(vec![PortBinding {
161 host_port: Some(port.to_string()),
162 host_ip: Some("0.0.0.0".to_string()),
163 }]),
164 );
165
166 let local_dir = utils::local_bind_init(mount_path);
167 let mounts = vec![Mount {
168 target: Some("/opt/illa/database".to_string()),
169 source: Some(local_dir),
170 typ: Some(MountTypeEnum::BIND),
171 read_only: Some(false),
172 ..Default::default()
173 }];
174
175 let builder_config = Config {
176 image: Some(builder_image),
177 env: Some(builder_env),
178 labels: Some(builder_labels),
179 host_config: Some(HostConfig {
180 port_bindings: Some(builder_port_bindings),
181 mounts: Some(mounts),
182 ..Default::default()
183 }),
184 ..Default::default()
185 };
186
187 let create_builder = &_docker
188 .create_container(
189 Some(CreateContainerOptions {
190 name: "illa_builder",
191 }),
192 builder_config,
193 )
194 .await;
195
196 let start_builder = &_docker
197 .start_container("illa_builder", None::<StartContainerOptions<String>>)
198 .await;
199
200 match (create_builder.is_err(), start_builder.is_err()) {
201 (true, _) => {
202 pb_deploy.set_style(finish_spinner_style.clone());
203 pb_deploy.finish_with_message(format!(
204 "{} {} {}",
205 ui::emoji::FAIL,
206 String::from("Create ILLA Builder error:"),
207 style(create_builder.as_ref().err().unwrap()).red(),
208 ));
209 process::exit(1);
210 }
211 (false, true) => {
212 pb_deploy.set_style(finish_spinner_style.clone());
213 pb_deploy.finish_with_message(format!(
214 "{} {} {}",
215 ui::emoji::FAIL,
216 String::from("Start ILLA Builder error:"),
217 style(start_builder.as_ref().err().unwrap()).red(),
218 ));
219 process::exit(1);
220 }
221 _ => {
222 pb_deploy.set_style(finish_spinner_style.clone());
223 pb_deploy.finish_with_message(format!(
224 "{} {} {}",
225 ui::emoji::SPARKLE,
226 String::from("ILLA Builder started, please visit"),
227 style(format!("{}:{}", "http://localhost", port)).blue(),
228 ));
229 process::exit(0);
230 }
231 };
232
233 Ok(())
234}
235
236async fn deploy_cloud(progress_style: ProgressStyle) -> Result {
237 println!("{} Looking forward to onboarding you!", ui::emoji::DIAMOND);
238
239 Ok(())
240}