isoloom_render/configure/
powershell.rs1use isoloom_plan::configure::{CommandPlan, FilePlan, Resource, ServicePlan, ServiceSource, UserPlan};
6use isoloom_spec::shell::base64;
7use isoloom_spec::syntax::{FileState, Guard, Invocation};
8
9use super::Configured;
10use crate::powershell::quote;
11use crate::steps::WINDOWS_PROJECT_DIR;
12use crate::{GeneratedFile, header};
13
14pub const LIBRARY: &str = include_str!("powershell/configure.ps1");
16
17pub const LIBRARY_PATH: &str = ".isoloom/configure/lib/configure.ps1";
19
20const CHANGED: &str = "C:\\Windows\\Temp\\isoloom-configure.changed";
22
23#[must_use]
25pub fn library_file() -> GeneratedFile {
26 GeneratedFile {
27 path: LIBRARY_PATH.to_owned(),
28 contents: format!("{}{LIBRARY}", header("#")),
29 }
30}
31
32#[must_use]
35pub fn script(c: &Configured<'_>, resources: &[Resource], fresh: bool) -> String {
36 let calls: String = resources.iter().filter_map(|r| call(c, r)).collect();
37 let mark = if fresh {
38 "Remove-Item -LiteralPath $env:ISOLOOM_CHANGED -Force -ErrorAction SilentlyContinue"
39 } else {
40 "Set-Content -LiteralPath $env:ISOLOOM_CHANGED -Value 'changed'"
41 };
42 let library = super::compact(LIBRARY);
43 format!("$ErrorActionPreference = 'Stop'\n{library}$env:ISOLOOM_CHANGED = '{CHANGED}'\n{mark}\nSet-Location -LiteralPath '{WINDOWS_PROJECT_DIR}'\n{calls}")
44}
45
46#[must_use]
49pub fn call(c: &Configured<'_>, r: &Resource) -> Option<String> {
50 let line = match r {
51 Resource::Packages(ps) => format!("Install-IsoloomFeature -Name {}", list(ps.iter().map(|p| p.name.to_string()))),
52 Resource::Copy(cp) => format!("Copy-IsoloomItem -From {} -To {}", quote(cp.from.as_str()), quote(cp.to.as_str())),
53 Resource::Template(t) => match c.template(&t.from) {
54 Ok(text) => format!("Install-IsoloomTemplate -To {} -Base64 '{}'", quote(t.to.as_str()), base64(text.as_bytes())),
55 Err(why) => format!("throw {}", quote(&format!("isoloom: {why}"))),
56 },
57 Resource::File(f) => file(f)?,
58 Resource::User(u) => user(u),
59 Resource::Group(g) => format!("Add-IsoloomGroup -Name {}", quote(g.name.as_str())),
60 Resource::Service(s) => service(s),
61 Resource::Command(cmd) => command(cmd),
62 Resource::Firewall(ports) => ports
63 .iter()
64 .map(|p| {
65 format!(
66 "Enable-IsoloomFirewallPort -Port {} -Protocol {}",
67 p.port,
68 if p.protocol.is_tcp() { "TCP" } else { "UDP" }
69 )
70 })
71 .collect::<Vec<_>>()
72 .join("\n"),
73 _ => return None,
74 };
75 (!line.is_empty()).then(|| format!("{line}\n"))
76}
77
78fn list<I: IntoIterator<Item = String>>(items: I) -> String {
80 items.into_iter().map(|i| quote(&i)).collect::<Vec<_>>().join(", ")
81}
82
83fn file(f: &FilePlan) -> Option<String> {
85 let path = quote(f.path.as_str());
86 Some(match &f.state {
87 FileState::Directory => format!("Install-IsoloomFile -Path {path} -State directory"),
88 FileState::Absent => format!("Install-IsoloomFile -Path {path} -State absent"),
89 FileState::Download { url, sha256 } => format!(
90 "Install-IsoloomFile -Path {path} -State download -Url {} -Sha256 '{sha256}'",
91 quote(url.as_str())
92 ),
93 _ => return None,
94 })
95}
96
97fn user(u: &UserPlan) -> String {
100 let phrase = u.password.as_ref().map(|p| format!(" -Phrase {}", quote(p.as_str()))).unwrap_or_default();
101 let groups: Vec<String> = u.group.iter().chain(&u.groups).map(ToString::to_string).collect();
102 let groups = if groups.is_empty() {
103 String::new()
104 } else {
105 format!(" -Groups {}", list(groups))
106 };
107 format!("Add-IsoloomUser -Name {}{phrase}{groups}", quote(u.name.as_str()))
108}
109
110fn service(s: &ServicePlan) -> String {
112 let name = quote(s.unit.as_str());
113 let enabled = if s.enabled { "$true" } else { "$false" };
114 match &s.source {
115 ServiceSource::Defined { run, cwd, .. } => {
116 let dir = cwd.as_ref().map(|d| format!(" -Directory {}", quote(d.as_str()))).unwrap_or_default();
117 format!(
118 "Register-IsoloomTask -Name {name} -Run {}{dir}\nRegister-IsoloomTaskState -Name {name} -Enabled {enabled}",
119 list(run.args().iter().cloned())
120 )
121 }
122 _ => format!("Register-IsoloomService -Name {name} -Enabled {enabled}"),
123 }
124}
125
126fn command(cmd: &CommandPlan) -> String {
128 let guard = match &cmd.guard {
129 Guard::Creates(path) => format!(" -Creates {}", quote(path.as_str())),
130 Guard::Unless(check) => format!(" -Unless {}", quote(&text(check))),
131 _ => String::new(),
132 };
133 format!("Invoke-IsoloomCommand -Run {}{guard}", quote(&text(&cmd.run)))
134}
135
136fn text(i: &Invocation) -> String {
138 match i {
139 Invocation::Exec(a) => format!("& {}", a.args().iter().map(|w| quote(w)).collect::<Vec<_>>().join(" ")),
140 Invocation::Shell(t) => t.clone(),
141 _ => "throw 'this Isoloom cannot render that command'".to_owned(),
142 }
143}
144
145#[cfg(test)]
146mod tests;