isoloom_plan/environment/
image.rs1use indexmap::IndexMap;
5use isoloom_spec::model::{BaseImage, Machine, Spec};
6
7use crate::configure::{ContainerProcess, Recipe};
8
9use super::MachineShape;
10use crate::images::{Images, tag_of};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum Image {
15 Container {
17 reference: String,
19 build: Option<Build>,
21 },
22 Vm {
24 os: String,
26 },
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum Build {
32 Own {
34 context: String,
36 dockerfile: Option<String>,
38 target: Option<String>,
40 args: IndexMap<String, String>,
42 },
43 Generated {
46 dockerfile: String,
49 base: String,
51 process: ContainerProcess,
54 },
55}
56
57#[must_use]
59pub(crate) fn generated_dockerfile(machine: &str) -> String {
60 format!(".isoloom/docker/build/{machine}/Dockerfile")
61}
62
63pub(super) fn of(spec: &Spec, shape: MachineShape, name: &str, m: &Machine, recipe: Option<&Recipe>) -> Option<Image> {
66 match shape {
67 MachineShape::Container => {
68 let d = m.docker.as_ref()?;
69 let tag = tag_of(spec, name, d.image.as_deref());
70 let own = d.build.as_ref().map(|context| Build::Own {
71 context: context.clone(),
72 dockerfile: d.dockerfile.clone(),
73 target: d.target.clone(),
74 args: d.args.clone(),
75 });
76 let generated = d.generated.as_ref().map(|g| Build::Generated {
77 dockerfile: generated_dockerfile(name),
78 base: g.base.clone(),
79 process: match (g.from, &g.after_start) {
80 (BaseImage::Entry, Some(image)) => ContainerProcess::Started {
83 entrypoint: image.entrypoint.clone(),
84 command: image.command.clone(),
85 },
86 (BaseImage::Entry, None) => ContainerProcess::Image,
87 _ => recipe
89 .and_then(|r| ContainerProcess::resolve(r, m.command.as_ref(), !m.ports.is_empty()).ok())
90 .unwrap_or(ContainerProcess::Idle),
91 },
92 });
93 Some(Image::Container {
94 reference: Images::of(spec).pull(&tag),
95 build: own.or(generated),
96 })
97 }
98 MachineShape::Vm => m.vm.as_ref().map(|vm| Image::Vm { os: vm.os.clone() }),
99 MachineShape::Supplied | MachineShape::External => None,
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use isoloom_spec::model::Target;
106
107 use super::{Build, Image};
108 use crate::configure::ContainerProcess;
109 use crate::testing;
110
111 #[test]
112 fn a_container_runs_its_image_its_build_or_the_one_isoloom_generates_and_a_vm_its_os() {
113 let env = testing::environment(
114 "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",
115 Target::Docker,
116 );
117 let image = |m: &str| env.machines.get(m).and_then(|m| m.image.clone());
118 assert_eq!(
119 image("pub"),
120 Some(Image::Container {
121 reference: "nginx".into(),
122 build: None
123 })
124 );
125 let Some(Image::Container {
126 reference,
127 build: Some(Build::Own {
128 context,
129 dockerfile,
130 target,
131 args,
132 }),
133 }) = image("own")
134 else {
135 panic!("a build of the project's own")
136 };
137 assert_eq!(
138 (reference.as_str(), context.as_str(), dockerfile.as_deref(), target.as_deref()),
139 ("isoloom/lab-own", "web", Some("web/Dockerfile.dev"), Some("prod"))
140 );
141 assert_eq!(args.get("A").map(String::as_str), Some("1"));
142 let Some(Image::Container {
143 reference: generated,
144 build: Some(Build::Generated {
145 dockerfile: written,
146 base,
147 process,
148 }),
149 }) = image("gen")
150 else {
151 panic!("a generated build")
152 };
153 assert_eq!(
154 (generated.as_str(), written.as_str()),
155 ("isoloom/lab-gen", ".isoloom/docker/build/gen/Dockerfile")
156 );
157 assert!(base.starts_with("debian:12@sha256:"));
158 assert_eq!(process, ContainerProcess::Idle, "nothing to run: kept idle");
159 assert_eq!(image("user"), None, "a supplied machine runs from nothing of ours");
160 let vm = testing::environment("version: 1\nname: lab\nmachines:\n m: { os: debian@12 }\n", Target::Vagrant);
161 assert_eq!(vm.machines.get("m").and_then(|m| m.image.clone()), Some(Image::Vm { os: "debian-12".into() }));
162 }
163}