difftreelog
fix do not exit after first built system
in: trunk
1 file changed
cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth1use 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}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}