libcoreinst/io/
ignition.rs1use anyhow::{bail, Context, Result};
16use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
17use flate2::read::GzEncoder;
18use flate2::Compression;
19use ignition_config as ign_multi;
20use ignition_config::v3_3 as ign;
21use std::io::Read;
22
23#[derive(Debug, Default)]
24pub struct Ignition {
25 config: ign::Config,
26}
27
28impl Ignition {
29 pub fn merge_config(&mut self, config: &ign_multi::Config) -> Result<()> {
30 let buf = serde_json::to_vec(config).context("serializing child Ignition config")?;
31 self.config
32 .ignition
33 .config
34 .get_or_insert_with(Default::default)
35 .merge
36 .get_or_insert_with(Default::default)
37 .push(make_resource(&buf)?);
38 Ok(())
39 }
40
41 pub fn add_file(&mut self, path: String, data: &[u8], mode: i64) -> Result<()> {
42 if self.have_path(&path) {
46 bail!("config already specifies path {}", path);
47 }
48 self.config
49 .storage
50 .get_or_insert_with(Default::default)
51 .files
52 .get_or_insert_with(Default::default)
53 .push(ign::File {
54 contents: Some(make_resource(data)?),
55 mode: Some(mode),
56 ..ign::File::new(path)
57 });
58 Ok(())
59 }
60
61 pub fn add_unit(&mut self, name: String, contents: String, enabled: bool) -> Result<()> {
62 let units = self
63 .config
64 .systemd
65 .get_or_insert_with(Default::default)
66 .units
67 .get_or_insert_with(Default::default);
68 if units.iter().any(|u| u.name == name) {
69 bail!("config already specifies unit {}", name);
70 }
71 units.push(ign::Unit {
72 contents: Some(contents),
73 enabled: Some(enabled),
74 ..ign::Unit::new(name)
75 });
76 Ok(())
77 }
78
79 pub fn add_ca(&mut self, data: &[u8]) -> Result<()> {
80 self.config
81 .ignition
82 .security
83 .get_or_insert_with(Default::default)
84 .tls
85 .get_or_insert_with(Default::default)
86 .certificate_authorities
87 .get_or_insert_with(Default::default)
88 .push(make_resource(data)?);
89 Ok(())
90 }
91
92 pub fn to_bytes(&self) -> Result<Vec<u8>> {
93 let mut json = serde_json::to_vec(&self.config).context("serializing Ignition config")?;
94 json.push(b'\n');
95 Ok(json)
96 }
97
98 fn have_path(&self, path: &str) -> bool {
99 let storage = self.config.storage.clone().unwrap_or_default();
100 storage
101 .files
102 .unwrap_or_default()
103 .iter()
104 .map(|f| &f.path)
105 .chain(
106 storage
107 .directories
108 .unwrap_or_default()
109 .iter()
110 .map(|d| &d.path),
111 )
112 .chain(storage.links.unwrap_or_default().iter().map(|l| &l.path))
113 .any(|p| p == path)
114 }
115}
116
117fn make_resource(data: &[u8]) -> Result<ign::Resource> {
118 let mut compressed = Vec::new();
119 GzEncoder::new(data, Compression::best()).read_to_end(&mut compressed)?;
120 Ok(ign::Resource {
121 source: Some(format!("data:;base64,{}", BASE64.encode(&compressed))),
122 compression: Some("gzip".into()),
123 ..Default::default()
124 })
125}
126
127#[cfg(test)]
128mod test {
129 use super::*;
130
131 #[test]
132 fn duplicate_path() {
133 let mut ignition = Ignition::default();
134 ignition.add_file("/a/b".into(), &[], 0o755).unwrap();
135 ignition.add_file("/a/b".into(), &[], 0o755).unwrap_err();
136 }
137}