risedev/task/
mongodb_service.rs1use std::process::Command;
16
17use anyhow::{Context, Result, anyhow};
18
19use super::docker_service::{DockerService, DockerServiceConfig};
20use crate::MongoDbConfig;
21use crate::task::{ExecuteContext, Task};
22
23const REPLICA_SET_NAME: &str = "rs0";
24
25impl DockerServiceConfig for MongoDbConfig {
26 fn id(&self) -> String {
27 self.id.clone()
28 }
29
30 fn is_user_managed(&self) -> bool {
31 self.user_managed
32 }
33
34 fn image(&self) -> String {
35 self.image.clone()
36 }
37
38 fn args(&self) -> Vec<String> {
39 vec![
40 "--replSet".to_owned(),
41 REPLICA_SET_NAME.to_owned(),
42 "--bind_ip_all".to_owned(),
43 "--oplogSize".to_owned(),
44 "128".to_owned(),
45 ]
46 }
47
48 fn ports(&self) -> Vec<(String, String)> {
49 vec![(self.port.to_string(), "27017".to_owned())]
50 }
51
52 fn data_path(&self) -> Option<String> {
53 self.persist_data.then(|| "/data/db".to_owned())
54 }
55}
56
57pub type MongoDbService = DockerService<MongoDbConfig>;
58
59pub struct MongoDbSetupTask {
60 config: MongoDbConfig,
61}
62
63impl MongoDbSetupTask {
64 pub fn new(config: MongoDbConfig) -> Self {
65 Self { config }
66 }
67
68 fn container_name(&self) -> String {
69 format!("risedev-{}", self.config.id)
70 }
71
72 fn mongosh_eval(&self, script: &str) -> Result<std::process::Output> {
73 let output = Command::new("docker")
74 .arg("exec")
75 .arg(self.container_name())
76 .arg("mongosh")
77 .arg("--quiet")
78 .arg("--eval")
79 .arg(script)
80 .output()
81 .context("failed to run mongosh in mongodb container")?;
82 Ok(output)
83 }
84
85 fn wait_replica_set_ready(&self, ctx: &mut ExecuteContext<impl std::io::Write>) -> Result<()> {
86 ctx.pb.set_message("waiting for replica set ready...");
87 ctx.wait(|| {
88 let output = self.mongosh_eval(
89 r#"
90try {
91 const status = rs.status();
92 if (status.ok === 1) {
93 quit(0);
94 }
95 quit(1);
96} catch (err) {
97 if (err.codeName === "NotYetInitialized") {
98 quit(2);
99 }
100 print(err);
101 quit(3);
102}
103"#,
104 )?;
105 match output.status.code() {
106 Some(0) => Ok(()),
107 Some(1) | Some(2) => Err(anyhow!("mongodb replica set is not ready yet")),
108 _ => {
109 let stderr = String::from_utf8_lossy(&output.stderr);
110 let stdout = String::from_utf8_lossy(&output.stdout);
111 Err(anyhow!(
112 "failed to check mongodb replica set status: {}{}",
113 stdout.trim(),
114 stderr.trim()
115 ))
116 }
117 }
118 })?;
119 Ok(())
120 }
121}
122
123impl Task for MongoDbSetupTask {
124 fn execute(&mut self, ctx: &mut ExecuteContext<impl std::io::Write>) -> Result<()> {
125 if self.config.user_managed {
126 return Ok(());
127 }
128
129 let member = format!("{}:{}", self.config.address, self.config.port);
130 let init_script = format!(
131 r#"
132try {{
133 const status = rs.status();
134 if (status.ok === 1) {{
135 quit(0);
136 }}
137}} catch (err) {{
138 if (err.codeName !== "NotYetInitialized") {{
139 print(err);
140 quit(2);
141 }}
142}}
143
144const result = rs.initiate({{
145 _id: "{REPLICA_SET_NAME}",
146 members: [{{ _id: 0, host: "{member}" }}]
147}});
148
149if (result.ok === 1) {{
150 quit(0);
151}}
152
153printjson(result);
154quit(1);
155"#
156 );
157
158 ctx.pb.set_message("initializing replica set...");
159 ctx.wait(|| {
160 let output = self.mongosh_eval(&init_script)?;
161 if output.status.success() {
162 return Ok(());
163 }
164
165 let stderr = String::from_utf8_lossy(&output.stderr);
166 let stdout = String::from_utf8_lossy(&output.stdout);
167 Err(anyhow!(
168 "failed to initialize mongodb replica set: {}{}",
169 stdout.trim(),
170 stderr.trim()
171 ))
172 })?;
173
174 self.wait_replica_set_ready(ctx)?;
175 Ok(())
176 }
177
178 fn id(&self) -> String {
179 self.config.id.clone()
180 }
181}