Allow multiple networks and network groups
This commit is contained in:
36
Cargo.lock
generated
36
Cargo.lock
generated
@@ -54,9 +54,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.41"
|
||||
version = "4.5.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
|
||||
checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -64,9 +64,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.41"
|
||||
version = "4.5.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
|
||||
checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -76,9 +76,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.41"
|
||||
version = "4.5.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
|
||||
checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -98,6 +98,12 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -132,6 +138,15 @@ version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.1"
|
||||
@@ -143,6 +158,7 @@ name = "podman-openrc"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"itertools",
|
||||
"serde",
|
||||
"toml",
|
||||
]
|
||||
@@ -213,9 +229,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.2"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac"
|
||||
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -237,9 +253,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30"
|
||||
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
@@ -12,6 +12,7 @@ repository = "https://git.plabble.org/Maurice/podman-openrc"
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.41", features = ["derive"] }
|
||||
clap = { version = "4.5.45", features = ["derive"] }
|
||||
itertools = "0.14.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.9.2"
|
||||
toml = "0.9.5"
|
||||
|
||||
14
README.md
14
README.md
@@ -24,7 +24,6 @@ capabilities = ["NET_BIND_SERVICE"] # Optional property, add Linux capabilities
|
||||
[service]
|
||||
name = "<CONTAINER NAME>" # Container name, required
|
||||
image = "<IMAGE>" # Podman image name
|
||||
networks = ["<NETWORK>"] # Optional, if you want to run the container within a specific network. Set to "host" if you don't want to use the podman networking.
|
||||
depend = ["<SERVICE NAME>"] # Name of any service in /etc/init.d to depend on
|
||||
restart = "unless-stopped" # Restart, optional. Defaults to "unless-stopped"
|
||||
detach = true # Run container in detach mode, optional, default true. Recommended.
|
||||
@@ -36,6 +35,19 @@ command = "<COMMAND>" # Container command to run, optional.
|
||||
ASPNETCORE_ENVIRONMENT = "Test"
|
||||
# If you have a not TOML-compatible key name, use "" around the key name
|
||||
|
||||
# Optional, if you want to run the container within specific network(s). Set to "host" if you don't want to use the podman networking.
|
||||
[[networks]]
|
||||
name = "host"
|
||||
|
||||
# You can also create groups
|
||||
[[networks]]
|
||||
name = "netw-service-test"
|
||||
group = "http-networks"
|
||||
|
||||
# And assign ALL networks assigned to a group to a service
|
||||
[[networks]]
|
||||
group = "http-networks"
|
||||
|
||||
# Optionally, you can assign one or more port mappings
|
||||
[[ports]]
|
||||
host = 80 # Port on your computer
|
||||
|
||||
186
src/main.rs
186
src/main.rs
@@ -1,12 +1,20 @@
|
||||
use std::fs;
|
||||
use std::{collections::HashMap, fs, path::Path};
|
||||
|
||||
use clap::Parser;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::service::ServiceConfig;
|
||||
use crate::service::{NetworkMapping, ServiceConfig};
|
||||
|
||||
mod service;
|
||||
|
||||
pub fn generate_openrc(config: &ServiceConfig) -> String {
|
||||
let networks: Vec<String> = config.networks.clone()
|
||||
.into_iter()
|
||||
.filter(|n|n.name.is_some())
|
||||
.map(|n|n.name.unwrap())
|
||||
.dedup()
|
||||
.collect();
|
||||
|
||||
let mut script = String::from("#!/sbin/openrc-run\n# !!! AUTO GENERATED - DO NOT EDIT !!!\n\n");
|
||||
let wrap = |cmd: &str| {
|
||||
if let Some(user) = config.user.as_ref() {
|
||||
@@ -27,11 +35,11 @@ pub fn generate_openrc(config: &ServiceConfig) -> String {
|
||||
// start_pre()
|
||||
script.push_str("start_pre() {\n");
|
||||
let mut start_pre_commands = Vec::new();
|
||||
for network in &config.service.networks {
|
||||
for network in networks.iter() {
|
||||
start_pre_commands.push(format!("podman network create {} --ignore;", network));
|
||||
}
|
||||
start_pre_commands.push(format!("podman rm {} --ignore;", config.service.name));
|
||||
script.push_str(&wrap(&start_pre_commands.join("\n")));
|
||||
script.push_str(&start_pre_commands.iter().map(|c|wrap(c)).collect::<Vec<String>>().join("\n"));
|
||||
script.push_str("\n}\n\n");
|
||||
// }
|
||||
|
||||
@@ -39,8 +47,15 @@ pub fn generate_openrc(config: &ServiceConfig) -> String {
|
||||
script.push_str("start() {\n");
|
||||
|
||||
let mut arguments = vec![
|
||||
format!("--restart {}", config.service.restart.as_deref().unwrap_or("unless-stopped")),
|
||||
format!("--name {}", config.service.name)
|
||||
format!(
|
||||
"--restart {}",
|
||||
config
|
||||
.service
|
||||
.restart
|
||||
.as_deref()
|
||||
.unwrap_or("unless-stopped")
|
||||
),
|
||||
format!("--name {}", config.service.name),
|
||||
];
|
||||
|
||||
if let Some(hostname) = &config.service.hostname {
|
||||
@@ -51,7 +66,7 @@ pub fn generate_openrc(config: &ServiceConfig) -> String {
|
||||
arguments.push("--detach".to_string());
|
||||
}
|
||||
|
||||
for network in &config.service.networks {
|
||||
for network in networks.iter() {
|
||||
arguments.push(format!("--network {}", network));
|
||||
}
|
||||
|
||||
@@ -72,7 +87,10 @@ pub fn generate_openrc(config: &ServiceConfig) -> String {
|
||||
}
|
||||
|
||||
for secret in &config.environment_secrets {
|
||||
arguments.push(format!("--env {}=$(podman secret inspect --showsecret --format {{{{.SecretData}}}} {})", secret.name, secret.secret));
|
||||
arguments.push(format!(
|
||||
"--env {}=$(podman secret inspect --showsecret --format {{{{.SecretData}}}} {})",
|
||||
secret.name, secret.secret
|
||||
));
|
||||
}
|
||||
|
||||
for volume in &config.volumes {
|
||||
@@ -80,7 +98,10 @@ pub fn generate_openrc(config: &ServiceConfig) -> String {
|
||||
}
|
||||
|
||||
for mount in &config.mounts {
|
||||
let mut mount_str = format!("--mount type={},source={},target={}", mount.typ, mount.source, mount.target);
|
||||
let mut mount_str = format!(
|
||||
"--mount type={},source={},target={}",
|
||||
mount.typ, mount.source, mount.target
|
||||
);
|
||||
if mount.read_only.unwrap_or(false) {
|
||||
mount_str.push_str(",readonly");
|
||||
}
|
||||
@@ -109,18 +130,29 @@ pub fn generate_openrc(config: &ServiceConfig) -> String {
|
||||
arguments.push(command.clone());
|
||||
}
|
||||
|
||||
script.push_str(&wrap(&format!("podman run {}", arguments.iter()
|
||||
.enumerate()
|
||||
.map(|(i, arg)| if i > 0 { format!("\t{}", arg) } else { arg.to_string() })
|
||||
.collect::<Vec<_>>()
|
||||
.join(" \\\n"))));
|
||||
script.push_str(&wrap(&format!(
|
||||
"podman run {}",
|
||||
arguments
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, arg)| if i > 0 {
|
||||
format!("\t{}", arg)
|
||||
} else {
|
||||
arg.to_string()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" \\\n")
|
||||
)));
|
||||
|
||||
script.push_str("\n}\n\n");
|
||||
// }
|
||||
|
||||
// stop()
|
||||
script.push_str("stop() {\n");
|
||||
script.push_str(&wrap(&format!("podman stop {} --ignore", config.service.name)));
|
||||
script.push_str(&wrap(&format!(
|
||||
"podman stop {} --ignore",
|
||||
config.service.name
|
||||
)));
|
||||
script.push_str("\n}\n\n");
|
||||
// }
|
||||
|
||||
@@ -130,21 +162,127 @@ pub fn generate_openrc(config: &ServiceConfig) -> String {
|
||||
/// Program to generate OpenRC scripts from Podman service definitions in TOML format.
|
||||
#[derive(Debug, Parser)]
|
||||
struct Args {
|
||||
/// Definition file in TOML format
|
||||
/// Definition file in TOML format, or directory with definition files
|
||||
definition: String,
|
||||
|
||||
/// Output file for the OpenRC script
|
||||
/// Output file for the OpenRC script, or output directory for the service files
|
||||
out: String,
|
||||
}
|
||||
|
||||
fn read_dir<P: AsRef<Path>>(path: &P, services: &mut Vec<ServiceConfig>) {
|
||||
for entry in fs::read_dir(path).expect(&format!(
|
||||
"Failed to read directory: {}",
|
||||
path.as_ref().display()
|
||||
)) {
|
||||
let path = entry.unwrap().path();
|
||||
|
||||
if path.is_dir() {
|
||||
read_dir(&path, services);
|
||||
} else if path.is_file()
|
||||
&& path.extension().is_some_and(|ext| ext == "toml")
|
||||
&& path
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.ends_with("service.toml")
|
||||
{
|
||||
println!("Processing: {}", path.display());
|
||||
let input = fs::read_to_string(&path).expect(&format!(
|
||||
"Failed to read definition file: {}",
|
||||
path.display()
|
||||
));
|
||||
|
||||
let config: ServiceConfig = toml::from_str(&input).expect(&format!(
|
||||
"Failed to parse definition file: {}",
|
||||
path.display()
|
||||
));
|
||||
|
||||
services.push(config);
|
||||
} else {
|
||||
println!("Skipped: {}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_network_groups(services: &mut Vec<ServiceConfig>) {
|
||||
// Map network groups
|
||||
let mut network_groups: HashMap<String, Vec<String>> = HashMap::new();
|
||||
for service in services.iter() {
|
||||
for network in &service.networks {
|
||||
if let NetworkMapping {
|
||||
name: Some(name),
|
||||
group: Some(group),
|
||||
} = network
|
||||
{
|
||||
if network_groups.contains_key(group) {
|
||||
network_groups.get_mut(group).unwrap().push(name.clone());
|
||||
} else {
|
||||
network_groups.insert(group.clone(), vec![name.clone()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expand network groups in services
|
||||
for service in services {
|
||||
let mut networks_to_add = Vec::new();
|
||||
service.networks.retain(|network| {
|
||||
if let NetworkMapping {
|
||||
name: None,
|
||||
group: Some(group),
|
||||
} = network
|
||||
{
|
||||
if let Some(names) = network_groups.get(group) {
|
||||
for name in names {
|
||||
networks_to_add.push(NetworkMapping {
|
||||
name: Some(name.clone()),
|
||||
group: Some(group.clone()),
|
||||
});
|
||||
}
|
||||
}
|
||||
false // Remove this network mapping
|
||||
} else {
|
||||
true // Keep this network mapping
|
||||
}
|
||||
});
|
||||
service.networks.extend(networks_to_add);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
let input = fs::read_to_string(&args.definition)
|
||||
.expect("Failed to read definition file");
|
||||
let config: ServiceConfig = toml::from_str(&input)
|
||||
.expect("Failed to parse definition file");
|
||||
let input_path = Path::new(&args.definition);
|
||||
let output_path = Path::new(&args.out);
|
||||
|
||||
let output = generate_openrc(&config);
|
||||
fs::write(&args.out, output)
|
||||
.expect("Failed to write OpenRC script to output file");
|
||||
if input_path.is_dir() {
|
||||
if !output_path.exists() {
|
||||
fs::create_dir_all(&output_path).expect("Failed to create output directory");
|
||||
}
|
||||
|
||||
if !output_path.is_dir() {
|
||||
panic!(
|
||||
"Input path is a directory, but output path is not a directory: {}",
|
||||
output_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
let mut services: Vec<ServiceConfig> = Vec::new();
|
||||
read_dir(&input_path, &mut services);
|
||||
process_network_groups(&mut services);
|
||||
|
||||
// Write
|
||||
for config in services {
|
||||
let output = generate_openrc(&config);
|
||||
fs::write(output_path.join(format!("{}.service.sh", config.service.name)), output)
|
||||
.expect("Failed to write OpenRC script to output file");
|
||||
}
|
||||
} else {
|
||||
let input = fs::read_to_string(&args.definition).expect("Failed to read definition file");
|
||||
let config: ServiceConfig =
|
||||
toml::from_str(&input).expect("Failed to parse definition file");
|
||||
|
||||
let output = generate_openrc(&config);
|
||||
fs::write(&args.out, output).expect("Failed to write OpenRC script to output file");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ pub struct ServiceConfig {
|
||||
#[serde(default)]
|
||||
pub mounts: Vec<MountMapping>,
|
||||
|
||||
#[serde(default)]
|
||||
pub networks: Vec<NetworkMapping>,
|
||||
|
||||
pub user: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
@@ -36,9 +39,6 @@ pub struct Service {
|
||||
pub hostname: Option<String>,
|
||||
pub image: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub networks: Vec<String>,
|
||||
|
||||
pub restart: Option<String>,
|
||||
pub detach: Option<bool>,
|
||||
pub healthcheck: Option<HealthCheck>,
|
||||
@@ -90,3 +90,9 @@ pub struct HealthCheck {
|
||||
pub retries: Option<u32>,
|
||||
pub on_failure: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct NetworkMapping {
|
||||
pub name: Option<String>,
|
||||
pub group: Option<String>,
|
||||
}
|
||||
Reference in New Issue
Block a user