diff --git a/src/command/apply_local.rs b/src/command/apply_local.rs index c375560..fe44826 100644 --- a/src/command/apply_local.rs +++ b/src/command/apply_local.rs @@ -105,7 +105,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<( let target = { if let Some(info) = hive.deployment_info_single(&hostname).await.unwrap() { - let nix_options = hive.nix_options().await.unwrap(); + let nix_options = hive.nix_options_with_builders().await.unwrap(); if !info.allows_local_deployment() { log::error!("Local deployment is not enabled for host {}.", hostname.as_str()); log::error!("Hint: Set deployment.allowLocalDeployment to true."); diff --git a/src/nix/deployment/mod.rs b/src/nix/deployment/mod.rs index e5db73a..77aec72 100644 --- a/src/nix/deployment/mod.rs +++ b/src/nix/deployment/mod.rs @@ -20,6 +20,7 @@ use itertools::Itertools; use crate::progress::Sender as ProgressSender; use crate::job::{JobMonitor, JobHandle, JobType, JobState}; use crate::util; +use super::NixOptions; use super::{ Hive, @@ -55,7 +56,7 @@ pub struct Deployment { options: Options, /// Options passed to Nix invocations. - nix_options: Vec, + nix_options: NixOptions, /// Handle to send messages to the ProgressOutput. progress: Option, @@ -106,7 +107,7 @@ impl Deployment { hive, goal, options: Options::default(), - nix_options: Vec::new(), + nix_options: NixOptions::default(), progress, nodes: targets.keys().cloned().collect(), targets, @@ -133,7 +134,7 @@ impl Deployment { monitor.set_label_width(width); } - let nix_options = self.hive.nix_options().await?; + let nix_options = self.hive.nix_options_with_builders().await?; self.nix_options = nix_options; if self.goal == Goal::UploadKeys { diff --git a/src/nix/hive/mod.rs b/src/nix/hive/mod.rs index acab5ed..0870a41 100644 --- a/src/nix/hive/mod.rs +++ b/src/nix/hive/mod.rs @@ -11,7 +11,7 @@ use validator::Validate; use super::{ Flake, - ColmenaResult, + NixOptions, NodeName, NodeConfig, NodeFilter, @@ -19,6 +19,7 @@ use super::{ StorePath, }; use super::deployment::TargetNode; +use crate::error::ColmenaResult; use crate::util::{CommandExecution, CommandExt}; use crate::job::JobHandle; @@ -82,8 +83,8 @@ pub struct Hive { /// Whether to pass --show-trace in Nix commands. show_trace: bool, - /// The cached --builders expression. - builders: RwLock>>, + /// The cached machines_file expression. + machines_file: RwLock>>, } impl Hive { @@ -98,7 +99,7 @@ impl Hive { context_dir, eval_nix: eval_nix.into_temp_path(), show_trace: false, - builders: RwLock::new(None), + machines_file: RwLock::new(None), }) } @@ -110,11 +111,20 @@ impl Hive { self.show_trace = value; } - pub async fn nix_options(&self) -> ColmenaResult> { - let mut options = self.builder_args().await?; + /// Returns Nix options to set for this Hive. + pub fn nix_options(&self) -> NixOptions { + let mut options = NixOptions::default(); + options.set_show_trace(self.show_trace); + options + } - if self.show_trace { - options.push("--show-trace".to_owned()); + /// Returns Nix options to set for this Hive, with configured remote builders. + pub async fn nix_options_with_builders(&self) -> ColmenaResult { + let mut options = NixOptions::default(); + options.set_show_trace(self.show_trace); + + if let Some(machines_file) = self.machines_file().await? { + options.set_builders(Some(format!("@{}", machines_file))); } Ok(options) @@ -266,7 +276,7 @@ impl Hive { .collect() } - /// Evaluates an expression using values from the configuration + /// Evaluates an expression using values from the configuration. pub async fn introspect(&self, expression: String, instantiate: bool) -> ColmenaResult { if instantiate { let expression = format!("hive.introspect ({})", expression); @@ -279,10 +289,10 @@ impl Hive { } } - /// Retrieve machinesFile setting for the hive. + /// Retrieve the machinesFile setting for the Hive. async fn machines_file(&self) -> ColmenaResult> { - if let Some(builders_opt) = &*self.builders.read().await { - return Ok(builders_opt.clone()); + if let Some(machines_file) = &*self.machines_file.read().await { + return Ok(machines_file.clone()); } let expr = "toJSON (hive.meta.machinesFile or null)"; @@ -290,26 +300,11 @@ impl Hive { .capture_json().await?; let parsed: Option = serde_json::from_str(&s).unwrap(); - self.builders.write().await.replace(parsed.clone()); + self.machines_file.write().await.replace(parsed.clone()); Ok(parsed) } - /// Returns Nix arguments to set builders. - async fn builder_args(&self) -> ColmenaResult> { - let mut options = Vec::new(); - - if let Some(machines_file) = self.machines_file().await? { - options.append(&mut vec![ - "--option".to_owned(), - "builders".to_owned(), - format!("@{}", machines_file), - ]); - } - - Ok(options) - } - fn nix_instantiate(&self, expression: &str) -> NixInstantiate { NixInstantiate::new(self, expression.to_owned()) } @@ -332,7 +327,7 @@ impl<'hive> NixInstantiate<'hive> { } } - fn instantiate(self) -> Command { + fn instantiate(&self) -> Command { // FIXME: unwrap // Technically filenames can be arbitrary byte strings (OsStr), // but Nix may not like it... @@ -365,38 +360,34 @@ impl<'hive> NixInstantiate<'hive> { } } - if self.hive.show_trace { - command.arg("--show-trace"); - } - command } fn eval(self) -> Command { let mut command = self.instantiate(); + let options = self.hive.nix_options(); command.arg("--eval").arg("--json").arg("--strict") // Ensures the derivations are instantiated // Required for system profile evaluation and IFD - .arg("--read-write-mode"); + .arg("--read-write-mode") + .args(options.to_args()); command } async fn instantiate_with_builders(self) -> ColmenaResult { - let hive = self.hive; + let options = self.hive.nix_options_with_builders().await?; let mut command = self.instantiate(); - let builder_args = hive.builder_args().await?; - command.args(&builder_args); + command.args(options.to_args()); Ok(command) } async fn eval_with_builders(self) -> ColmenaResult { - let hive = self.hive; + let options = self.hive.nix_options_with_builders().await?; let mut command = self.eval(); - let builder_args = hive.builder_args().await?; - command.args(&builder_args); + command.args(options.to_args()); Ok(command) } diff --git a/src/nix/host/local.rs b/src/nix/host/local.rs index 9ba73df..e573da9 100644 --- a/src/nix/host/local.rs +++ b/src/nix/host/local.rs @@ -6,7 +6,7 @@ use async_trait::async_trait; use tokio::process::Command; use crate::error::{ColmenaResult, ColmenaError}; -use crate::nix::{StorePath, Profile, Goal, Key, SYSTEM_PROFILE, CURRENT_PROFILE}; +use crate::nix::{StorePath, Profile, Goal, Key, NixOptions, SYSTEM_PROFILE, CURRENT_PROFILE}; use crate::util::{CommandExecution, CommandExt}; use crate::job::JobHandle; use super::{CopyDirection, CopyOptions, Host, key_uploader}; @@ -18,11 +18,11 @@ use super::{CopyDirection, CopyOptions, Host, key_uploader}; #[derive(Debug)] pub struct Local { job: Option, - nix_options: Vec, + nix_options: NixOptions, } impl Local { - pub fn new(nix_options: Vec) -> Self { + pub fn new(nix_options: NixOptions) -> Self { Self { job: None, nix_options, @@ -39,7 +39,7 @@ impl Host for Local { async fn realize_remote(&mut self, derivation: &StorePath) -> ColmenaResult> { let mut command = Command::new("nix-store"); - command.args(self.nix_options.clone()); + command.args(self.nix_options.to_args()); command .arg("--no-gc-warning") .arg("--realise") diff --git a/src/nix/host/mod.rs b/src/nix/host/mod.rs index 3695dfe..a178b75 100644 --- a/src/nix/host/mod.rs +++ b/src/nix/host/mod.rs @@ -2,8 +2,9 @@ use std::collections::HashMap; use async_trait::async_trait; -use super::{StorePath, Profile, Goal, ColmenaResult, ColmenaError, Key}; +use crate::error::{ColmenaError, ColmenaResult}; use crate::job::JobHandle; +use super::{StorePath, Profile, Goal, Key, NixOptions}; mod ssh; pub use ssh::Ssh; @@ -13,7 +14,7 @@ pub use local::Local; mod key_uploader; -pub(crate) fn local(nix_options: Vec) -> Box { +pub(crate) fn local(nix_options: NixOptions) -> Box { Box::new(Local::new(nix_options)) } diff --git a/src/nix/mod.rs b/src/nix/mod.rs index 515979d..e539d5f 100644 --- a/src/nix/mod.rs +++ b/src/nix/mod.rs @@ -81,6 +81,22 @@ pub struct NodeConfig { keys: HashMap, } +/// Nix options. +#[derive(Debug, Clone)] +pub struct NixOptions { + /// Whether to pass --show-trace. + show_trace: bool, + + /// Designated builders. + /// + /// See . + /// + /// Valid examples: + /// - `@/path/to/machines` + /// - `builder@host.tld riscv64-linux /home/nix/.ssh/keys/builder.key 8 1 kvm` + builders: Option, +} + impl NodeName { /// Returns the string. pub fn as_str(&self) -> &str { @@ -150,6 +166,43 @@ impl NodeConfig { } } +impl Default for NixOptions { + fn default() -> Self { + Self { + show_trace: false, + builders: None, + } + } +} + +impl NixOptions { + pub fn set_show_trace(&mut self, show_trace: bool) { + self.show_trace = show_trace; + } + + pub fn set_builders(&mut self, builders: Option) { + self.builders = builders; + } + + pub fn to_args(&self) -> Vec { + let mut options = Vec::new(); + + if let Some(builders) = &self.builders { + options.append(&mut vec![ + "--option".to_string(), + "builders".to_string(), + builders.clone(), + ]); + } + + if self.show_trace { + options.push("--show-trace".to_string()); + } + + options + } +} + fn validate_keys(keys: &HashMap) -> Result<(), ValidationErrorType> { // Bad secret names: // - /etc/passwd