1use crate::tui::{self, colors, white_confirmation};
2use anyhow::{bail, Context, Error};
3use colorful::{Color, Colorful};
4use simplelog::*;
5use spinoff::{spinners, Spinner};
6use std::io::{BufRead, BufReader};
7use std::path::Path;
8use std::process::Output;
9use std::process::{Command as ShellCommand, Stdio};
10use std::thread;
11
12pub struct Docker {}
13
14impl Docker {
15 pub fn info() -> Output {
16 ShellCommand::new("sh")
17 .arg("-c")
18 .arg("docker info")
19 .output()
20 .expect("failed to execute process")
21 }
22
23 pub fn installed_and_running() -> Result<(), anyhow::Error> {
24 info!("Checking requirements: [Docker]");
25
26 let output = Self::info();
27 let stdout = String::from_utf8(output.stdout).unwrap();
28 let stderr = String::from_utf8(output.stderr).unwrap();
29
30 if stdout.is_empty() && !stderr.is_empty() {
32 bail!("- Docker is not installed, please visit docker.com to install")
33 } else {
34 if !stdout.is_empty() && !stderr.is_empty() {
36 let connection_err = stderr.find("Cannot connect to the Docker daemon");
37
38 if connection_err.is_some() {
39 bail!("- Docker is not running, please start it and try again")
40 }
41 }
42 }
43
44 Ok(())
45 }
46
47 pub fn build(instance_name: String, verbose: bool) -> Result<(), anyhow::Error> {
48 let mut sp = if !verbose {
49 Some(Spinner::new(
50 spinners::Dots,
51 "Running Docker Build",
52 spinoff::Color::White,
53 ))
54 } else {
55 None
56 };
57
58 let mut show_message = |message: &str, new_spinner: bool| {
59 if let Some(mut spinner) = sp.take() {
60 spinner.stop_with_message(&format!(
61 "{} {}",
62 "✓".color(colors::indicator_good()).bold(),
63 message.color(Color::White).bold()
64 ));
65 if new_spinner {
66 sp = Some(Spinner::new(
67 spinners::Dots,
68 format!("Building container for {}", instance_name),
69 spinoff::Color::White,
70 ));
71 }
72 } else {
73 white_confirmation(message);
74 }
75 };
76
77 let command = format!(
78 "cd {} && docker build . -t postgres-{}",
79 instance_name, instance_name
80 );
81 run_command(&command, verbose)?;
82
83 show_message(
84 &format!("Docker Build completed for {}", instance_name),
85 false,
86 );
87
88 Ok(())
89 }
90
91 pub fn docker_compose_up(verbose: bool) -> Result<(), anyhow::Error> {
92 let mut sp = if !verbose {
93 Some(Spinner::new(
94 spinners::Dots,
95 "Running Docker Compose Up",
96 spinoff::Color::White,
97 ))
98 } else {
99 None
100 };
101
102 let mut show_message = |message: &str, new_spinner: bool| {
103 if let Some(mut spinner) = sp.take() {
104 spinner.stop_with_message(&format!(
105 "{} {}",
106 "✓".color(colors::indicator_good()).bold(),
107 message.color(Color::White).bold()
108 ));
109 if new_spinner {
110 sp = Some(Spinner::new(
111 spinners::Dots,
112 "Running docker compose up",
113 spinoff::Color::White,
114 ));
115 }
116 } else {
117 white_confirmation(message);
118 }
119 };
120
121 let command = "docker compose up -d --build";
122
123 if verbose {
124 run_command(command, verbose)?;
125 } else {
126 let output = match ShellCommand::new("sh").arg("-c").arg(command).output() {
127 Ok(output) => output,
128 Err(err) => {
129 return Err(Error::msg(format!("Issue starting the instances: {}", err)))
130 }
131 };
132 let stderr = String::from_utf8(output.stderr).unwrap();
133
134 if !output.status.success() {
135 tui::error(&format!(
136 "\nThere was an issue starting the instances: {}",
137 stderr
138 ));
139
140 return Err(Error::msg("Error running docker compose up!"));
141 }
142 }
143
144 show_message("Docker Compose Up completed", false);
145
146 Ok(())
147 }
148
149 pub fn docker_compose_down(verbose: bool) -> Result<(), anyhow::Error> {
150 let path: &Path = Path::new("docker-compose.yml");
151 if !path.exists() {
152 if verbose {
153 println!(
154 "{} {}",
155 "✓".color(colors::indicator_good()).bold(),
156 "No docker-compose.yml found in the directory"
157 .color(Color::White)
158 .bold()
159 )
160 }
161 return Ok(());
162 }
163
164 let mut sp = Spinner::new(
165 spinners::Dots,
166 "Running Docker Compose Down",
167 spinoff::Color::White,
168 );
169
170 let command: String = String::from("docker compose down");
171
172 let output = match ShellCommand::new("sh").arg("-c").arg(&command).output() {
173 Ok(output) => output,
174 Err(_) => {
175 sp.stop_with_message("- Tembo instances failed to stop & remove");
176 bail!("There was an issue stopping the instances")
177 }
178 };
179
180 sp.stop_with_message(&format!(
181 "{} {}",
182 "✓".color(colors::indicator_good()).bold(),
183 "Tembo instances stopped & removed"
184 .color(Color::White)
185 .bold()
186 ));
187
188 let stderr = String::from_utf8(output.stderr).unwrap();
189
190 if !output.status.success() {
191 tui::error(&format!(
192 "\nThere was an issue stopping the instances: {}",
193 stderr
194 ));
195
196 return Err(Error::msg("Error running docker compose down!"));
197 }
198
199 Ok(())
200 }
201}
202
203pub fn run_command(command: &str, verbose: bool) -> Result<(), anyhow::Error> {
204 let mut child = ShellCommand::new("sh")
205 .arg("-c")
206 .arg(command)
207 .stdout(Stdio::piped())
208 .stderr(Stdio::piped())
209 .spawn()
210 .with_context(|| format!("Failed to spawn command '{}'", command))?;
211
212 if verbose {
213 let stdout = BufReader::new(child.stdout.take().expect("Failed to open stdout"));
214 let stderr = BufReader::new(child.stderr.take().expect("Failed to open stderr"));
215 let stdout_handle = thread::spawn(move || {
216 for line in stdout.lines() {
217 match line {
218 Ok(line) => println!("{}", line),
219 Err(e) => eprintln!("Error reading line from stdout: {}", e),
220 }
221 }
222 });
223 let stderr_handle = thread::spawn(move || {
224 for line in stderr.lines() {
225 match line {
226 Ok(line) => eprintln!("{}", line),
227 Err(e) => eprintln!("Error reading line from stderr: {}", e),
228 }
229 }
230 });
231
232 stdout_handle.join().expect("Stdout thread panicked");
233 stderr_handle.join().expect("Stderr thread panicked");
234 }
235
236 let status = child.wait().expect("Failed to wait on child");
237
238 if !status.success() {
239 return Err(Error::msg("Command executed with failures!"));
240 }
241
242 Ok(())
243}
244
245#[cfg(test)]
246mod tests {
247 #[test]
248 #[ignore] fn docker_installed_and_running_test() {
250 }
254}