use bollard::models::ContainerInspectResponse;
use semver::Version;
use crate::models::{
CreationSource, EnvironmentVariables, GetLocalDeploymentLabelsError,
GetMongoDBPortBindingError, GetStateError, LocalDeploymentLabels, MongoDBPortBinding,
MongodbType, State,
};
pub const LOCAL_SEED_LOCATION: &str = "/docker-entrypoint-initdb.d";
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Deployment {
pub container_id: String,
pub name: Option<String>,
pub state: State,
pub port_bindings: Option<MongoDBPortBinding>,
pub mongodb_type: MongodbType,
pub mongodb_version: Version,
pub creation_source: Option<CreationSource>,
pub local_seed_location: Option<String>,
pub mongodb_initdb_database: Option<String>,
pub mongodb_initdb_root_password_file: Option<String>,
pub mongodb_initdb_root_password: Option<String>,
pub mongodb_initdb_root_username_file: Option<String>,
pub mongodb_initdb_root_username: Option<String>,
pub mongodb_load_sample_data: Option<bool>,
pub voyage_api_key: Option<String>,
pub mongot_log_file: Option<String>,
pub runner_log_file: Option<String>,
pub do_not_track: bool,
pub telemetry_base_url: Option<String>,
}
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum IntoDeploymentError {
#[error("Container ID is missing")]
MissingContainerID,
#[error(transparent)]
LocalDeploymentLabels(#[from] GetLocalDeploymentLabelsError),
#[error(transparent)]
MongoDBPortBinding(#[from] GetMongoDBPortBindingError),
#[error(transparent)]
State(#[from] GetStateError),
}
impl TryFrom<ContainerInspectResponse> for Deployment {
type Error = IntoDeploymentError;
fn try_from(value: ContainerInspectResponse) -> Result<Self, Self::Error> {
let container_id = value
.id
.as_ref()
.ok_or(IntoDeploymentError::MissingContainerID)?
.clone();
let name = value
.name
.as_ref()
.and_then(|n| n.strip_prefix('/'))
.map(|n| n.to_string());
let container_labels = LocalDeploymentLabels::try_from(&value)?;
let container_environment_variables = EnvironmentVariables::from(&value);
let local_seed_location = extract_local_seed_location(&value);
let port_bindings = MongoDBPortBinding::try_from(&value)?;
let state = State::try_from(&value)?;
let LocalDeploymentLabels {
mongodb_version,
mongodb_type,
} = container_labels;
let EnvironmentVariables {
tool,
runner_log_file,
mongodb_initdb_root_username,
mongodb_initdb_root_username_file,
mongodb_initdb_root_password,
mongodb_initdb_root_password_file,
mongodb_initdb_database,
mongodb_load_sample_data,
mongot_log_file,
do_not_track,
telemetry_base_url,
voyage_api_key,
} = container_environment_variables;
Ok(Self {
name,
container_id,
state,
port_bindings,
mongodb_type,
mongodb_version,
creation_source: tool,
local_seed_location,
mongodb_initdb_database,
mongodb_initdb_root_password_file,
mongodb_initdb_root_password,
mongodb_initdb_root_username_file,
mongodb_initdb_root_username,
mongodb_load_sample_data: mongodb_load_sample_data.map(is_seeding_true),
voyage_api_key,
mongot_log_file,
runner_log_file,
do_not_track: do_not_track.is_some(),
telemetry_base_url,
})
}
}
fn extract_local_seed_location(
container_inspect_response: &ContainerInspectResponse,
) -> Option<String> {
let mount = container_inspect_response
.mounts
.as_ref()?
.iter()
.find(|m| m.destination.as_deref() == Some(LOCAL_SEED_LOCATION))?;
mount.source.clone()
}
fn is_seeding_true(value: impl AsRef<str>) -> bool {
let value = value.as_ref();
if let Ok(value) = value.to_ascii_lowercase().parse::<bool>() {
return value;
}
if let Ok(value) = value.parse::<i32>() {
return value > 0;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use bollard::models::{
ContainerConfig, ContainerState, ContainerStateStatusEnum, MountPoint, NetworkSettings,
PortBinding,
};
use std::collections::HashMap;
#[test]
fn test_into_deployment() {
let mut labels = HashMap::new();
labels.insert("mongodb-atlas-local".to_string(), "container".to_string());
labels.insert("version".to_string(), "8.0.0".to_string());
labels.insert("mongodb-type".to_string(), "community".to_string());
let env_vars = vec![
"TOOL=ATLASCLI".to_string(),
"MONGODB_INITDB_ROOT_USERNAME=admin".to_string(),
"MONGODB_INITDB_ROOT_USERNAME_FILE=/run/secrets/username".to_string(),
"MONGODB_INITDB_ROOT_PASSWORD=password123".to_string(),
"MONGODB_INITDB_ROOT_PASSWORD_FILE=/run/secrets/password".to_string(),
"MONGODB_INITDB_DATABASE=testdb".to_string(),
"RUNNER_LOG_FILE=/tmp/runner.log".to_string(),
"MONGOT_LOG_FILE=/tmp/mongot.log".to_string(),
"TELEMETRY_BASE_URL=https://telemetry.example.com".to_string(),
"MONGODB_LOAD_SAMPLE_DATA=true".to_string(),
"VOYAGE_API_KEY=voyage-api-key".to_string(),
];
let mount = MountPoint {
destination: Some("/docker-entrypoint-initdb.d".to_string()),
source: Some("/host/seed-data".to_string()),
..Default::default()
};
let container_state = ContainerState {
status: Some(ContainerStateStatusEnum::RUNNING),
..Default::default()
};
let port_binding = PortBinding {
host_ip: Some("127.0.0.1".to_string()),
host_port: Some("27017".to_string()),
};
let mut port_map = HashMap::new();
port_map.insert("27017/tcp".to_string(), Some(vec![port_binding]));
let network_settings = NetworkSettings {
ports: Some(port_map),
..Default::default()
};
let container_inspect_response = ContainerInspectResponse {
id: Some("container_id".to_string()),
name: Some("/test-deployment".to_string()),
config: Some(ContainerConfig {
env: Some(env_vars),
labels: Some(labels),
..Default::default()
}),
mounts: Some(vec![mount]),
state: Some(container_state),
network_settings: Some(network_settings),
..Default::default()
};
let deployment = Deployment::try_from(container_inspect_response).unwrap();
assert_eq!(deployment.container_id, "container_id");
assert_eq!(deployment.name, Some("test-deployment".to_string()));
assert_eq!(deployment.state, State::Running);
assert!(deployment.port_bindings.is_some());
let port_binding = deployment.port_bindings.unwrap();
assert_eq!(port_binding.port, Some(27017));
assert_eq!(
port_binding.binding_type,
crate::models::BindingType::Loopback
);
assert_eq!(deployment.creation_source, Some(CreationSource::AtlasCLI));
assert_eq!(deployment.mongodb_type, MongodbType::Community);
assert_eq!(deployment.mongodb_version, Version::new(8, 0, 0));
assert_eq!(
deployment.local_seed_location,
Some("/host/seed-data".to_string())
);
assert_eq!(
deployment.mongodb_initdb_database,
Some("testdb".to_string())
);
assert_eq!(
deployment.mongodb_initdb_root_username,
Some("admin".to_string())
);
assert_eq!(
deployment.mongodb_initdb_root_username_file,
Some("/run/secrets/username".to_string())
);
assert_eq!(
deployment.mongodb_initdb_root_password,
Some("password123".to_string())
);
assert_eq!(
deployment.mongodb_initdb_root_password_file,
Some("/run/secrets/password".to_string())
);
assert_eq!(
deployment.runner_log_file,
Some("/tmp/runner.log".to_string())
);
assert_eq!(
deployment.mongot_log_file,
Some("/tmp/mongot.log".to_string())
);
assert_eq!(deployment.do_not_track, false);
assert_eq!(
deployment.telemetry_base_url,
Some("https://telemetry.example.com".to_string())
);
assert_eq!(deployment.mongodb_load_sample_data, Some(true));
assert_eq!(
deployment.voyage_api_key,
Some("voyage-api-key".to_string())
);
}
#[test]
fn test_extract_local_seed_location_no_mounts() {
let container_inspect_response = ContainerInspectResponse {
mounts: None,
..Default::default()
};
let result = extract_local_seed_location(&container_inspect_response);
assert_eq!(result, None);
}
#[test]
fn test_extract_local_seed_location_empty_mounts() {
let container_inspect_response = ContainerInspectResponse {
mounts: Some(vec![]),
..Default::default()
};
let result = extract_local_seed_location(&container_inspect_response);
assert_eq!(result, None);
}
#[test]
fn test_extract_local_seed_location_no_matching_mount() {
let mount1 = MountPoint {
destination: Some("/var/log".to_string()),
source: Some("/host/logs".to_string()),
..Default::default()
};
let mount2 = MountPoint {
destination: Some("/app/data".to_string()),
source: Some("/host/data".to_string()),
..Default::default()
};
let container_inspect_response = ContainerInspectResponse {
mounts: Some(vec![mount1, mount2]),
..Default::default()
};
let result = extract_local_seed_location(&container_inspect_response);
assert_eq!(result, None);
}
#[test]
fn test_extract_local_seed_location_matching_mount() {
let mount = MountPoint {
destination: Some(LOCAL_SEED_LOCATION.to_string()),
source: Some("/host/seed-data".to_string()),
..Default::default()
};
let container_inspect_response = ContainerInspectResponse {
mounts: Some(vec![mount]),
..Default::default()
};
let result = extract_local_seed_location(&container_inspect_response);
assert_eq!(result, Some("/host/seed-data".to_string()));
}
#[test]
fn test_is_seeding_true() {
assert!(is_seeding_true("true"));
assert!(is_seeding_true("True"));
assert!(is_seeding_true("TRUE"));
assert!(is_seeding_true("1"));
assert!(is_seeding_true("2"));
assert!(!is_seeding_true("false"));
assert!(!is_seeding_true("0"));
assert!(!is_seeding_true("something else"));
}
}