use super::*;
fn spec(yaml: &str) -> Spec {
isoloom_spec::parse(yaml).unwrap()
}
fn starters_of(spec: &Spec) -> Vec<ProjectFile> {
let mut files = Vec::new();
add_starters(&mut files, spec, &[]);
files
}
fn project(machine: &str) -> Spec {
spec(&format!(
"version: 1\nname: x\nnetworks:\n lan: {{ cidr: 10.10.1.0/24 }}\nmachines:\n{machine}"
))
}
#[test]
fn a_database_gets_postgresql_listening_on_the_lab() {
let files = starters_of(&project(
" db:\n os: debian@12\n networks: { lan: 12 }\n ports: { postgres: 5432 }\n app: { steps: [{ run: provision/db.sh }] }\n",
));
assert_eq!(files.len(), 1);
assert_eq!(files[0].path, "provision/db.sh");
let sh = &files[0].contents;
assert!(sh.starts_with("#!/bin/sh\n"));
assert!(sh.contains("# Starter set-up for `db` (debian@12)"));
assert!(sh.contains("# Address: 10.10.1.12 (lan)"));
assert!(sh.contains("apt-get install -y postgresql\n"));
assert!(sh.contains("listen_addresses = '*'"));
assert!(sh.contains("if [ -d /run/systemd/system ]; then\n systemctl enable postgresql\n systemctl restart postgresql\nfi\n"));
}
#[test]
fn a_script_builds_in_a_container_where_no_init_system_runs() {
let sh = &starters_of(&project(
" web:\n os: debian@12\n networks: { lan: 10 }\n ports: { http: 80 }\n app: { steps: [{ run: provision/web.sh }] }\n",
))[0]
.contents;
let (before_start, _) = sh.split_once("if [ -d /run/systemd/system ]").unwrap();
assert!(!before_start.contains("systemctl"), "{sh}");
assert!(!sh.contains("pipefail"), "POSIX sh: {sh}");
}
#[test]
fn each_family_installs_with_its_own_tool() {
for (os, install) in [
("rocky@9", "dnf install -y nginx"),
("centos@7", "yum install -y nginx"),
("alpine@3.19", "apk add --no-cache nginx"),
("kali", "apt-get install -y nginx"),
] {
let files = starters_of(&project(&format!(
" web:\n os: {os}\n networks: {{ lan: 10 }}\n ports: {{ http: 80, admin: 9000 }}\n app: {{ steps: [{{ run: provision/web.sh }}] }}\n"
)));
assert!(files[0].contents.contains(install), "{os}: {}", files[0].contents);
assert!(files[0].contents.contains("To do: install what answers on port(s) 9000"));
}
let apk = &starters_of(&project(
" web:\n os: alpine\n networks: { lan: 10 }\n ports: { ssh: 22 }\n app: { steps: [{ run: provision/web.sh }] }\n",
))[0]
.contents;
assert!(
apk.contains("[ -d /run/openrc ]; then\n rc-update add sshd default\n rc-service sshd restart\nfi"),
"{apk}"
);
}
#[test]
fn windows_and_ansible_get_their_own_templates() {
let files = starters_of(&project(
" win:\n os: windows-server@2022\n networks: { lan: 10 }\n ports: { http: 80, admin: 8443 }\n app: { steps: [{ run: provision/win.ps1 }] }\n web:\n os: debian@12\n networks: { lan: 11 }\n app: { steps: [{ playbook: provision/web.yml }] }\n",
));
let win = &files[0].contents;
assert!(win.contains("$ErrorActionPreference = 'Stop'\n"), "{win}");
assert!(win.contains("Install-WindowsFeature -Name Web-Server"), "{win}");
assert!(
win.contains("New-NetFirewallRule -DisplayName 'project-8443' -Direction Inbound -Protocol TCP -LocalPort 8443 -Action Allow"),
"{win}"
);
assert!(!win.contains("project-80'"), "IIS opens its own port: {win}");
let web = &files[1].contents;
assert!(
web.starts_with("# Starter playbook for `web`") && web.contains("- hosts: all\n become: true\n"),
"{web}"
);
assert!(web.contains("ansible.builtin.ping"), "{web}");
}
#[test]
fn a_vlans_address_is_read_under_its_lan() {
let s = spec(
"version: 1\nname: x\nnetworks:\n sw1:\n cidr: 10.10.0.0/19\n vlans:\n 10: { cidr: 10.10.10.0/24 }\nmachines:\n pc:\n os: debian@12\n networks: { sw1.vlan10: 10 }\n app: { steps: [{ run: provision/pc.sh }] }\n",
);
assert!(starters_of(&s)[0].contents.contains("# Address: 10.10.10.10 (sw1.vlan10)"));
}
#[test]
fn a_step_said_twice_gets_one_starter() {
let files = starters_of(&project(
" a:\n os: debian@12\n networks: { lan: 10 }\n app: { steps: [{ run: provision/x.sh }] }\n b:\n os: debian@12\n networks: { lan: 11 }\n app: { steps: [{ run: provision/x.sh }] }\n",
));
assert_eq!(files.iter().map(|f| f.path.as_str()).collect::<Vec<_>>(), ["provision/x.sh"]);
}
#[test]
fn a_controller_playbook_gets_its_starter_after_the_machines_at_a_free_path_of_the_project() {
let spec = project(" web:\n os: debian@12\n networks: { lan: 10 }\n app: { steps: [{ run: provision/web.sh }] }\n");
let playbook = |path: &str, machine: &str| ControllerPlaybook {
path: path.into(),
machine: machine.into(),
};
let config = ProjectFile {
path: "configs/r1.cfg".into(),
contents: "hostname r1\n".into(),
};
let mut files = vec![config];
let controller = [
playbook("provision/dc01.yml", "dc01"),
playbook("provision/web.sh", "dc01"),
playbook("../dc01.yml", "dc01"),
playbook("/etc/dc01.yml", "dc01"),
playbook("configs/r1.cfg", "dc01"),
playbook("isoloom.yml", "dc01"),
playbook(".isoloom/docker/compose.yml", "dc01"),
];
add_starters(&mut files, &spec, &controller);
let paths: Vec<&str> = files.iter().map(|f| f.path.as_str()).collect();
assert_eq!(paths, ["configs/r1.cfg", "provision/web.sh", "provision/dc01.yml"]);
assert!(files[2].contents.contains("- hosts: dc01"));
}
#[test]
fn a_starter_names_the_services_it_answers_only_when_it_has_ports() {
let with = &starters_of(&project(
" db:\n os: debian@12\n networks: { lan: 12 }\n ports: { postgres: 5432 }\n app: { steps: [{ run: provision/db.sh }] }\n",
))[0]
.contents;
assert!(with.contains("# Services the project expects to answer: 5432\n"), "{with}");
let without = &starters_of(&project(
" box:\n os: debian@12\n networks: { lan: 12 }\n app: { steps: [{ run: provision/box.sh }] }\n",
))[0]
.contents;
assert!(!without.contains("Services the project expects"), "{without}");
assert!(without.contains("Nothing to install for this machine's ports yet"), "{without}");
}
#[test]
fn mariadb_and_redis_listen_on_the_lab() {
let sh = &starters_of(&project(
" data:\n os: debian@12\n networks: { lan: 12 }\n ports: { mysql: 3306, redis: 6379 }\n app: { steps: [{ run: provision/data.sh }] }\n",
))[0]
.contents;
assert!(sh.contains("apt-get install -y mariadb-server redis-server\n"), "{sh}");
assert!(sh.contains("bind-address = 0.0.0.0"), "{sh}");
assert!(sh.contains("protected-mode no"), "{sh}");
}
#[test]
fn a_package_two_ports_call_for_is_installed_once() {
let sh = &starters_of(&project(
" web:\n os: debian@12\n networks: { lan: 12 }\n ports: { http: 80, https: 443 }\n app: { steps: [{ run: provision/web.sh }] }\n",
))[0]
.contents;
assert!(sh.contains("apt-get install -y nginx\n"), "{sh}");
assert_eq!(sh.matches("systemctl enable nginx").count(), 1, "{sh}");
}