use indexmap::IndexMap;
use isoloom_spec::model::{BaseImage, Machine, Spec};
use crate::configure::{ContainerProcess, Recipe};
use super::MachineShape;
use crate::images::{Images, tag_of};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Image {
Container {
reference: String,
build: Option<Build>,
},
Vm {
os: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Build {
Own {
context: String,
dockerfile: Option<String>,
target: Option<String>,
args: IndexMap<String, String>,
},
Generated {
dockerfile: String,
base: String,
process: ContainerProcess,
},
}
#[must_use]
pub(crate) fn generated_dockerfile(machine: &str) -> String {
format!(".isoloom/docker/build/{machine}/Dockerfile")
}
pub(super) fn of(spec: &Spec, shape: MachineShape, name: &str, m: &Machine, recipe: Option<&Recipe>) -> Option<Image> {
match shape {
MachineShape::Container => {
let d = m.docker.as_ref()?;
let tag = tag_of(spec, name, d.image.as_deref());
let own = d.build.as_ref().map(|context| Build::Own {
context: context.clone(),
dockerfile: d.dockerfile.clone(),
target: d.target.clone(),
args: d.args.clone(),
});
let generated = d.generated.as_ref().map(|g| Build::Generated {
dockerfile: generated_dockerfile(name),
base: g.base.clone(),
process: match (g.from, &g.after_start) {
(BaseImage::Entry, Some(image)) => ContainerProcess::Started {
entrypoint: image.entrypoint.clone(),
command: image.command.clone(),
},
(BaseImage::Entry, None) => ContainerProcess::Image,
_ => recipe
.and_then(|r| ContainerProcess::resolve(r, m.command.as_ref(), !m.ports.is_empty()).ok())
.unwrap_or(ContainerProcess::Idle),
},
});
Some(Image::Container {
reference: Images::of(spec).pull(&tag),
build: own.or(generated),
})
}
MachineShape::Vm => m.vm.as_ref().map(|vm| Image::Vm { os: vm.os.clone() }),
MachineShape::Supplied | MachineShape::External => None,
}
}
#[cfg(test)]
mod tests {
use isoloom_spec::model::Target;
use super::{Build, Image};
use crate::configure::ContainerProcess;
use crate::testing;
#[test]
fn a_container_runs_its_image_its_build_or_the_one_isoloom_generates_and_a_vm_its_os() {
let env = testing::environment(
"version: 1\nname: lab\nmachines:\n pub: { app: { image: nginx } }\n own: { app: { build: { context: web, dockerfile: web/Dockerfile.dev, target: prod, args: { A: '1' } } } }\n gen: { os: debian@12, app: { steps: [{ packages: [curl] }] } }\n user: { access: true }\n",
Target::Docker,
);
let image = |m: &str| env.machines.get(m).and_then(|m| m.image.clone());
assert_eq!(
image("pub"),
Some(Image::Container {
reference: "nginx".into(),
build: None
})
);
let Some(Image::Container {
reference,
build: Some(Build::Own {
context,
dockerfile,
target,
args,
}),
}) = image("own")
else {
panic!("a build of the project's own")
};
assert_eq!(
(reference.as_str(), context.as_str(), dockerfile.as_deref(), target.as_deref()),
("isoloom/lab-own", "web", Some("web/Dockerfile.dev"), Some("prod"))
);
assert_eq!(args.get("A").map(String::as_str), Some("1"));
let Some(Image::Container {
reference: generated,
build: Some(Build::Generated {
dockerfile: written,
base,
process,
}),
}) = image("gen")
else {
panic!("a generated build")
};
assert_eq!(
(generated.as_str(), written.as_str()),
("isoloom/lab-gen", ".isoloom/docker/build/gen/Dockerfile")
);
assert!(base.starts_with("debian:12@sha256:"));
assert_eq!(process, ContainerProcess::Idle, "nothing to run: kept idle");
assert_eq!(image("user"), None, "a supplied machine runs from nothing of ours");
let vm = testing::environment("version: 1\nname: lab\nmachines:\n m: { os: debian@12 }\n", Target::Vagrant);
assert_eq!(vm.machines.get("m").and_then(|m| m.image.clone()), Some(Image::Vm { os: "debian-12".into() }));
}
}