difftreelog
feat manual connection destination
in: trunk
2 files changed
cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth1use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf};23use anyhow::{Result, anyhow};4use clap::Parser;5use fleet_base::{6 deploy::{DeployAction, deploy_task, upload_task},7 host::{Config, DeployKind, GenerationStorage},8 opts::FleetOpts,9};10use nix_eval::{NixBuildBatch, nix_go};11use tokio::task::LocalSet;12use tracing::{Instrument, error, field, info, info_span, warn};1314#[derive(Parser)]15pub struct Deploy {16 /// Disable automatic rollback17 #[clap(long)]18 disable_rollback: bool,19 /// Action to execute after system is built20 action: DeployAction,21}2223#[derive(Parser, Clone)]24pub struct BuildSystems {25 /// Attribute to build. Systems are deployed from "toplevel" attr, well-known used attributes26 /// are "sdImage"/"isoImage", and your configuration may include any other build attributes.27 #[clap(long, default_value = "toplevel")]28 build_attr: String,29}3031async fn build_task(32 config: Config,33 hostname: String,34 build_attr: &str,35 batch: Option<NixBuildBatch>,36) -> Result<PathBuf> {37 info!("building");38 let host = config.host(&hostname).await?;39 // let action = Action::from(self.subcommand.clone());40 let nixos = host.nixos_config().await?;41 let drv = nix_go!(nixos.system.build[{ build_attr }]);42 let outputs = drv.build_maybe_batch(batch).await?;43 let out_output = outputs44 .get("out")45 .ok_or_else(|| anyhow!("system build should produce \"out\" output"))?;4647 // We already have system profiles for backups.48 if !host.local {49 info!("adding gc root");50 let mut cmd = config.local_host().cmd("nix").await?;51 cmd.arg("build")52 .comparg(53 "--profile",54 format!(55 "/nix/var/nix/profiles/{}-{hostname}",56 config.data().gc_root_prefix57 ),58 )59 .arg(out_output);60 cmd.sudo().run_nix().await?;61 }6263 Ok(out_output.clone())64}6566impl BuildSystems {67 pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {68 let hosts = opts.filter_skipped(config.list_hosts().await?).await?;69 let set = LocalSet::new();70 let build_attr = self.build_attr.clone();71 let batch = (hosts.len() > 1).then(|| {72 config73 .nix_session74 .new_build_batch("build-hosts".to_string())75 });76 for host in hosts {77 let config = config.clone();78 let span = info_span!("build", host = field::display(&host.name));79 let hostname = host.name;80 let build_attr = build_attr.clone();81 let batch = batch.clone();82 set.spawn_local(83 (async move {84 let built = match build_task(config, hostname.clone(), &build_attr, batch).await85 {86 Ok(path) => path,87 Err(e) => {88 error!("failed to deploy host: {}", e);89 return;90 }91 };92 // TODO: Handle error93 let mut out = current_dir().expect("cwd exists");94 out.push(format!("built-{}", hostname));9596 info!("linking iso image to {:?}", out);97 if let Err(e) = symlink(built, out) {98 error!("failed to symlink: {e}")99 }100 })101 .instrument(span),102 );103 }104 drop(batch);105 set.await;106 Ok(())107 }108}109110impl Deploy {111 pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {112 let hosts = opts.filter_skipped(config.list_hosts().await?).await?;113 let set = LocalSet::new();114 let batch = (hosts.len() > 1).then(|| {115 config116 .nix_session117 .new_build_batch("deploy-hosts".to_string())118 });119 for host in hosts.into_iter() {120 let config = config.clone();121 let span = info_span!("deploy", host = field::display(&host.name));122 let hostname = host.name.clone();123 let opts = opts.clone();124 let batch = batch.clone();125 if let Some(deploy_kind) = opts.action_attr::<DeployKind>(&host, "deploy_kind").await? {126 host.set_deploy_kind(deploy_kind);127 };128 if let Some(destination) = opts.action_attr::<String>(&host, "dest").await? {129 host.set_session_destination(destination);130 };131132 set.spawn_local(133 (async move {134 let built =135 match build_task(config.clone(), hostname.clone(), "toplevel", batch).await136 {137 Ok(path) => path,138 Err(e) => {139 error!("failed to build host system closure: {}", e);140 return;141 }142 };143144 let deploy_kind = match host.deploy_kind().await {145 Ok(v) => v,146 Err(e) => {147 error!("failed to query target deploy kind: {e}");148 return;149 }150 };151152 // TODO: Make disable_rollback a host attribute instead153 let mut disable_rollback = self.disable_rollback;154 if !disable_rollback && deploy_kind != DeployKind::Fleet {155 warn!("disabling rollback, as not supported by non-fleet deployment kinds");156 disable_rollback = true;157 }158159 let remote_path =160 match upload_task(&config, &host, GenerationStorage::Deployer, built).await161 {162 Ok(v) => v,163 Err(e) => {164 error!("upload failed: {e}");165 return;166 }167 };168169 if let Err(e) = deploy_task(170 self.action,171 &host,172 remote_path,173 match opts.action_attr(&host, "specialisation").await {174 Ok(v) => v,175 _ => {176 error!("unreachable? failed to get specialization");177 return;178 }179 },180 disable_rollback,181 )182 .await183 {184 error!("activation failed: {e}");185 }186 })187 .instrument(span),188 );189 }190 drop(batch);191 set.await;192 Ok(())193 }194}crates/fleet-base/src/host.rsdiffbeforeafterboth--- a/crates/fleet-base/src/host.rs
+++ b/crates/fleet-base/src/host.rs
@@ -98,7 +98,9 @@
pub name: String,
groups: OnceCell<Vec<String>>,
+ // TODO: Both of those values are taken from host opts, there should be a cleaner way to specify it
deploy_kind: OnceCell<DeployKind>,
+ session_destination: OnceCell<String>,
pub host_config: Option<Value>,
pub nixos_config: OnceCell<Value>,
@@ -209,6 +211,12 @@
Ok(generations)
}
+ pub fn set_session_destination(&self, dest: String) {
+ self.session_destination
+ .set(dest)
+ .ok()
+ .expect("session destination is already set")
+ }
pub fn set_deploy_kind(&self, kind: DeployKind) {
self.deploy_kind
.set(kind)
@@ -217,7 +225,7 @@
}
pub async fn deploy_kind(&self) -> Result<DeployKind> {
if let Some(kind) = self.deploy_kind.get() {
- return Ok(kind.clone());
+ return Ok(*kind);
}
let is_fleet_managed = match self.file_exists("/etc/FLEET_HOST").await {
Ok(v) => v,
@@ -237,11 +245,7 @@
}
// TOCTOU is possible
let _ = self.deploy_kind.set(DeployKind::Fleet);
- Ok(self
- .deploy_kind
- .get()
- .expect("deploy kind is just set")
- .clone())
+ Ok(*self.deploy_kind.get().expect("deploy kind is just set"))
}
pub async fn escalation_strategy(&self) -> Result<EscalationStrategy> {
// Prefer sudo, as run0 has some gotchas with polkit
@@ -261,8 +265,10 @@
return Ok((*session).clone());
};
let session = SessionBuilder::default();
+
+ let dest = self.session_destination.get().unwrap_or(&self.name);
let session = session
- .connect(&self.name)
+ .connect(&dest)
.await
.map_err(|e| anyhow!("ssh error while connecting to {}: {e:#?}", self.name))?;
let session = Arc::new(session);
@@ -281,7 +287,7 @@
.arg("test -e \"$1\" && echo true || echo false")
.arg("_")
.arg(path);
- Ok(cmd.run_value().await?)
+ cmd.run_value().await
}
pub async fn read_file_bin(&self, path: impl AsRef<OsStr>) -> Result<Vec<u8>> {
let mut cmd = self.cmd("cat").await?;