Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "molnctl"
version = "0.7.0"
version = "0.8.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand Down
21 changes: 21 additions & 0 deletions src/api/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,30 @@ pub struct NonComposeManifest {
pub services: Vec<Container>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)]
pub struct Volume {
pub name: String,
}

impl Display for Volume {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.name)
}
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Tabled)]
pub struct ComposeService {
pub name: String,
#[serde(default)]
pub containers: DisplayVec<Container>,
#[serde(default)]
pub volumes: DisplayVec<Volume>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)]
pub struct VolumeMount {
pub volume_name: String,
pub path: String,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)]
Expand All @@ -116,6 +135,8 @@ pub struct Container {
pub secrets: IndexMap<String, String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ports: Vec<Port>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub volume_mounts: Vec<VolumeMount>,
}

impl Display for Container {
Expand Down
19 changes: 16 additions & 3 deletions src/commands/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ impl ComposeBuilder {
image: full_image,
container_type: String::new(),
shared_volume_path: String::new(),
volume_mounts: vec![],
ports: vec![Port {
target: container_port,
publish: Some(true),
Expand Down Expand Up @@ -462,6 +463,7 @@ impl ComposeBuilder {
let service = ComposeService {
name: service_name,
containers: DisplayVec(vec![container]),
volumes: DisplayVec(vec![]),
};

self.compose.services.push(service);
Expand Down Expand Up @@ -561,13 +563,22 @@ impl ImageName {
.0
.iter_mut()
.find(|c| c.name == *container_name);

match container {
Some(container) => {
container.image = full_image.clone();
println!("Updated container '{}' in service '{}'", container_name, self.service);
println!(
"Updated container '{}' in service '{}'",
container_name, self.service
);
}
None => {
return Err(anyhow!(
"Container '{}' not found in service '{}'",
container_name,
self.service
))
}
None => return Err(anyhow!("Container '{}' not found in service '{}'", container_name, self.service)),
}
} else {
// Update the image in the first container or create one if none exists
Expand All @@ -583,6 +594,7 @@ impl ImageName {
environment: IndexMap::new(),
secrets: IndexMap::new(),
command: Vec::new(),
volume_mounts: vec![],
});
}
}
Expand Down Expand Up @@ -738,6 +750,7 @@ pub fn read_manifest(path: &str) -> Result<ComposeFile> {
let new_service = ComposeService {
name: old_service.name,
containers: DisplayVec(vec![container]),
volumes: DisplayVec(vec![]),
};

new_services.push(new_service);
Expand Down
138 changes: 137 additions & 1 deletion src/manifest_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use std::io::Write;
use std::path::Path;
use tempfile::tempdir;

use crate::api::types::{ComposeService, Container, DisplayVec, NonComposeManifest, Port};
use crate::api::types::{
ComposeService, Container, DisplayVec, NonComposeManifest, Port, Volume, VolumeMount,
};
use crate::commands::services::{read_manifest, ComposeFile};

#[cfg(test)]
Expand Down Expand Up @@ -48,6 +50,7 @@ mod tests {
target: 80,
publish: Some(true),
}],
volume_mounts: vec![],
},
Container {
name: "api".to_string(),
Expand All @@ -65,6 +68,7 @@ mod tests {
target: 3000,
publish: Some(true),
}],
volume_mounts: vec![],
},
],
};
Expand All @@ -75,6 +79,7 @@ mod tests {
services: vec![
ComposeService {
name: "web".to_string(),
volumes: DisplayVec(vec![]),
containers: DisplayVec(vec![Container {
name: "main".to_string(),
image: "nginx:latest".to_string(),
Expand All @@ -87,10 +92,12 @@ mod tests {
target: 80,
publish: Some(true),
}],
volume_mounts: vec![],
}]),
},
ComposeService {
name: "api".to_string(),
volumes: DisplayVec(vec![]),
containers: DisplayVec(vec![
Container {
name: "main".to_string(),
Expand All @@ -108,6 +115,7 @@ mod tests {
target: 3000,
publish: Some(true),
}],
volume_mounts: vec![],
},
Container {
name: "redis".to_string(),
Expand All @@ -121,6 +129,7 @@ mod tests {
target: 6379,
publish: None,
}],
volume_mounts: vec![],
},
]),
},
Expand Down Expand Up @@ -204,6 +213,7 @@ mod tests {
target: 8080,
publish: Some(true),
}],
volume_mounts: vec![],
}],
};

