use indexmap::IndexMap;
use isoloom_plan::{Environment, MachinePlan};
use isoloom_render::container::{copied_from_project, project_variables, variable};
use crate::file::{Config, ConfigMount};
use crate::interpolation::literal;
use crate::layout::ROOT;
use crate::machine::Machine;
fn name(machine: &str, n: usize) -> String {
format!("{machine}-file-{n}")
}
pub(super) fn usage(env: &Environment) -> String {
let vars = project_variables(env);
if vars.is_empty() {
return String::new();
}
let each: Vec<String> = vars.iter().map(|(var, path)| format!("{var} to {path}'s contents")).collect();
format!(
"# Project files with an owner or a mode: `isoloom run` reads them into Compose's environment; by hand, set {} for `up`.\n",
each.join(", ")
)
}
pub(super) fn declared(env: &Environment) -> IndexMap<String, Config> {
Machine::containers(env)
.flat_map(|Machine { name: machine, plan, .. }| {
plan.files.iter().enumerate().map(move |(n, f)| {
let config = match (&f.source, &f.content) {
(Some(_), _) if copied_from_project(f) => Config {
environment: Some(variable(machine, n)),
..Config::default()
},
(Some(source), _) => Config {
file: Some(format!("{ROOT}/{source}")),
..Config::default()
},
(None, content) => Config {
content: Some(literal(content.as_deref().unwrap_or_default())),
..Config::default()
},
};
(name(machine, n), config)
})
})
.collect()
}
pub(super) fn mounts(machine: &str, mp: &MachinePlan) -> Vec<ConfigMount> {
mp.files
.iter()
.enumerate()
.map(|(n, f)| {
let text = f.source.is_none() || copied_from_project(f);
ConfigMount {
source: name(machine, n),
target: f.path.clone(),
uid: f.uid.filter(|_| text).map(|u| u.to_string()),
gid: f.gid.filter(|_| text).map(|g| g.to_string()),
mode: text.then(|| f.mode_or_default().to_owned()),
}
})
.collect()
}
#[cfg(test)]
mod tests {
use isoloom_render::container::{project_variables, variable};
use crate::testing::environment;
const SPEC: &str = "
version: 1
name: files
networks:
lan: { cidr: 10.20.0.0/24 }
machines:
web:
networks: { lan: 10 }
app: { image: nginx }
files:
- { path: /etc/app.ini, content: \"home=$HOME\\n\", uid: 101, gid: 102, mode: '0600' }
- { path: /etc/nginx/conf.d/site.conf, source: conf/site.conf }
- { path: /etc/app/key.pem, source: conf/key.pem, uid: 101, mode: '0400' }
";
#[test]
fn text_is_copied_with_its_owner_and_a_project_file_mounted() {
let env = environment(SPEC);
let configs = super::declared(&env);
assert_eq!(configs["web-file-0"].content.as_deref(), Some("home=$$HOME\n"));
assert_eq!(configs["web-file-1"].file.as_deref(), Some("../../conf/site.conf"));
let mounts = super::mounts("web", &env.machines["web"]);
assert_eq!(
(mounts[0].uid.as_deref(), mounts[0].gid.as_deref(), mounts[0].mode.as_deref()),
(Some("101"), Some("102"), Some("0600"))
);
assert_eq!((mounts[1].target.as_str(), mounts[1].mode.as_deref()), ("/etc/nginx/conf.d/site.conf", None));
}
#[test]
fn an_owned_project_file_is_copied_in_from_compose_s_environment() {
let env = environment(SPEC);
let configs = super::declared(&env);
assert_eq!(configs["web-file-2"].environment.as_deref(), Some("ISOLOOM_FILE_WEB_2"));
assert_eq!((configs["web-file-2"].file.as_ref(), configs["web-file-2"].content.as_ref()), (None, None));
let mounts = super::mounts("web", &env.machines["web"]);
assert_eq!(
(mounts[2].uid.as_deref(), mounts[2].gid.as_deref(), mounts[2].mode.as_deref()),
(Some("101"), None, Some("0400"))
);
assert_eq!(project_variables(&env), [("ISOLOOM_FILE_WEB_2".to_owned(), "conf/key.pem".to_owned())]);
assert_eq!(variable("db-main", 0), "ISOLOOM_FILE_DB_MAIN_0");
assert_eq!(
isoloom_render::container::host_environment(&env),
"ISOLOOM_FILE_WEB_2=\"$(cat 'conf/key.pem'; printf x)\" && ISOLOOM_FILE_WEB_2=\"${ISOLOOM_FILE_WEB_2%x}\" && export ISOLOOM_FILE_WEB_2 && "
);
assert!(
super::usage(&env).contains("set ISOLOOM_FILE_WEB_2 to conf/key.pem's contents"),
"{}",
super::usage(&env)
);
}
}