risedev/task/
docker_service.rs1use std::env;
16use std::path::Path;
17use std::process::{Command, Stdio};
18
19use anyhow::{Context, Result};
20
21use super::{ExecuteContext, Task};
22use crate::DummyService;
23
24pub trait DockerServiceConfig: Send + 'static {
29 fn id(&self) -> String;
31
32 fn is_user_managed(&self) -> bool;
37
38 fn image(&self) -> String;
40
41 fn args(&self) -> Vec<String> {
43 vec![]
44 }
45
46 fn envs(&self) -> Vec<(String, String)> {
48 vec![]
49 }
50
51 fn ports(&self) -> Vec<(String, String)> {
56 vec![]
57 }
58
59 fn data_path(&self) -> Option<String> {
63 None
64 }
65}
66
67pub struct DockerService<B> {
69 config: B,
70}
71
72impl<B> DockerService<B>
73where
74 B: DockerServiceConfig,
75{
76 pub fn new(config: B) -> Self {
77 Self { config }
78 }
79
80 fn check_image_exists(&self) -> bool {
86 Command::new("docker")
87 .arg("image")
88 .arg("inspect")
89 .arg(self.config.image())
90 .stdout(Stdio::null())
91 .stderr(Stdio::null())
92 .status()
93 .map(|status| status.success())
94 .unwrap_or(false)
95 }
96
97 fn docker_pull(&self) -> Command {
98 let mut cmd = Command::new("docker");
99 cmd.arg("pull").arg(self.config.image());
100 cmd
101 }
102
103 fn docker_run(&self) -> Result<Command> {
104 let mut cmd = Command::new("docker");
105 cmd.arg("run")
106 .arg("--rm")
107 .arg("--name")
108 .arg(format!("risedev-{}", self.id()))
109 .arg("--add-host")
110 .arg("host.docker.internal:host-gateway");
111
112 for (k, v) in self.config.envs() {
113 cmd.arg("-e").arg(format!("{k}={v}"));
114 }
115 for (container, host) in self.config.ports() {
116 cmd.arg("-p").arg(format!("{container}:{host}"));
117 }
118
119 if let Some(data_path) = self.config.data_path() {
120 let path = Path::new(&env::var("PREFIX_DATA")?).join(self.id());
121 fs_err::create_dir_all(&path)?;
122 cmd.arg("-v")
123 .arg(format!("{}:{}", path.to_string_lossy(), data_path));
124 }
125
126 cmd.arg(self.config.image());
127
128 cmd.args(self.config.args());
129
130 Ok(cmd)
131 }
132}
133
134impl<B> Task for DockerService<B>
135where
136 B: DockerServiceConfig,
137{
138 fn execute(&mut self, ctx: &mut ExecuteContext<impl std::io::Write>) -> anyhow::Result<()> {
139 if self.config.is_user_managed() {
140 return DummyService::new(&self.id()).execute(ctx);
141 }
142
143 ctx.service(self);
144
145 check_docker_installed()?;
146
147 if !self.check_image_exists() {
148 ctx.pb
149 .set_message(format!("pulling image `{}`...", self.config.image()));
150 ctx.run_command(self.docker_pull())?;
151 }
152
153 ctx.pb.set_message("starting...");
154 ctx.run_command(ctx.tmux_run(self.docker_run()?)?)?;
155
156 ctx.pb.set_message("started");
157
158 Ok(())
159 }
160
161 fn id(&self) -> String {
162 self.config.id()
163 }
164}
165
166fn check_docker_installed() -> Result<()> {
167 Command::new("docker")
168 .arg("--version")
169 .stdout(Stdio::null())
170 .stderr(Stdio::null())
171 .status()
172 .map_err(anyhow::Error::from)
173 .and_then(|status| status.exit_ok().map_err(anyhow::Error::from))
174 .context("service requires docker to be installed")
175}