Expand All @@ -212,6 +222,7 @@ mod tests {
version: 1,
services: vec![ComposeService {
name: "app".to_string(),
volumes: DisplayVec(vec![]),
containers: DisplayVec(vec![
Container {
name: "main".to_string(),
Expand All @@ -229,6 +240,7 @@ mod tests {
target: 8080,
publish: Some(true),
}],
volume_mounts: vec![],
},
Container {
// Added sidecar container
Expand All @@ -240,6 +252,7 @@ mod tests {
environment: IndexMap::new(),
secrets: IndexMap::new(),
ports: vec![],
volume_mounts: vec![],
},
]),
}],
Expand Down Expand Up @@ -325,4 +338,127 @@ mod tests {

Ok(())
}

// Test volumes and volume mounts
#[test]
fn test_volumes_and_mounts() -> Result<()> {
// Create a temporary directory
let temp_dir = tempdir()?;

// Create a manifest with volumes and volume mounts
let manifest = ComposeFile {
version: 1,
services: vec![ComposeService {
name: "app".to_string(),
volumes: DisplayVec(vec![
Volume {
name: "app_data".to_string(),
},
Volume {
name: "shared_logs".to_string(),
},
]),
containers: DisplayVec(vec![
Container {
name: "main".to_string(),
image: "app:latest".to_string(),
container_type: "main".to_string(),
shared_volume_path: "/app/data".to_string(),
command: vec![],
environment: IndexMap::new(),
secrets: IndexMap::new(),
ports: vec![],
volume_mounts: vec![
VolumeMount {
volume_name: "app_data".to_string(),
path: "/app/data".to_string(),
},
VolumeMount {
volume_name: "./logs".to_string(),
path: "/app/logs".to_string(),
},
],
},
Container {
name: "sidecar".to_string(),
image: "logger:latest".to_string(),
container_type: "helper".to_string(),
shared_volume_path: "/logs".to_string(),
command: vec![],
environment: IndexMap::new(),
secrets: IndexMap::new(),
ports: vec![],
volume_mounts: vec![
VolumeMount {
volume_name: "shared_logs".to_string(),
path: "/logs".to_string(),
},
VolumeMount {
volume_name: "./logs".to_string(),
path: "/backup".to_string(),
},
],
},
]),
}],
};

// Write manifest to a temporary file
let manifest_path = write_temp_yaml(&manifest, &temp_dir, "volumes_test.yaml")?;

// Read the manifest back
let read_manifest = read_manifest(&manifest_path)?;

// Verify the volumes were parsed correctly
assert_eq!(read_manifest.services.len(), 1);
let app_service = &read_manifest.services[0];
assert_eq!(app_service.name, "app");

// Check volumes at service level
assert_eq!(app_service.volumes.0.len(), 2);
assert!(app_service.volumes.0.iter().any(|v| v.name == "app_data"));
assert!(app_service
.volumes
.0
.iter()
.any(|v| v.name == "shared_logs"));

// Check main container volume mounts
let main_container = app_service
.containers
.0
.iter()
.find(|c| c.name == "main")
.unwrap();
assert_eq!(main_container.volume_mounts.len(), 2);
assert!(main_container
.volume_mounts
.iter()
.any(|vm| vm.volume_name == "app_data" && vm.path == "/app/data"));
assert!(main_container
.volume_mounts
.iter()
.any(|vm| vm.volume_name == "./logs" && vm.path == "/app/logs"));
assert_eq!(main_container.shared_volume_path, "/app/data");

// Check sidecar container volume mounts
let sidecar_container = app_service
.containers
.0
.iter()
.find(|c| c.name == "sidecar")
.unwrap();
assert_eq!(sidecar_container.volume_mounts.len(), 2);
assert!(sidecar_container
.volume_mounts
.iter()
.any(|vm| vm.volume_name == "shared_logs" && vm.path == "/logs"));
assert!(sidecar_container
.volume_mounts
.iter()
.any(|vm| vm.volume_name == "./logs" && vm.path == "/backup"));
assert_eq!(sidecar_container.shared_volume_path, "/logs");

Ok(())
}
}