git.delta.rocks / jrsonnet / refs/commits / 1e20ff1f8900

difftreelog

fix do not exit after first built system

xzkurnmtYaroslav Bolyukin2026-01-20parent: #fcbbd81.patch.diff
in: trunk

1 file changed

modifiedcmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth
before · cmds/fleet/src/cmds/build_systems.rs
1use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf};23use anyhow::Result;4use clap::Parser;5use fleet_base::{6	deploy::{DeployAction, deploy_task, upload_task},7	host::{Config, DeployKind, GenerationStorage},8	opts::FleetOpts,9};10use futures::{StreamExt as _, stream::FuturesUnordered};11use nix_eval::nix_go;12use tokio::task::spawn_blocking;13use tracing::{Instrument, error, field, info, info_span, warn};1415#[derive(Parser)]16pub struct Deploy {17	/// Disable automatic rollback18	#[clap(long)]19	disable_rollback: bool,20	/// Action to execute after system is built21	action: DeployAction,22}2324#[derive(Parser, Clone)]25pub struct BuildSystems {26	/// Attribute to build. Systems are deployed from "toplevel-fleet" attr, well-known used attributes27	/// are "sdImage"/"isoImage", and your configuration may include any other build attributes.28	#[clap(long, default_value = "toplevel-fleet")]29	build_attr: String,30}3132async fn build_task(config: Config, hostname: String, build_attr: &str) -> Result<PathBuf> {33	info!("building");34	let host = config.host(&hostname)?;35	// let action = Action::from(self.subcommand.clone());36	let nixos = host.nixos_config()?;37	let drv = nix_go!(nixos.system.build[{ build_attr }]);38	let out_output = spawn_blocking(move || drv.build("out"))39		.await40		.expect("system derivation build should not panic")?;4142	// We already have system profiles for backups.43	if !host.local {44		info!("adding gc root");45		let mut cmd = config.local_host().cmd("nix").await?;46		cmd.arg("build")47			.comparg(48				"--profile",49				format!(50					"/nix/var/nix/profiles/{}-{hostname}",51					config.data.gc_root_prefix52				),53			)54			.arg(&out_output);55		cmd.sudo().run_nix().await?;56	}5758	Ok(out_output)59}6061impl BuildSystems {62	pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {63		let hosts = opts.filter_skipped(config.list_hosts()?)?;64		let mut tasks = FuturesUnordered::new();65		let build_attr = self.build_attr.clone();66		for host in hosts {67			let config = config.clone();68			let span = info_span!("build", host = field::display(&host.name));69			let hostname = host.name;70			let build_attr = build_attr.clone();71			tasks.push(72				(async move {73					let built = match build_task(config, hostname.clone(), &build_attr).await {74						Ok(path) => path,75						Err(e) => {76							error!("failed to deploy host: {}", e);77							return;78						}79					};80					// TODO: Handle error81					let mut out = current_dir().expect("cwd exists");82					out.push(format!("built-{hostname}"));8384					info!("linking iso image to {:?}", out);85					if let Err(e) = symlink(built, out) {86						error!("failed to symlink: {e}")87					}88				})89				.instrument(span),90			);91		}92		for _task in tasks.next().await {}93		Ok(())94	}95}9697impl Deploy {98	pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {99		let hosts = opts.filter_skipped(config.list_hosts()?)?;100		let mut tasks = FuturesUnordered::new();101		for host in hosts.into_iter() {102			let config = config.clone();103			let span = info_span!("deploy", host = field::display(&host.name));104			let hostname = host.name.clone();105			let opts = opts.clone();106			if let Some(deploy_kind) = opts.action_attr::<DeployKind>(&host, "deploy_kind")? {107				host.set_deploy_kind(deploy_kind);108			};109			if let Some(destination) = opts.action_attr::<String>(&host, "dest")? {110				host.set_session_destination(destination);111			};112			if let Some(legacy) = opts.action_attr::<bool>(&host, "legacy_ssh_store")? {113				host.set_legacy_ssh_store(legacy);114			};115116			tasks.push(117				(async move {118					let built = match build_task(config.clone(), hostname.clone(), "toplevel-fleet")119						.await120					{121						Ok(path) => path,122						Err(e) => {123							error!("failed to build host system closure: {:?}", e);124							return;125						}126					};127128					let deploy_kind = match host.deploy_kind().await {129						Ok(v) => v,130						Err(e) => {131							error!("failed to query target deploy kind: {e}");132							return;133						}134					};135136					// TODO: Make disable_rollback a host attribute instead137					let mut disable_rollback = self.disable_rollback;138					if !disable_rollback && deploy_kind != DeployKind::Fleet {139						warn!("disabling rollback, as not supported by non-fleet deployment kinds");140						disable_rollback = true;141					}142143					let remote_path =144						match upload_task(&config, &host, GenerationStorage::Deployer, built).await145						{146							Ok(v) => v,147							Err(e) => {148								error!("upload failed: {e}");149								return;150							}151						};152153					if let Err(e) = deploy_task(154						self.action,155						&host,156						remote_path,157						match opts.action_attr(&host, "specialisation") {158							Ok(v) => v,159							_ => {160								error!("unreachable? failed to get specialization");161								return;162							}163						},164						disable_rollback,165					)166					.await167					{168						error!("activation failed: {e}");169					}170				})171				.instrument(span),172			);173		}174		for _task in tasks.next().await {}175		Ok(())176	}177}
after · cmds/fleet/src/cmds/build_systems.rs
1use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf};23use anyhow::Result;4use clap::Parser;5use fleet_base::{6	deploy::{DeployAction, deploy_task, upload_task},7	host::{Config, DeployKind, GenerationStorage},8	opts::FleetOpts,9};10use futures::{StreamExt as _, stream::FuturesUnordered};11use nix_eval::nix_go;12use tokio::task::spawn_blocking;13use tracing::{Instrument, error, field, info, info_span, warn};1415#[derive(Parser)]16pub struct Deploy {17	/// Disable automatic rollback18	#[clap(long)]19	disable_rollback: bool,20	/// Action to execute after system is built21	action: DeployAction,22}2324#[derive(Parser, Clone)]25pub struct BuildSystems {26	/// Attribute to build. Systems are deployed from "toplevel-fleet" attr, well-known used attributes27	/// are "sdImage"/"isoImage", and your configuration may include any other build attributes.28	#[clap(long, default_value = "toplevel-fleet")]29	build_attr: String,30}3132async fn build_task(config: Config, hostname: String, build_attr: &str) -> Result<PathBuf> {33	info!("building");34	let host = config.host(&hostname)?;35	// let action = Action::from(self.subcommand.clone());36	let nixos = host.nixos_config()?;37	let drv = nix_go!(nixos.system.build[{ build_attr }]);38	let out_output = spawn_blocking(move || drv.build("out"))39		.await40		.expect("system derivation build should not panic")?;4142	// We already have system profiles for backups.43	if !host.local {44		info!("adding gc root");45		let mut cmd = config.local_host().cmd("nix").await?;46		cmd.arg("build")47			.comparg(48				"--profile",49				format!(50					"/nix/var/nix/profiles/{}-{hostname}",51					config.data.gc_root_prefix52				),53			)54			.arg(&out_output);55		cmd.sudo().run_nix().await?;56	}5758	Ok(out_output)59}6061impl BuildSystems {62	pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {63		let hosts = opts.filter_skipped(config.list_hosts()?)?;64		let tasks = FuturesUnordered::new();65		let build_attr = self.build_attr.clone();66		for host in hosts {67			let config = config.clone();68			let span = info_span!("build", host = field::display(&host.name));69			let hostname = host.name;70			let build_attr = build_attr.clone();71			tasks.push(72				(async move {73					let built = match build_task(config, hostname.clone(), &build_attr).await {74						Ok(path) => path,75						Err(e) => {76							error!("failed to deploy host: {}", e);77							return;78						}79					};80					// TODO: Handle error81					let mut out = current_dir().expect("cwd exists");82					out.push(format!("built-{hostname}"));8384					info!("linking iso image to {:?}", out);85					if let Err(e) = symlink(built, out) {86						error!("failed to symlink: {e}")87					}88				})89				.instrument(span),90			);91		}92		tasks.collect::<Vec<()>>().await;93		Ok(())94	}95}9697impl Deploy {98	pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {99		let hosts = opts.filter_skipped(config.list_hosts()?)?;100		let mut tasks = FuturesUnordered::new();101		for host in hosts.into_iter() {102			let config = config.clone();103			let span = info_span!("deploy", host = field::display(&host.name));104			let hostname = host.name.clone();105			let opts = opts.clone();106			if let Some(deploy_kind) = opts.action_attr::<DeployKind>(&host, "deploy_kind")? {107				host.set_deploy_kind(deploy_kind);108			};109			if let Some(destination) = opts.action_attr::<String>(&host, "dest")? {110				host.set_session_destination(destination);111			};112			if let Some(legacy) = opts.action_attr::<bool>(&host, "legacy_ssh_store")? {113				host.set_legacy_ssh_store(legacy);114			};115116			tasks.push(117				(async move {118					let built = match build_task(config.clone(), hostname.clone(), "toplevel-fleet")119						.await120					{121						Ok(path) => path,122						Err(e) => {123							error!("failed to build host system closure: {:?}", e);124							return;125						}126					};127128					let deploy_kind = match host.deploy_kind().await {129						Ok(v) => v,130						Err(e) => {131							error!("failed to query target deploy kind: {e}");132							return;133						}134					};135136					// TODO: Make disable_rollback a host attribute instead137					let mut disable_rollback = self.disable_rollback;138					if !disable_rollback && deploy_kind != DeployKind::Fleet {139						warn!("disabling rollback, as not supported by non-fleet deployment kinds");140						disable_rollback = true;141					}142143					let remote_path =144						match upload_task(&config, &host, GenerationStorage::Deployer, built).await145						{146							Ok(v) => v,147							Err(e) => {148								error!("upload failed: {e}");149								return;150							}151						};152153					if let Err(e) = deploy_task(154						self.action,155						&host,156						remote_path,157						match opts.action_attr(&host, "specialisation") {158							Ok(v) => v,159							_ => {160								error!("unreachable? failed to get specialization");161								return;162							}163						},164						disable_rollback,165					)166					.await167					{168						error!("activation failed: {e}");169					}170				})171				.instrument(span),172			);173		}174		tasks.collect::<Vec<()>>().await;175		Ok(())176	}177}