git.delta.rocks / jrsonnet / refs/commits / 15ef984b843b

difftreelog

source

cmds/fleet/src/cmds/build_systems.rs6.1 KiBsourcehistory
1use std::{env::current_dir, process::Stdio, time::Duration};23use crate::{command::CommandExt, host::Config};4use anyhow::Result;5use clap::Parser;6use tokio::{process::Command, task::LocalSet, time::sleep};7use tracing::{error, field, info, info_span, warn, Instrument};89#[derive(Parser, Clone)]10pub struct BuildSystems {11	/// Do not continue on error12	#[clap(long)]13	fail_fast: bool,14	/// Run builds as sudo15	#[clap(long)]16	privileged_build: bool,17	#[clap(subcommand)]18	subcommand: Subcommand,19}2021enum UploadAction {22	Test,23	Boot,24	Switch,25}26impl UploadAction {27	fn name(&self) -> &'static str {28		match self {29			UploadAction::Test => "test",30			UploadAction::Boot => "boot",31			UploadAction::Switch => "switch",32		}33	}3435	pub(crate) fn should_switch_profile(&self) -> bool {36		matches!(self, Self::Switch | Self::Test)37	}38}3940enum PackageAction {41	SdImage,42	InstallationCd,43}44impl PackageAction {45	fn build_attr(&self) -> String {46		match self {47			PackageAction::SdImage => "sdImage".to_owned(),48			PackageAction::InstallationCd => "installationCd".to_owned(),49		}50	}51}5253enum Action {54	Upload { action: Option<UploadAction> },55	Package(PackageAction),56}57impl Action {58	fn build_attr(&self) -> String {59		match self {60			Action::Upload { .. } => "toplevel".to_owned(),61			Action::Package(p) => p.build_attr(),62		}63	}64}6566impl From<Subcommand> for Action {67	fn from(s: Subcommand) -> Self {68		match s {69			Subcommand::Upload => Self::Upload { action: None },70			Subcommand::Test => Self::Upload {71				action: Some(UploadAction::Test),72			},73			Subcommand::Boot => Self::Upload {74				action: Some(UploadAction::Boot),75			},76			Subcommand::Switch => Self::Upload {77				action: Some(UploadAction::Switch),78			},79			Subcommand::SdImage => Self::Package(PackageAction::SdImage),80			Subcommand::InstallationCd => Self::Package(PackageAction::InstallationCd),81		}82	}83}8485#[derive(Parser, Clone)]86enum Subcommand {87	/// Upload, but do not switch88	Upload,89	/// Upload + switch to built system until reboot90	Test,91	/// Upload + switch to built system after reboot92	Boot,93	/// Upload + test + boot94	Switch,9596	/// Build SD .img image97	SdImage,98	/// Build an installation cd ISO image99	InstallationCd,100}101102impl BuildSystems {103	async fn build_task(self, config: Config, host: String) -> Result<()> {104		info!("building");105		let action = Action::from(self.subcommand.clone());106		let built = {107			let dir = tempfile::tempdir()?;108			dir.path().to_owned()109		};110111		let mut nix_build = if self.privileged_build {112			let mut out = Command::new("sudo");113			out.arg("nix");114			out115		} else {116			Command::new("nix")117		};118		nix_build119			.args([120				"build",121				"--impure",122				"--json",123				// "--show-trace",124				"--no-link",125				"--out-link",126			])127			.arg(&built)128			.arg(129				config.configuration_attr_name(&format!(130					"buildSystems.{}.{host}",131					action.build_attr()132				)),133			)134			.args(&config.nix_args);135136		nix_build.run_nix().await?;137		let built = std::fs::canonicalize(built)?;138139		match action {140			Action::Upload { action } => {141				if !config.is_local(&host) {142					info!("uploading system closure");143					let mut tries = 0;144					loop {145						match Command::new("nix")146							.args(["copy", "--to"])147							.arg(format!("ssh://root@{}", host))148							.arg(&built)149							.inherit_stdio()150							.run_nix()151							.await152						{153							Ok(()) => break,154							Err(e) if tries < 3 => {155								tries += 1;156								warn!("Copy failure ({}/3): {}", tries, e);157								sleep(Duration::from_millis(5000)).await;158							}159							Err(e) => return Err(e),160						}161					}162				}163				if let Some(action) = action {164					if action.should_switch_profile() {165						info!("switching generation");166						config167							.command_on(&host, "nix-env", true)168							.args(["-p", "/nix/var/nix/profiles/system", "--set"])169							.arg(&built)170							.inherit_stdio()171							.run()172							.await?;173					}174					info!("executing activation script");175					let mut switch_script = built.clone();176					switch_script.push("bin");177					switch_script.push("switch-to-configuration");178					config179						.command_on(&host, switch_script, true)180						.arg(action.name())181						.stdout(Stdio::inherit())182						.run()183						.await?;184				}185			}186			Action::Package(PackageAction::SdImage) => {187				let mut out = current_dir()?;188				out.push(format!("sd-image-{}", host));189190				info!("building sd image to {:?}", out);191				let mut nix_build = if self.privileged_build {192					let mut out = Command::new("sudo");193					out.arg("nix");194					out195				} else {196					Command::new("nix")197				};198				nix_build199					.args(["build", "--impure", "--no-link", "--out-link"])200					.arg(&out)201					.arg(config.configuration_attr_name(&format!("buildSystems.sdImage.{}", host,)))202					.args(&config.nix_args);203				if !self.fail_fast {204					nix_build.arg("--keep-going");205				}206207				nix_build.inherit_stdio().run_nix().await?;208			}209			Action::Package(PackageAction::InstallationCd) => {210				let mut out = current_dir()?;211				out.push(format!("installation-cd-{}", host));212213				info!("building sd image to {:?}", out);214				let mut nix_build = if self.privileged_build {215					let mut out = Command::new("sudo");216					out.arg("nix");217					out218				} else {219					Command::new("nix")220				};221				nix_build222					.args(["build", "--impure", "--no-link", "--out-link"])223					.arg(&out)224					.arg(225						config.configuration_attr_name(&format!(226							"buildSystems.installationCd.{}",227							host,228						)),229					)230					.args(&config.nix_args);231				if !self.fail_fast {232					nix_build.arg("--keep-going");233				}234235				nix_build.inherit_stdio().run_nix().await?;236			}237		};238		Ok(())239	}240241	pub async fn run(self, config: &Config) -> Result<()> {242		let hosts = config.list_hosts().await?;243		let set = LocalSet::new();244		let this = &self;245		for host in hosts.iter() {246			if config.should_skip(host) {247				continue;248			}249			let config = config.clone();250			let host = host.clone();251			let this = this.clone();252			let span = info_span!("deployment", host = field::display(&host));253			set.spawn_local(254				(async move {255					match this.build_task(config, host).await {256						Ok(_) => {}257						Err(e) => {258							error!("failed to deploy host: {}", e)259						}260					}261				})262				.instrument(span),263			);264		}265		set.await;266		Ok(())267	}268}