/
githubmirror
/
coreos-installer
Обзор
Документация
Войти
/
githubmirror
/
coreos-installer
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/live/customize.rs
409 строк
14 KB
Benjamin Gilbert
Inline variables into format strings
19 фев 2023, 09:06
19 фев 2023, 09:06
7e906d0
Код
Авторство
О чём код?
// Copyright 2019 CoreOS, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Infrastructure for high-level ISO/PXE customizations use anyhow::{bail, Context, Result}; use nmstate::NetworkState; use serde::Deserialize; use serde_json; use std::fs::read; use std::path::Path; use crate::cmdline::*; use crate::io::*; use crate::iso9660::{self, IsoFs}; use super::embed::{INITRD_IGNITION_PATH, INITRD_NETWORK_DIR}; use super::util::filename; pub(super) const INITRD_FEATURES_PATH: &str = "etc/coreos/features.json"; const COREOS_ISO_FEATURES_PATH: &str = "COREOS/FEATURES.JSO"; /// CoreOS feature flags in /etc/coreos/features.json in the live initramfs /// and /coreos/features.json in the live ISO. Written by /// cosa buildextend-live. #[derive(Default, Deserialize)] #[serde(default, rename_all = "kebab-case")] pub(super) struct OsFeatures { /// Installer reads config files from /etc/coreos/installer.d pub installer_config: bool, /// Directives supported in installer config files pub installer_config_directives: InstallerDirectives, /// Live initrd reads NM keyfiles from /etc/coreos-firstboot-network pub live_initrd_network: bool, } #[derive(Default, Deserialize)] #[serde(default, rename_all = "kebab-case")] pub(super) struct InstallerDirectives { pub console: bool, } impl OsFeatures { pub fn for_iso(iso: &mut IsoFs) -> Result<Self> { match iso.get_path(COREOS_ISO_FEATURES_PATH) { Ok(record) => serde_json::from_reader( iso.read_file(&record.try_into_file()?) .context("reading OS features")?, ) .context("parsing OS features"), Err(e) if e.is::<iso9660::NotFound>() => Ok(Self::default()), Err(e) => Err(e).context("looking up OS features"), } } } #[derive(Default)] pub(super) struct LiveInitrd { /// OS features features: OsFeatures, /// The initrd for the live system initrd: Initrd, /// The Ignition config for the live system live: Option<Ignition>, /// The Ignition config for the destination system dest: Option<Ignition>, /// User-supplied Ignition configs for the dest system, which might be /// merged into the dest config or might become the dest config user_dest: Vec<ignition_config::Config>, /// The coreos-installer config for our own parameters, excluding custom /// configs supplied by the user installer: Option<InstallConfig>, /// Have the installer copy network configs, if we are running it installer_copy_network: bool, /// Ignition CAs for the dest system, if it has an Ignition config dest_ca: Vec<Vec<u8>>, /// Prefix for installer config filenames installer_serial: u32, } impl LiveInitrd { pub fn from_common(common: &CommonCustomizeConfig, features: OsFeatures) -> Result<Self> { let mut conf = Self { features, ..Default::default() }; for path in &common.dest_ignition { conf.dest_ignition(path)?; } if let Some(path) = &common.dest_device { conf.dest_device(path)?; } for arg in &common.dest_console { conf.dest_console(arg)?; } Console::maybe_warn_on_kargs( &common.dest_karg_append, "--dest-karg-append", "--dest-console", ); for arg in &common.dest_karg_append { conf.dest_karg_append(arg); } for arg in &common.dest_karg_delete { conf.dest_karg_delete(arg); } for path in &common.network_keyfile { conf.network_keyfile(path)?; } for path in &common.network_nmstate { conf.network_nmstate(path)?; } for path in &common.ignition_ca { conf.ignition_ca(path)?; } for path in &common.pre_install { conf.pre_install(path)?; } for path in &common.post_install { conf.post_install(path)?; } for path in &common.installer_config { conf.installer_config(path)?; } for path in &common.live_ignition { conf.live_config(path)?; } Ok(conf) } pub fn dest_ignition(&mut self, path: &str) -> Result<()> { let data = read(path).with_context(|| format!("reading {path}"))?; let (config, warnings) = ignition_config::Config::parse_slice(&data) .with_context(|| format!("parsing Ignition config {path}"))?; for warning in warnings { eprintln!("Warning parsing {path}: {warning}"); } self.user_dest.push(config); Ok(()) } pub fn dest_device(&mut self, device: &str) -> Result<()> { self.installer .get_or_insert_with(Default::default) .dest_device = Some(device.into()); Ok(()) } pub fn dest_console(&mut self, console: &Console) -> Result<()> { if !self.features.installer_config_directives.console { bail!("This OS image does not support customizing the destination console."); } self.installer .get_or_insert_with(Default::default) .console .push(console.clone()); Ok(()) } pub fn dest_karg_append(&mut self, arg: &str) { self.installer .get_or_insert_with(Default::default) .append_karg .push(arg.into()); } pub fn dest_karg_delete(&mut self, arg: &str) { self.installer .get_or_insert_with(Default::default) .delete_karg .push(arg.into()); } pub fn network_keyfile(&mut self, path: &str) -> Result<()> { if !self.features.live_initrd_network { bail!("This OS image does not support customizing network settings."); } let data = read(path).with_context(|| format!("reading {path}"))?; let name = filename(path)?; let path = format!("{INITRD_NETWORK_DIR}/{name}"); if self.initrd.get(&path).is_some() { bail!("config already specifies keyfile {}", name); } self.initrd.add(&path, data); self.installer_copy_network = true; Ok(()) } pub fn network_nmstate(&mut self, path: &str) -> Result<()> { if !self.features.live_initrd_network { bail!("This OS image does not support customizing network settings."); } let net_state_reader = std::fs::File::open(path).context("opening nmstate file")?; // Despite of the name the serde_yaml is able to parse JSON too. let net_state: NetworkState = serde_yaml::from_reader(net_state_reader).context("parsing nmstate")?; let generated_conf = net_state .gen_conf() .context("generating configuration from nmstate")?; let nm_connections = generated_conf .get("NetworkManager") .context("extracting NetworkManager generated config")?; for (nm_con_file_name, nm_con_content) in nm_connections { let nm_con_path = Path::new(INITRD_NETWORK_DIR).join(nm_con_file_name); let nm_con_path_str = nm_con_path .to_str() .context("converting generated NetworkManager keyfile path to UTF-8")?; if self.initrd.get(nm_con_path_str).is_some() { bail!("config already specifies keyfile {}", nm_con_path_str); } self.initrd .add(nm_con_path_str, nm_con_content.as_bytes().to_vec()); } self.installer_copy_network = true; Ok(()) } pub fn ignition_ca(&mut self, path: &str) -> Result<()> { let data = read(path).with_context(|| format!("reading {path}"))?; self.live .get_or_insert_with(Default::default) .add_ca(&data)?; self.dest_ca.push(data); Ok(()) } pub fn pre_install(&mut self, path: &str) -> Result<()> { self.install_hook( path, "pre", "After=coreos-installer-pre.target\nBefore=coreos-installer.service", "coreos-installer.service", ) } pub fn post_install(&mut self, path: &str) -> Result<()> { self.install_hook( path, "post", "After=coreos-installer.service\nBefore=coreos-installer.target", "coreos-installer.target", ) } #[allow(clippy::format_in_format_args)] fn install_hook( &mut self, path: &str, typ: &str, deps: &str, install_target: &str, ) -> Result<()> { let data = read(path).with_context(|| format!("reading {path}"))?; let name = filename(path)?; let live = self.live.get_or_insert_with(Default::default); live.add_file(format!("/usr/local/bin/{typ}-install-{name}"), &data, 0o700)?; live.add_unit( format!("{typ}-install-{name}.service"), format!( "# Generated by coreos-installer {{iso|pxe}} customize [Unit] Description={typ_title}-Install Script ({name}) Documentation=https://coreos.github.io/coreos-installer/customizing-install/ {deps} [Service] Type=oneshot ExecStart=/usr/local/bin/{typ}-install-{name} RemainAfterExit=true StandardOutput=kmsg+console StandardError=kmsg+console [Install] RequiredBy={install_target}", name = name, typ = typ, typ_title = format!("{}{}", typ[..1].to_uppercase(), &typ[1..]), deps = deps, install_target = install_target ), true, ) } pub fn installer_config(&mut self, path: &str) -> Result<()> { let data = read(path).with_context(|| format!("reading {path}"))?; // we don't validate but at least we parse serde_yaml::from_slice::<InstallConfig>(&data) .with_context(|| format!("parsing installer config {path}"))?; self.installer_config_bytes(&filename(path)?, &data) } fn installer_config_bytes(&mut self, filename: &str, data: &[u8]) -> Result<()> { if !self.features.installer_config { bail!("This OS image does not support customizing installer configuration."); } self.live.get_or_insert_with(Default::default).add_file( format!( "/etc/coreos/installer.d/{:04}-{}", self.installer_serial, filename ), data, 0o600, )?; self.installer_serial += 1; Ok(()) } pub fn live_config(&mut self, path: &str) -> Result<()> { let data = read(path).with_context(|| format!("reading {path}"))?; // we don't validate but at least we parse let (config, warnings) = ignition_config::Config::parse_slice(&data) .with_context(|| format!("parsing Ignition config {path}"))?; for warning in warnings { eprintln!("Warning parsing {path}: {warning}"); } self.live .get_or_insert_with(Default::default) .merge_config(&config) .with_context(|| format!("merging Ignition config {path}")) } pub fn into_initrd(mut self) -> Result<Initrd> { if self.dest.is_some() || !self.user_dest.is_empty() { // Embed dest config in live and installer configs // We now know we'll have a dest config, so add CAs to it for ca in self.dest_ca.drain(..) { self.dest.get_or_insert_with(Default::default).add_ca(&ca)?; } let data = if self.dest.is_none() && self.user_dest.len() == 1 { // Special case: the user supplied exactly one dest config // and we didn't add any dest config directives of our own. // Avoid another level of wrapping by embedding the user's // dest config directly. let mut buf = serde_json::to_vec(&self.user_dest.pop().unwrap()) .context("serializing dest Ignition config")?; buf.push(b'\n'); buf } else { let dest = self.dest.get_or_insert_with(Default::default); for user_dest in self.user_dest.drain(..) { dest.merge_config(&user_dest)?; } dest.to_bytes()? }; let conf = self.installer.get_or_insert_with(Default::default); assert!(conf.ignition_file.is_none()); let dest_path = "/etc/coreos/dest.ign"; self.live.get_or_insert_with(Default::default).add_file( dest_path.into(), &data, 0o600, )?; conf.ignition_file = Some(dest_path.into()); } if self.installer_serial > 0 || self.installer.is_some() { // The installer will run; apply deferred settings if let Some(device) = self.installer.as_ref().and_then(|c| c.dest_device.as_ref()) { eprintln!( "Boot media will automatically install to {device} without confirmation." ); } else { eprintln!("Boot media will automatically run installer."); } if self.installer_copy_network { self.installer .get_or_insert_with(Default::default) .copy_network = true; } } if let Some(conf) = self.installer.take() { // Embed installer config in live config self.installer_config_bytes( "customize.yaml", &serde_yaml::to_string(&conf) .context("serializing installer config")? .into_bytes(), )?; } // Embed live config, if we have one, in the initrd. Avoid embedding // an empty config because the ISO disables autologin if it sees one. if let Some(live) = self.live { self.initrd.add(INITRD_IGNITION_PATH, live.to_bytes()?); } Ok(self.initrd) } }