use isoloom_spec::model::Target;
use super::*;
use crate::testing;
fn built(m: &str, templates: &[(&str, &str)]) -> Vec<GeneratedFile> {
files(&testing::machine(m, Target::Docker, templates))
}
fn dockerfile(os: &str, steps: &str) -> String {
let found = built(&format!("os: {os}, app: {{ steps: {steps} }}"), &[]);
found.into_iter().next().expect("a Dockerfile").contents
}
#[test]
fn steps_become_instructions_from_the_os_image() {
let d = dockerfile(
"debian@12",
"[{ packages: [nginx] }, { copy: { from: site/, to: /var/www/, owner: www-data, mode: \"0755\" } }, { run: provision/web.sh }]",
);
assert!(d.starts_with("# Generated by isoloom"));
assert!(
d.contains("# The container form of machine `m`: its OS's image and its steps.\nFROM debian:12@sha256:"),
"{d}"
);
assert!(
d.contains("\nWORKDIR /opt/isoloom\nCOPY .isoloom/configure/lib/configure.sh /usr/local/lib/isoloom/configure.sh\n"),
"{d}"
);
assert!(d.contains("RUN . /usr/local/lib/isoloom/configure.sh && isoloom_packages 'nginx'\n"), "{d}");
assert!(d.contains("COPY --chown=www-data --chmod=0755 site/ /var/www/\n"));
assert!(d.contains("COPY provision/web.sh provision/web.sh\nRUN sh provision/web.sh\n"));
assert!(!d.contains("ENTRYPOINT"), "nothing to run: the target keeps it idle");
}
#[test]
fn scripts_alone_need_no_library() {
let d = dockerfile("debian@12", "[{ run: a.sh, creates: /done }]");
assert!(d.contains("RUN [ -e /done ] || sh a.sh") && !d.contains("configure.sh"), "{d}");
}
#[test]
fn every_kind_with_a_container_form_is_a_library_call_or_a_copy() {
let key = format!("key: {{ url: \"https://a.test/k\", sha256: {} }}", "0".repeat(64));
let d = dockerfile(
"rocky@9",
&format!(
"[{{ repository: {{ name: vendor, url: \"https://a.test/rpm\", {key} }} }}, {{ packages: [{{ name: curl, version: 7.76.1-31.el9 }}] }}, {{ group: {{ name: ops }} }}, {{ user: {{ name: alice, group: ops }} }}, {{ file: {{ path: /srv, state: directory }} }}, {{ command: [touch, /done], creates: /done }}, {{ firewall: {{ allow: [8443] }} }}]"
),
);
let runs: Vec<&str> = d.lines().filter(|l| l.starts_with("RUN ")).collect();
assert_eq!(runs.len(), 6, "{d}");
for (line, function) in runs.iter().zip([
"isoloom_repository",
"isoloom_packages",
"isoloom_group",
"isoloom_user",
"isoloom_file",
"isoloom_command",
]) {
assert!(line.starts_with(&format!("RUN . /usr/local/lib/isoloom/configure.sh && {function} ")), "{line}");
}
assert!(!d.contains("firewall"), "a container has no host firewall: {d}");
}
#[test]
fn a_template_is_copied_filled_in_with_its_owner_and_mode() {
let found = built(
"os: debian@12, app: { steps: [{ template: { from: conf/app.toml, to: /etc/app.toml, owner: app, group: ops } }] }",
&[("conf/app.toml", "host = '{{ machine.name }}'\n")],
);
assert!(
found[0]
.contents
.contains("COPY --chown=app:ops --chmod=0644 .isoloom/configure/m/rendered/conf/app.toml /etc/app.toml\n"),
"{}",
found[0].contents
);
let rendered = found
.iter()
.find(|f| f.path == ".isoloom/configure/m/rendered/conf/app.toml")
.expect("the filled template");
assert_eq!(rendered.contents, "host = 'm'\n");
let unread = built("os: debian@12, app: { steps: [{ template: { from: conf/app.toml, to: /etc/app.toml } }] }", &[]);
assert!(
unread[0].contents.contains("&& isoloom_fail 'the template conf/app.toml wasn'\\''t read"),
"{}",
unread[0].contents
);
assert!(!unread.iter().any(|f| f.path.contains("/rendered/")), "nothing filled in to copy");
}
#[test]
fn a_container_that_runs_services_builds_its_runner_in_as_its_entrypoint() {
let found = built(
"os: debian@12, ports: { web: 80 }, app: { steps: [{ packages: [nginx] }, { service: nginx }] }",
&[],
);
let paths: Vec<&str> = found.iter().map(|f| f.path.as_str()).collect();
assert_eq!(
paths,
[
".isoloom/docker/build/m/Dockerfile",
".isoloom/configure/lib/configure.sh",
".isoloom/configure/m/run"
]
);
assert!(
found[0]
.contents
.ends_with("COPY --chmod=0755 .isoloom/configure/m/run /usr/local/bin/isoloom-run\nENTRYPOINT [\"/usr/local/bin/isoloom-run\"]\nCMD []\n"),
"{}",
found[0].contents
);
assert!(!found[0].contents.contains("isoloom_service"), "the runner starts it, not the build");
}
#[test]
fn each_container_built_from_steps_gets_its_dockerfile_and_a_vm_none() {
let yaml = "version: 1\nname: d\nnetworks: { lan: { cidr: 10.9.0.0/24 } }\nmachines:\n web: { networks: { lan: 10 }, os: debian@12, app: { steps: [{ packages: [curl] }] } }\n img: { networks: { lan: 11 }, app: { image: nginx } }\n";
let found = files(&testing::env(yaml, Target::Docker));
assert_eq!(found[0].path, ".isoloom/docker/build/web/Dockerfile");
assert!(found[0].contents.contains("FROM debian:12@sha256:"));
assert_eq!(files(&testing::env(yaml, Target::Vagrant)), [], "a VM sets itself up");
}
#[test]
fn a_bare_official_image_keeps_its_own_working_folder() {
let d = built("os: debian@12, service: juice-shop", &[])
.into_iter()
.next()
.expect("a Dockerfile")
.contents;
assert!(d.contains("FROM docker.io/bkimminich/juice-shop"), "{d}");
assert!(!d.contains("WORKDIR"), "a bare image keeps its own working folder: {d}");
assert!(!d.contains("ENTRYPOINT"), "the image's own entrypoint runs: {d}");
}
#[test]
fn each_family_installs_with_its_library_call() {
for os in ["alpine@3.19", "rocky@9", "centos@7", "ubuntu@24.04"] {
assert!(dockerfile(os, "[{ packages: [curl] }]").contains("&& isoloom_packages 'curl'\n"), "{os}");
}
}