1#[macro_use]
2mod util;
3
4mod client;
5pub mod error;
6mod fs;
7pub mod os;
8pub mod process;
9mod url;
10
11use std::{ffi::OsString, io::ErrorKind::NotFound, process::Child};
12
13use lazy_static::lazy_static;
14use regex::Regex;
15use semver::Version;
16use soup::{NodeExt, QueryBuilderExt, Soup};
17
18use crate::{
19 client::HttpClient,
20 error::{Error, Result},
21 fs::Fs,
22 os::OperatingSystem,
23 process::{exec_command, run_background_command},
24 util::{parse_major_minor_version, select_newer_version},
25};
26
27const MONGODB_VERSION_LIST_URL: &str = "https://dl.mongodb.org/dl/src";
28
29lazy_static! {
30 static ref MONGODB_SEMVER_REGEX: Regex =
31 Regex::new(r"src/mongodb-src-r(\d+\.\d+\.\d+)\.tar\.gz$").unwrap();
32}
33
34#[derive(Debug)]
35pub struct Monger {
36 client: HttpClient,
37 fs: Fs,
38}
39
40#[derive(Debug)]
41pub struct LogFile {
42 pub cluster_id: String,
43 pub port: u16,
44 pub node_type: LogFileType,
45}
46
47#[derive(Clone, Copy, Debug)]
48pub enum LogFileType {
49 DataNode,
50 ShardingRouter,
51 ShardNode { shard_num: usize },
52 ConfigServer,
53}
54
55impl Monger {
56 pub fn new() -> Result<Self> {
57 Ok(Self {
58 client: HttpClient::new()?,
59 fs: Fs::builder().build()?,
60 })
61 }
62
63 pub fn clear_database_files(&self, version_str: &str) -> Result<bool> {
64 self.fs.clear_db_dir(version_str)
65 }
66
67 pub fn clear_cluster_logs(&self, cluster_id: &str) -> Result<bool> {
68 self.fs.clear_cluster_logs(cluster_id)
69 }
70
71 pub fn clear_default_args(&self) -> Result<bool> {
72 self.fs.clear_default_args()
73 }
74
75 pub fn download_mongodb_version_from_url(
76 &self,
77 url: &str,
78 id: &str,
79 force: bool,
80 ) -> Result<()> {
81 if self.fs.version_exists(id) {
82 if force {
83 self.delete_mongodb_version(id)?;
84 } else {
85 return Err(Error::ExistingId { id: id.into() });
86 }
87 }
88
89 let dir = format!("custom-download-{}", id);
90 let file = format!("{}.tgz", dir);
91 let data = self.client.download_url(&url)?;
92
93 self.fs
94 .write_mongodb_download(&file, &dir, &data[..], &id)?;
95
96 Ok(())
97 }
98
99 pub fn download_mongodb_version(
100 &self,
101 version_str: &str,
102 force: bool,
103 os: Option<&str>,
104 id: Option<&str>,
105 ) -> Result<()> {
106 let version = if version_str == "latest" {
107 self.find_latest_mongodb_version()?
108 } else if let Some((major, minor)) = parse_major_minor_version(version_str) {
109 self.find_latest_matching_version(major, minor)?
110 } else {
111 crate::util::parse_version(version_str)?
112 };
113
114 let id = id
115 .map(ToString::to_string)
116 .unwrap_or_else(|| version.to_string());
117
118 if self.fs.version_exists(&id) {
119 if force {
120 self.delete_mongodb_version(&id)?;
121 } else {
122 return Ok(());
123 }
124 }
125
126 let os = if let Some(os_name) = os {
127 OperatingSystem::from_name(os_name).unwrap()
128 } else {
129 OperatingSystem::get(&version)?
130 };
131
132 let url = os.download_url(&version);
133 let file = url.filename();
134 let dir = url.dirname();
135 let url: String = url.into();
136 let data = self.client.download_version(&url, &version_str)?;
137
138 self.fs
139 .write_mongodb_download(&file, &dir, &data[..], &id)?;
140
141 Ok(())
142 }
143
144 fn find_latest_matching_version(&self, major: u64, minor: u64) -> Result<Version> {
145 let response = self.client.get(MONGODB_VERSION_LIST_URL)?;
146 let soup = Soup::from_reader(response)?;
147
148 let matches = soup
149 .tag("a")
150 .attr("href", MONGODB_SEMVER_REGEX.clone())
151 .find_all()
152 .map(|item| {
153 Version::parse(
157 &*MONGODB_SEMVER_REGEX
158 .captures(&item.text())
159 .unwrap()
160 .get(1)
161 .unwrap()
162 .as_str(),
163 )
164 .unwrap()
165 });
166
167 for version in matches {
168 if major == version.major && minor == version.minor {
169 return Ok(version);
170 }
171 }
172
173 Err(Error::VersionNotFound {
174 version: format!("{}.{}", major, minor),
175 })
176 }
177
178 fn find_latest_mongodb_version(&self) -> Result<Version> {
179 let response = self.client.get(MONGODB_VERSION_LIST_URL)?;
180 let soup = Soup::from_reader(response)?;
181
182 let mut newest_stable = None;
183 let mut newest_dev = None;
184
185 for version in soup
186 .tag("a")
187 .attr("href", MONGODB_SEMVER_REGEX.clone())
188 .find_all()
189 .map(|item| {
190 Version::parse(
194 &*MONGODB_SEMVER_REGEX
195 .captures(&item.text())
196 .unwrap()
197 .get(1)
198 .unwrap()
199 .as_str()
200 .to_string(),
201 )
202 .unwrap()
203 })
204 {
205 if version.minor % 2 == 0 {
206 newest_stable = Some(select_newer_version(newest_stable, version));
207 } else {
208 newest_dev = Some(version);
209 }
210
211 if let Some(ref stable_version) = newest_stable {
215 if let Some(ref dev_version) = newest_dev {
216 if dev_version.major == stable_version.major
217 && dev_version.minor == stable_version.minor + 1
218 {
219 return Ok(newest_stable.unwrap());
220 }
221 }
222 }
223 }
224
225 if let Some(version) = newest_stable {
226 Ok(version)
227 } else {
228 Err(Error::InvalidHtml {
229 url: MONGODB_VERSION_LIST_URL.to_string(),
230 })
231 }
232 }
233
234 pub fn delete_mongodb_version(&self, version: &str) -> Result<()> {
235 if self.fs.delete_mongodb_version(version)? {
236 println!("Deleted version {}", version);
237 }
238
239 Ok(())
240 }
241
242 pub fn get_default_args(&self) -> Result<Option<String>> {
243 self.fs.get_default_args()
244 }
245
246 pub fn list_versions(&self) -> Result<Vec<OsString>> {
247 self.fs.list_versions()
248 }
249
250 pub fn prune(&self) -> Result<()> {
251 self.fs.prune()
252 }
253
254 fn process_args(&self, args: Vec<OsString>, version: &str) -> Result<Vec<OsString>> {
255 let mut args = args.into_iter();
256
257 let mut processed_args = Vec::new();
258 let mut found_dbpath = false;
259
260 while let Some(arg) = args.next() {
261 if arg.as_os_str() == "--dbpath" {
262 processed_args.push(arg);
263 found_dbpath = true;
264 break;
265 }
266
267 processed_args.push(arg);
268 }
269
270 if found_dbpath {
271 processed_args.extend(args);
272 } else {
273 processed_args.push("--dbpath".into());
274 processed_args.push(self.fs.create_or_get_db_dir(version)?.into_os_string());
275 }
276
277 Ok(processed_args)
278 }
279
280 pub fn set_default_args(&self, default_args: &str) -> Result<()> {
281 self.fs.set_default_args(default_args)
282 }
283
284 pub fn start_mongod(
285 &self,
286 args: Vec<OsString>,
287 version: &str,
288 exec: bool,
289 save_log: Option<LogFile>,
290 ) -> Result<Child> {
291 let mut processed_args = self.process_args(args, version)?;
292
293 if let Some(default_args) = self.fs.get_default_args()? {
294 processed_args.extend(default_args.split_whitespace().map(Into::into));
295 }
296
297 if let Some(log_file) = save_log {
298 processed_args.push("--logpath".into());
299 processed_args.push(
300 self.fs
301 .get_and_create_log_file_path(
302 &log_file.cluster_id,
303 log_file.port,
304 log_file.node_type,
305 )?
306 .into(),
307 );
308 }
309
310 if exec {
311 Err(self.exec_command("mongod", processed_args, version))
312 } else {
313 self.run_background_command("mongod", processed_args, version)
314 }
315 }
316
317 pub fn start_mongos(
318 &self,
319 mut args: Vec<OsString>,
320 version: &str,
321 exec: bool,
322 save_log: Option<LogFile>,
323 ) -> Result<Child> {
324 if let Some(log_file) = save_log {
325 args.push("--logpath".into());
326 args.push(
327 self.fs
328 .get_and_create_log_file_path(
329 &log_file.cluster_id,
330 log_file.port,
331 log_file.node_type,
332 )?
333 .into(),
334 );
335 }
336
337 if exec {
338 Err(self.exec_command("mongos", args, version))
339 } else {
340 self.run_background_command("mongos", args, version)
341 }
342 }
343
344 pub fn run_background_command(
345 &self,
346 binary_name: &str,
347 args: Vec<OsString>,
348 version: &str,
349 ) -> Result<Child> {
350 if version == "system" {
351 return run_background_command(binary_name, args, std::env::current_dir()?);
352 }
353
354 let result =
355 self.fs
356 .run_background_command(binary_name, args.into_iter().collect(), version);
357
358 match result {
359 Err(Error::Io { ref inner }) if inner.kind() == NotFound => {
360 Err(Error::BinaryNotFound {
361 binary: binary_name.into(),
362 version: version.into(),
363 })
364 }
365 other => other,
366 }
367 }
368
369 pub fn exec_command(&self, binary_name: &str, args: Vec<OsString>, version: &str) -> Error {
370 let dir = match std::env::current_dir() {
371 Ok(dir) => dir,
372 Err(e) => return e.into(),
373 };
374
375 let error = if version == "system" {
376 exec_command(binary_name, args, dir)
377 } else {
378 self.fs
379 .exec_command(binary_name, args.into_iter().collect(), version)
380 };
381
382 match error {
383 Error::Io { ref inner } if inner.kind() == NotFound => Error::BinaryNotFound {
384 binary: binary_name.into(),
385 version: version.into(),
386 },
387 other => other,
388 }
389 }
390}