isoloom_render/configure/
dockerfile.rs1use isoloom_plan::configure::{ContainerProcess, Resource, RunPlan, TemplatePlan};
10use isoloom_plan::{Build, Environment, Image, MachineShape};
11use isoloom_spec::syntax::Mode;
12
13use super::{Configured, rendered_path, runner, shell};
14use crate::steps::PROJECT_DIR;
15use crate::{GeneratedFile, header};
16
17const LIBRARY: &str = "/usr/local/lib/isoloom/configure.sh";
19
20const RUNNER: &str = "/usr/local/bin/isoloom-run";
22
23#[must_use]
27pub fn files(env: &Environment) -> Vec<GeneratedFile> {
28 let mut out = Vec::new();
29 for (name, mp) in &env.machines {
30 let (
31 Some(Image::Container {
32 build: Some(Build::Generated { dockerfile, base, process }),
33 ..
34 }),
35 Some(c),
36 MachineShape::Container,
37 ) = (&mp.image, Configured::of(env, name), mp.shape)
38 else {
39 continue;
40 };
41 out.push(GeneratedFile {
42 path: dockerfile.clone(),
43 contents: render(&c, &env.images.pull(base), process),
44 });
45 if super::uses_library(c.recipe()) && !out.iter().any(|f| f.path == shell::LIBRARY_PATH) {
46 out.push(shell::library_file());
47 }
48 out.extend(runner::file(&c, process));
49 out.extend(templates(&c));
50 }
51 out
52}
53
54#[must_use]
56pub fn render(c: &Configured<'_>, base: &str, process: &ContainerProcess) -> String {
57 let library = if super::uses_library(c.recipe()) {
58 format!("COPY {} {LIBRARY}\n", shell::LIBRARY_PATH)
59 } else {
60 String::new()
61 };
62 let recipe = c.recipe();
64 let steps: String = recipe.install.iter().chain(&recipe.configure).filter_map(|r| instructions(c, r)).collect();
65 let entrypoint = if process.is_runner() {
66 let command = match process {
68 ContainerProcess::Started { command, .. } => serde_json::to_string(command).unwrap_or_else(|_| "[]".to_owned()),
69 _ => "[]".to_owned(),
70 };
71 format!(
72 "{}COPY --chmod=0755 {} {RUNNER}\nENTRYPOINT [\"{RUNNER}\"]\nCMD {command}\n",
73 start_files(c),
74 runner::path(c.machine())
75 )
76 } else {
77 String::new()
78 };
79 format!(
80 "{}# The container form of machine `{}`: its OS's image and its steps.\nFROM {base}\nWORKDIR {PROJECT_DIR}\n{library}{steps}{entrypoint}",
81 header("#"),
82 c.machine()
83 )
84}
85
86fn start_files(c: &Configured<'_>) -> String {
89 c.recipe()
90 .start
91 .iter()
92 .filter_map(|r| match r {
93 Resource::Run(run) => Some(run.script.as_str()),
94 Resource::Copy(cp) => Some(cp.from.as_str()),
95 _ => None,
96 })
97 .map(|path| format!("COPY {path} {path}\n"))
98 .collect::<Vec<_>>()
99 .concat()
100}
101
102fn instructions(c: &Configured<'_>, step: &Resource) -> Option<String> {
105 let lines = match step {
106 Resource::Run(run) => script(run),
107 Resource::Copy(cp) => {
108 let owner = cp.owner.as_ref().map(|o| format!("--chown={o} ")).unwrap_or_default();
109 let mode = cp.mode.map(|m| format!("--chmod={m} ")).unwrap_or_default();
110 format!("COPY {owner}{mode}{} {}\n", cp.from, cp.to)
111 }
112 Resource::Template(t) => template(c, t),
113 Resource::Service(_) | Resource::Firewall(_) | Resource::Reboot | Resource::Playbook(_) => return None,
114 other => {
115 let call = shell::call(c, other)?;
116 let ignore = if shell::literal_dollar(&call) { "# hadolint ignore=SC2016\n" } else { "" };
117 format!("{ignore}RUN . {LIBRARY} && {call}")
118 }
119 };
120 Some(lines)
121}
122
123fn script(run: &RunPlan) -> String {
125 let script = &run.script;
126 let line = match &run.creates {
127 Some(path) => format!("[ -e {path} ] || sh {script}"),
128 None => format!("sh {script}"),
129 };
130 format!("COPY {script} {script}\nRUN {line}\n")
131}
132
133fn template(c: &Configured<'_>, t: &TemplatePlan) -> String {
136 if let Err(why) = c.template(&t.from) {
137 return format!("RUN . {LIBRARY} && {}\n", shell::words("isoloom_fail", [why]));
138 }
139 let chown = match (&t.owner, &t.group) {
140 (Some(o), Some(g)) => format!("--chown={o}:{g} "),
141 (Some(o), None) => format!("--chown={o} "),
142 (None, Some(g)) => format!("--chown=root:{g} "),
143 (None, None) => String::new(),
144 };
145 let mode = t.mode.unwrap_or(Mode::CONFIG);
146 format!("COPY {chown}--chmod={mode} {} {}\n", rendered_path(c.machine(), &t.from), t.to)
147}
148
149fn templates(c: &Configured<'_>) -> Vec<GeneratedFile> {
152 c.recipe()
153 .resources()
154 .filter_map(|r| match r {
155 Resource::Template(t) => Some(GeneratedFile {
156 path: rendered_path(c.machine(), &t.from),
157 contents: c.template(&t.from).ok()?,
158 }),
159 _ => None,
160 })
161 .collect()
162}
163
164#[cfg(test)]
165mod tests;