mod checks;
mod guest;
mod lifecycle;
mod machines;
mod slots;
pub(crate) use guest::guest;
use std::collections::BTreeSet;
use isoloom_manifest::{
Argv, Checks, ComposeIdentity, Driver, HostArgv, HostOs, Identity, Manifest, ManifestVersion, NonEmpty, PathTemplate, Placeholder, Program, RelPath,
RunVar, SCHEMA_URL, Template, Tool, Version,
};
use isoloom_plan::Environment;
use isoloom_render::run_manifest;
use crate::error::Result;
use crate::file::ComposeFile;
use crate::start::StartPlan;
const COMPOSE_FILE: &str = "compose.yml";
const PORTS_FILE: &str = "ports.yml";
pub(crate) fn docker(env: &Environment, file: &ComposeFile) -> Result<Manifest> {
let members = machines::classify(env, file)?;
let plan = StartPlan::of(file);
let runners = checks::runners(env)?;
let machines = machines::machines(env, file, &members, &Project::Run)?;
Ok(Manifest {
schema: Some(SCHEMA_URL.to_owned()),
manifest_version: ManifestVersion,
generator: run_manifest::generator()?,
lab: run_manifest::lab(env)?,
target: isoloom_manifest::Target::Docker,
cloud: None,
driver: Driver::Compose,
hosts: BTreeSet::from([HostOs::Linux, HostOs::Macos, HostOs::Windows]),
tools: tools()?,
identity: identity()?,
params: slots::params(&machines, &runners.derived)?,
secrets: slots::slots(env, &members, &plan, slots::Starts::Host)?,
networks: run_manifest::networks(env, |n| Project::Run.prefixed(n))?,
outputs: run_manifest::outputs(&machines)?,
machines,
lifecycle: lifecycle::lifecycle(env, &plan, &members)?,
ready: lifecycle::ready(&plan, &members, "up")?,
checks: Checks {
protocol: isoloom_manifest::CheckProtocol::V1,
setup: checks::setup(file)?,
runners: runners.runners,
teardown: Vec::new(),
},
recovery: Some(lifecycle::recovery()?),
expiry: None,
guest: None,
})
}
fn compose(args: &[&str]) -> Result<HostArgv> {
let mut all = vec![
Template::text("compose"),
Template::text("--project-name"),
Template::from(Placeholder::Run(RunVar::Name)),
Template::text("--file"),
Template::text(COMPOSE_FILE),
Template::text("--file"),
pinned_ports()?.template().clone(),
];
all.extend(args.iter().map(|a| Template::text(a)));
Ok(HostArgv::Every(Argv::new(program("docker")?, all)))
}
fn program(name: &str) -> Result<Program> {
Ok(run_manifest::program(name)?)
}
fn pinned_ports() -> Result<PathTemplate> {
Ok(PathTemplate::in_state_dir(&RelPath::parse(PORTS_FILE)?))
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Project {
Run,
Named(String),
}
impl Project {
fn prefixed(&self, name: &str) -> Template {
match self {
Project::Run => Template::from_parts([
isoloom_manifest::Part::Placeholder(Placeholder::Run(RunVar::Name)),
isoloom_manifest::Part::Text(format!("_{name}")),
]),
Project::Named(project) => Template::text(&format!("{project}_{name}")),
}
}
}
fn identity() -> Result<Identity> {
let compose_file = PathTemplate::relative(&RelPath::parse(COMPOSE_FILE)?);
let pinned = pinned_ports()?;
Ok(Identity::Compose(ComposeIdentity {
project: Template::from(Placeholder::Run(RunVar::Name)),
files: NonEmpty::new(vec![compose_file, pinned.clone()], "the Compose files")?,
pinned_ports_file: Some(pinned),
}))
}
const ENVIRONMENT_SECRETS: &str = "2.14.1";
const WAIT_TIMEOUT: &str = "2.17.0";
fn compose_min_version() -> Result<Version> {
let numeric = |v: &str| v.split('.').filter_map(|p| p.parse::<u32>().ok()).collect::<Vec<_>>();
let later = if numeric(ENVIRONMENT_SECRETS) > numeric(WAIT_TIMEOUT) {
ENVIRONMENT_SECRETS
} else {
WAIT_TIMEOUT
};
Ok(Version::parse(later)?)
}
fn tools() -> Result<Vec<Tool>> {
let probe = |args: &[&str]| -> Result<HostArgv> { Ok(run_manifest::argv("docker", args)?) };
let tool = |name: &str, check: HostArgv, min: Option<Version>| -> Result<Tool> {
let mut t = run_manifest::tool(name, check)?;
t.min_version = min;
Ok(t)
};
Ok(vec![
tool("docker", probe(&["info", "--format", "{{.ServerVersion}}"])?, None)?,
tool("docker-compose", probe(&["compose", "version", "--short"])?, Some(compose_min_version()?))?,
])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compose_is_new_enough_to_copy_environment_secrets_with_their_owner_and_mode() {
let min = compose_min_version().unwrap();
assert_eq!(min.as_str(), WAIT_TIMEOUT);
let numeric = |v: &str| v.split('.').map(|p| p.parse::<u32>().unwrap()).collect::<Vec<_>>();
assert!(numeric(min.as_str()) >= numeric(ENVIRONMENT_SECRETS));
let compose = tools().unwrap().into_iter().find(|t| t.name.as_str() == "docker-compose").unwrap();
assert_eq!(compose.min_version, Some(min));
}
}