Move nix::{NixResult, NixError} to error::{ColmenaResult, ColmenaError}

This commit is contained in:
Zhaofeng Li 2022-01-08 01:20:36 -08:00
parent 16ed9d8c66
commit 31fd1e49ac
26 changed files with 295 additions and 275 deletions

View file

@ -3,6 +3,7 @@ use std::path::PathBuf;
use clap::{Arg, App, ArgMatches, ArgSettings}; use clap::{Arg, App, ArgMatches, ArgSettings};
use crate::error::ColmenaError;
use crate::nix::deployment::{ use crate::nix::deployment::{
Deployment, Deployment,
Goal, Goal,
@ -11,7 +12,7 @@ use crate::nix::deployment::{
ParallelismLimit, ParallelismLimit,
}; };
use crate::progress::SimpleProgressOutput; use crate::progress::SimpleProgressOutput;
use crate::nix::{NixError, NodeFilter}; use crate::nix::NodeFilter;
use crate::util; use crate::util;
pub fn register_deploy_args(command: App) -> App { pub fn register_deploy_args(command: App) -> App {
@ -126,7 +127,7 @@ pub fn subcommand() -> App<'static> {
util::register_selector_args(command) util::register_selector_args(command)
} }
pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), NixError> { pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> {
let hive = util::hive_from_args(local_args).await?; let hive = util::hive_from_args(local_args).await?;
let ssh_config = env::var("SSH_CONFIG_FILE") let ssh_config = env::var("SSH_CONFIG_FILE")

View file

@ -5,13 +5,14 @@ use clap::{Arg, App, ArgMatches};
use tokio::fs; use tokio::fs;
use tokio::process::Command; use tokio::process::Command;
use crate::error::ColmenaError;
use crate::nix::deployment::{ use crate::nix::deployment::{
Deployment, Deployment,
Goal, Goal,
TargetNode, TargetNode,
Options, Options,
}; };
use crate::nix::{NixError, NodeName, host}; use crate::nix::{NodeName, host};
use crate::progress::SimpleProgressOutput; use crate::progress::SimpleProgressOutput;
use crate::util; use crate::util;
@ -57,7 +58,7 @@ By default, Colmena will deploy keys set in `deployment.keys` before activating
.takes_value(false)) .takes_value(false))
} }
pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), NixError> { pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> {
// Sanity check: Are we running NixOS? // Sanity check: Are we running NixOS?
if let Ok(os_release) = fs::read_to_string("/etc/os-release").await { if let Ok(os_release) = fs::read_to_string("/etc/os-release").await {
if !os_release.contains("ID=nixos\n") { if !os_release.contains("ID=nixos\n") {

View file

@ -2,8 +2,8 @@ use std::path::PathBuf;
use clap::{Arg, App, AppSettings, ArgMatches}; use clap::{Arg, App, AppSettings, ArgMatches};
use crate::error::ColmenaError;
use crate::util; use crate::util;
use crate::nix::NixError;
pub fn subcommand() -> App<'static> { pub fn subcommand() -> App<'static> {
subcommand_gen("eval") subcommand_gen("eval")
@ -41,7 +41,7 @@ For example, to retrieve the configuration of one node, you may write something
.takes_value(false)) .takes_value(false))
} }
pub async fn run(global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), NixError> { pub async fn run(global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> {
if let Some("introspect") = global_args.subcommand_name() { if let Some("introspect") = global_args.subcommand_name() {
log::warn!("`colmena introspect` has been renamed to `colmena eval`. Please update your scripts."); log::warn!("`colmena introspect` has been renamed to `colmena eval`. Please update your scripts.");
} }

View file

@ -6,8 +6,9 @@ use clap::{Arg, App, AppSettings, ArgMatches};
use futures::future::join_all; use futures::future::join_all;
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
use crate::nix::{NixError, NodeFilter}; use crate::error::ColmenaError;
use crate::job::{JobMonitor, JobState, JobType}; use crate::job::{JobMonitor, JobState, JobType};
use crate::nix::NodeFilter;
use crate::progress::SimpleProgressOutput; use crate::progress::SimpleProgressOutput;
use crate::util; use crate::util;
@ -54,7 +55,7 @@ It's recommended to use -- to separate Colmena options from the command to run.
util::register_selector_args(command) util::register_selector_args(command)
} }
pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), NixError> { pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> {
let hive = util::hive_from_args(local_args).await?; let hive = util::hive_from_args(local_args).await?;
let ssh_config = env::var("SSH_CONFIG_FILE") let ssh_config = env::var("SSH_CONFIG_FILE")
.ok().map(PathBuf::from); .ok().map(PathBuf::from);

View file

@ -1,13 +1,14 @@
use clap::{App, ArgMatches}; use clap::{App, ArgMatches};
use crate::nix::{NixCheck, NixError}; use crate::error::ColmenaError;
use crate::nix::NixCheck;
pub fn subcommand() -> App<'static> { pub fn subcommand() -> App<'static> {
App::new("nix-info") App::new("nix-info")
.about("Show information about the current Nix installation") .about("Show information about the current Nix installation")
} }
pub async fn run(_global_args: &ArgMatches, _local_args: &ArgMatches) -> Result<(), NixError> { pub async fn run(_global_args: &ArgMatches, _local_args: &ArgMatches) -> Result<(), ColmenaError> {
let check = NixCheck::detect().await; let check = NixCheck::detect().await;
check.print_version_info(); check.print_version_info();
check.print_flakes_info(false); check.print_flakes_info(false);

View file

@ -3,8 +3,9 @@ use std::time::Duration;
use clap::{App, AppSettings, ArgMatches}; use clap::{App, AppSettings, ArgMatches};
use tokio::time; use tokio::time;
use crate::error::{ColmenaError, ColmenaResult};
use crate::job::{JobMonitor, JobType}; use crate::job::{JobMonitor, JobType};
use crate::nix::{NixError, NixResult, NodeName}; use crate::nix::NodeName;
use crate::progress::{ProgressOutput, spinner::SpinnerOutput}; use crate::progress::{ProgressOutput, spinner::SpinnerOutput};
macro_rules! node { macro_rules! node {
@ -19,7 +20,7 @@ pub fn subcommand() -> App<'static> {
.setting(AppSettings::Hidden) .setting(AppSettings::Hidden)
} }
pub async fn run(_global_args: &ArgMatches, _local_args: &ArgMatches) -> Result<(), NixError> { pub async fn run(_global_args: &ArgMatches, _local_args: &ArgMatches) -> Result<(), ColmenaError> {
let mut output = SpinnerOutput::new(); let mut output = SpinnerOutput::new();
let (monitor, meta) = JobMonitor::new(output.get_sender()); let (monitor, meta) = JobMonitor::new(output.get_sender());
@ -52,7 +53,7 @@ pub async fn run(_global_args: &ArgMatches, _local_args: &ArgMatches) -> Result<
let (_, _) = tokio::join!(eval, build); let (_, _) = tokio::join!(eval, build);
Err(NixError::Unsupported) as NixResult<()> Err(ColmenaError::Unsupported) as ColmenaResult<()>
}); });
let (monitor, output, ret) = tokio::join!( let (monitor, output, ret) = tokio::join!(

103
src/error.rs Normal file
View file

@ -0,0 +1,103 @@
//! Custom error types.
use std::os::unix::process::ExitStatusExt;
use std::process::ExitStatus;
use snafu::Snafu;
use validator::ValidationErrors;
use crate::nix::{key, StorePath};
pub type ColmenaResult<T> = Result<T, ColmenaError>;
#[non_exhaustive]
#[derive(Debug, Snafu)]
pub enum ColmenaError {
#[snafu(display("I/O Error: {}", error))]
IoError { error: std::io::Error },
#[snafu(display("Nix returned invalid response: {}", output))]
BadOutput { output: String },
#[snafu(display("Nix exited with error code: {}", exit_code))]
NixFailure { exit_code: i32 },
#[snafu(display("Nix was killed by signal {}", signal))]
NixKilled { signal: i32 },
#[snafu(display("This operation is not supported"))]
Unsupported,
#[snafu(display("Invalid Nix store path"))]
InvalidStorePath,
#[snafu(display("Validation error"))]
ValidationError { errors: ValidationErrors },
#[snafu(display("Failed to upload keys: {}", error))]
KeyError { error: key::KeyError },
#[snafu(display("Store path {:?} is not a derivation", store_path))]
NotADerivation { store_path: StorePath },
#[snafu(display("Invalid NixOS system profile"))]
InvalidProfile,
#[snafu(display("Unknown active profile: {:?}", store_path))]
ActiveProfileUnknown { store_path: StorePath },
#[snafu(display("Could not determine current profile"))]
FailedToGetCurrentProfile,
#[snafu(display("Current Nix version does not support Flakes"))]
NoFlakesSupport,
#[snafu(display("Don't know how to connect to the node"))]
NoTargetHost,
#[snafu(display("Node name cannot be empty"))]
EmptyNodeName,
#[snafu(display("Filter rule cannot be empty"))]
EmptyFilterRule,
#[snafu(display("Deployment already executed"))]
DeploymentAlreadyExecuted,
#[snafu(display("Unknown error: {}", message))]
Unknown { message: String },
}
impl From<std::io::Error> for ColmenaError {
fn from(error: std::io::Error) -> Self {
Self::IoError { error }
}
}
impl From<key::KeyError> for ColmenaError {
fn from(error: key::KeyError) -> Self {
Self::KeyError { error }
}
}
impl From<ValidationErrors> for ColmenaError {
fn from(errors: ValidationErrors) -> Self {
Self::ValidationError { errors }
}
}
impl From<ExitStatus> for ColmenaError {
fn from(status: ExitStatus) -> Self {
match status.code() {
Some(exit_code) => Self::NixFailure { exit_code },
None => Self::NixKilled { signal: status.signal().unwrap() },
}
}
}
impl ColmenaError {
pub fn unknown(error: Box<dyn std::error::Error>) -> Self {
let message = error.to_string();
Self::Unknown { message }
}
}

View file

@ -13,7 +13,8 @@ use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
use tokio::time; use tokio::time;
use uuid::Uuid; use uuid::Uuid;
use crate::nix::{NixResult, NixError, NodeName}; use crate::error::{ColmenaResult, ColmenaError};
use crate::nix::NodeName;
use crate::progress::{Sender as ProgressSender, Message as ProgressMessage, Line, LineStyle}; use crate::progress::{Sender as ProgressSender, Message as ProgressMessage, Line, LineStyle};
pub type Sender = UnboundedSender<Event>; pub type Sender = UnboundedSender<Event>;
@ -192,7 +193,7 @@ pub enum EventPayload {
/// The job failed. /// The job failed.
/// ///
/// We can't pass the NixError because the wrapper needs to /// We can't pass the ColmenaError because the wrapper needs to
/// be able to return it as-is. /// be able to return it as-is.
Failure(String), Failure(String),
@ -277,7 +278,7 @@ impl JobMonitor {
} }
/// Starts the monitor. /// Starts the monitor.
pub async fn run_until_completion(mut self) -> NixResult<Self> { pub async fn run_until_completion(mut self) -> ColmenaResult<Self> {
if let Some(width) = self.label_width { if let Some(width) = self.label_width {
if let Some(sender) = &self.progress { if let Some(sender) = &self.progress {
sender.send(ProgressMessage::HintLabelWidth(width)).unwrap(); sender.send(ProgressMessage::HintLabelWidth(width)).unwrap();
@ -465,7 +466,7 @@ impl JobMonitor {
} }
/// Shows human-readable summary and performs cleanup. /// Shows human-readable summary and performs cleanup.
async fn finish(mut self) -> NixResult<Self> { async fn finish(mut self) -> ColmenaResult<Self> {
if let Some(sender) = self.progress.take() { if let Some(sender) = self.progress.take() {
sender.send(ProgressMessage::Complete).unwrap(); sender.send(ProgressMessage::Complete).unwrap();
} }
@ -500,7 +501,7 @@ impl JobHandleInner {
/// Creates a new job with a distinct ID. /// Creates a new job with a distinct ID.
/// ///
/// This sends out a Creation message with the metadata. /// This sends out a Creation message with the metadata.
pub fn create_job(&self, job_type: JobType, nodes: Vec<NodeName>) -> NixResult<JobHandle> { pub fn create_job(&self, job_type: JobType, nodes: Vec<NodeName>) -> ColmenaResult<JobHandle> {
let job_id = JobId::new(); let job_id = JobId::new();
let creation = JobCreation { let creation = JobCreation {
friendly_name: None, friendly_name: None,
@ -509,7 +510,7 @@ impl JobHandleInner {
}; };
if job_type == JobType::Meta { if job_type == JobType::Meta {
return Err(NixError::Unknown { message: "Cannot create a meta job!".to_string() }); return Err(ColmenaError::Unknown { message: "Cannot create a meta job!".to_string() });
} }
let new_handle = Arc::new(Self { let new_handle = Arc::new(Self {
@ -525,9 +526,9 @@ impl JobHandleInner {
/// Runs a closure, automatically updating the job monitor based on the result. /// Runs a closure, automatically updating the job monitor based on the result.
/// ///
/// This immediately transitions the state to Running. /// This immediately transitions the state to Running.
pub async fn run<F, U, T>(self: Arc<Self>, f: U) -> NixResult<T> pub async fn run<F, U, T>(self: Arc<Self>, f: U) -> ColmenaResult<T>
where U: FnOnce(Arc<Self>) -> F, where U: FnOnce(Arc<Self>) -> F,
F: Future<Output = NixResult<T>>, F: Future<Output = ColmenaResult<T>>,
{ {
self.run_internal(f, true).await self.run_internal(f, true).await
} }
@ -535,52 +536,52 @@ impl JobHandleInner {
/// Runs a closure, automatically updating the job monitor based on the result. /// Runs a closure, automatically updating the job monitor based on the result.
/// ///
/// This does not immediately transition the state to Running. /// This does not immediately transition the state to Running.
pub async fn run_waiting<F, U, T>(self: Arc<Self>, f: U) -> NixResult<T> pub async fn run_waiting<F, U, T>(self: Arc<Self>, f: U) -> ColmenaResult<T>
where U: FnOnce(Arc<Self>) -> F, where U: FnOnce(Arc<Self>) -> F,
F: Future<Output = NixResult<T>>, F: Future<Output = ColmenaResult<T>>,
{ {
self.run_internal(f, false).await self.run_internal(f, false).await
} }
/// Sends a line of child stdout to the job monitor. /// Sends a line of child stdout to the job monitor.
pub fn stdout(&self, output: String) -> NixResult<()> { pub fn stdout(&self, output: String) -> ColmenaResult<()> {
self.send_payload(EventPayload::ChildStdout(output)) self.send_payload(EventPayload::ChildStdout(output))
} }
/// Sends a line of child stderr to the job monitor. /// Sends a line of child stderr to the job monitor.
pub fn stderr(&self, output: String) -> NixResult<()> { pub fn stderr(&self, output: String) -> ColmenaResult<()> {
self.send_payload(EventPayload::ChildStderr(output)) self.send_payload(EventPayload::ChildStderr(output))
} }
/// Sends a human-readable message to the job monitor. /// Sends a human-readable message to the job monitor.
pub fn message(&self, message: String) -> NixResult<()> { pub fn message(&self, message: String) -> ColmenaResult<()> {
self.send_payload(EventPayload::Message(message)) self.send_payload(EventPayload::Message(message))
} }
/// Transitions to a new job state. /// Transitions to a new job state.
pub fn state(&self, new_state: JobState) -> NixResult<()> { pub fn state(&self, new_state: JobState) -> ColmenaResult<()> {
self.send_payload(EventPayload::NewState(new_state)) self.send_payload(EventPayload::NewState(new_state))
} }
/// Marks the job as successful, with a custom message. /// Marks the job as successful, with a custom message.
pub fn success_with_message(&self, message: String) -> NixResult<()> { pub fn success_with_message(&self, message: String) -> ColmenaResult<()> {
self.send_payload(EventPayload::SuccessWithMessage(message)) self.send_payload(EventPayload::SuccessWithMessage(message))
} }
/// Marks the job as noop. /// Marks the job as noop.
pub fn noop(&self, message: String) -> NixResult<()> { pub fn noop(&self, message: String) -> ColmenaResult<()> {
self.send_payload(EventPayload::Noop(message)) self.send_payload(EventPayload::Noop(message))
} }
/// Marks the job as failed. /// Marks the job as failed.
pub fn failure(&self, error: &NixError) -> NixResult<()> { pub fn failure(&self, error: &ColmenaError) -> ColmenaResult<()> {
self.send_payload(EventPayload::Failure(error.to_string())) self.send_payload(EventPayload::Failure(error.to_string()))
} }
/// Runs a closure, automatically updating the job monitor based on the result. /// Runs a closure, automatically updating the job monitor based on the result.
async fn run_internal<F, U, T>(self: Arc<Self>, f: U, report_running: bool) -> NixResult<T> async fn run_internal<F, U, T>(self: Arc<Self>, f: U, report_running: bool) -> ColmenaResult<T>
where U: FnOnce(Arc<Self>) -> F, where U: FnOnce(Arc<Self>) -> F,
F: Future<Output = NixResult<T>>, F: Future<Output = ColmenaResult<T>>,
{ {
if report_running { if report_running {
// Tell monitor we are starting // Tell monitor we are starting
@ -603,7 +604,7 @@ impl JobHandleInner {
} }
/// Sends an event to the job monitor. /// Sends an event to the job monitor.
fn send_payload(&self, payload: EventPayload) -> NixResult<()> { fn send_payload(&self, payload: EventPayload) -> ColmenaResult<()> {
if payload.privileged() { if payload.privileged() {
panic!("Tried to send privileged payload with JobHandle"); panic!("Tried to send privileged payload with JobHandle");
} }
@ -611,7 +612,7 @@ impl JobHandleInner {
let event = Event::new(self.job_id, payload); let event = Event::new(self.job_id, payload);
self.sender.send(event) self.sender.send(event)
.map_err(|e| NixError::unknown(Box::new(e)))?; .map_err(|e| ColmenaError::unknown(Box::new(e)))?;
Ok(()) Ok(())
} }
@ -619,9 +620,9 @@ impl JobHandleInner {
impl MetaJobHandle { impl MetaJobHandle {
/// Runs a closure, automatically updating the job monitor based on the result. /// Runs a closure, automatically updating the job monitor based on the result.
pub async fn run<F, U, T>(self, f: U) -> NixResult<T> pub async fn run<F, U, T>(self, f: U) -> ColmenaResult<T>
where U: FnOnce(JobHandle) -> F, where U: FnOnce(JobHandle) -> F,
F: Future<Output = NixResult<T>>, F: Future<Output = ColmenaResult<T>>,
{ {
let normal_handle = Arc::new(JobHandleInner { let normal_handle = Arc::new(JobHandleInner {
job_id: self.job_id, job_id: self.job_id,
@ -645,11 +646,11 @@ impl MetaJobHandle {
} }
/// Sends an event to the job monitor. /// Sends an event to the job monitor.
fn send_payload(&self, payload: EventPayload) -> NixResult<()> { fn send_payload(&self, payload: EventPayload) -> ColmenaResult<()> {
let event = Event::new(self.job_id, payload); let event = Event::new(self.job_id, payload);
self.sender.send(event) self.sender.send(event)
.map_err(|e| NixError::unknown(Box::new(e)))?; .map_err(|e| ColmenaError::unknown(Box::new(e)))?;
Ok(()) Ok(())
} }
@ -874,7 +875,7 @@ mod tests {
Ok(()) Ok(())
}).await?; }).await?;
Err(NixError::Unsupported) as NixResult<()> Err(ColmenaError::Unsupported) as ColmenaResult<()>
}); });
// Run until completion // Run until completion
@ -884,7 +885,7 @@ mod tests {
); );
match ret { match ret {
Err(NixError::Unsupported) => (), Err(ColmenaError::Unsupported) => (),
_ => { _ => {
panic!("Wrapper must return error as-is"); panic!("Wrapper must return error as-is");
} }

View file

@ -1,5 +1,6 @@
#![deny(unused_must_use)] #![deny(unused_must_use)]
mod error;
mod nix; mod nix;
mod cli; mod cli;
mod command; mod command;

View file

@ -26,8 +26,8 @@ use super::{
Host, Host,
NodeName, NodeName,
NodeConfig, NodeConfig,
NixError, ColmenaError,
NixResult, ColmenaResult,
Profile, Profile,
ProfileDerivation, ProfileDerivation,
CopyDirection, CopyDirection,
@ -120,9 +120,9 @@ impl Deployment {
/// ///
/// If a ProgressSender is supplied, then this should be run in parallel /// If a ProgressSender is supplied, then this should be run in parallel
/// with its `run_until_completion()` future. /// with its `run_until_completion()` future.
pub async fn execute(mut self) -> NixResult<()> { pub async fn execute(mut self) -> ColmenaResult<()> {
if self.executed { if self.executed {
return Err(NixError::DeploymentAlreadyExecuted); return Err(ColmenaError::DeploymentAlreadyExecuted);
} }
self.executed = true; self.executed = true;
@ -148,7 +148,7 @@ impl Deployment {
} }
join_all(futures).await join_all(futures).await
.into_iter().collect::<NixResult<Vec<()>>>()?; .into_iter().collect::<ColmenaResult<Vec<()>>>()?;
Ok(()) Ok(())
}); });
@ -173,7 +173,7 @@ impl Deployment {
} }
join_all(futures).await join_all(futures).await
.into_iter().collect::<NixResult<Vec<()>>>()?; .into_iter().collect::<ColmenaResult<Vec<()>>>()?;
Ok(()) Ok(())
}); });
@ -219,7 +219,7 @@ impl Deployment {
} }
/// Executes the deployment against a portion of nodes. /// Executes the deployment against a portion of nodes.
async fn execute_chunk(self: &DeploymentHandle, parent: JobHandle, mut chunk: TargetNodeMap) -> NixResult<()> { async fn execute_chunk(self: &DeploymentHandle, parent: JobHandle, mut chunk: TargetNodeMap) -> ColmenaResult<()> {
if self.goal == Goal::UploadKeys { if self.goal == Goal::UploadKeys {
unreachable!(); // some logic is screwed up unreachable!(); // some logic is screwed up
} }
@ -256,14 +256,14 @@ impl Deployment {
} }
join_all(futures).await join_all(futures).await
.into_iter().collect::<NixResult<Vec<()>>>()?; .into_iter().collect::<ColmenaResult<Vec<()>>>()?;
Ok(()) Ok(())
} }
/// Evaluates a set of nodes, returning their corresponding store derivations. /// Evaluates a set of nodes, returning their corresponding store derivations.
async fn evaluate_nodes(self: &DeploymentHandle, parent: JobHandle, nodes: Vec<NodeName>) async fn evaluate_nodes(self: &DeploymentHandle, parent: JobHandle, nodes: Vec<NodeName>)
-> NixResult<HashMap<NodeName, ProfileDerivation>> -> ColmenaResult<HashMap<NodeName, ProfileDerivation>>
{ {
let job = parent.create_job(JobType::Evaluate, nodes.clone())?; let job = parent.create_job(JobType::Evaluate, nodes.clone())?;
@ -280,12 +280,12 @@ impl Deployment {
} }
/// Only uploads keys to a node. /// Only uploads keys to a node.
async fn upload_keys_to_node(self: &DeploymentHandle, parent: JobHandle, mut target: TargetNode) -> NixResult<()> { async fn upload_keys_to_node(self: &DeploymentHandle, parent: JobHandle, mut target: TargetNode) -> ColmenaResult<()> {
let nodes = vec![target.name.clone()]; let nodes = vec![target.name.clone()];
let job = parent.create_job(JobType::UploadKeys, nodes)?; let job = parent.create_job(JobType::UploadKeys, nodes)?;
job.run(|_| async move { job.run(|_| async move {
if target.host.is_none() { if target.host.is_none() {
return Err(NixError::Unsupported); return Err(ColmenaError::Unsupported);
} }
let host = target.host.as_mut().unwrap(); let host = target.host.as_mut().unwrap();
@ -297,7 +297,7 @@ impl Deployment {
/// Builds a system profile directly on the node itself. /// Builds a system profile directly on the node itself.
async fn build_on_node(self: &DeploymentHandle, parent: JobHandle, mut target: TargetNode, profile_drv: ProfileDerivation) async fn build_on_node(self: &DeploymentHandle, parent: JobHandle, mut target: TargetNode, profile_drv: ProfileDerivation)
-> NixResult<(TargetNode, Profile)> -> ColmenaResult<(TargetNode, Profile)>
{ {
let nodes = vec![target.name.clone()]; let nodes = vec![target.name.clone()];
@ -306,7 +306,7 @@ impl Deployment {
let build_job = parent.create_job(JobType::Build, nodes.clone())?; let build_job = parent.create_job(JobType::Build, nodes.clone())?;
let (target, profile) = build_job.run(|job| async move { let (target, profile) = build_job.run(|job| async move {
if target.host.is_none() { if target.host.is_none() {
return Err(NixError::Unsupported); return Err(ColmenaError::Unsupported);
} }
let mut host = target.host.as_mut().unwrap(); let mut host = target.host.as_mut().unwrap();
@ -331,7 +331,7 @@ impl Deployment {
/// Builds and pushes a system profile on a node. /// Builds and pushes a system profile on a node.
async fn build_and_push_node(self: &DeploymentHandle, parent: JobHandle, mut target: TargetNode, profile_drv: ProfileDerivation) async fn build_and_push_node(self: &DeploymentHandle, parent: JobHandle, mut target: TargetNode, profile_drv: ProfileDerivation)
-> NixResult<(TargetNode, Profile)> -> ColmenaResult<(TargetNode, Profile)>
{ {
let nodes = vec![target.name.clone()]; let nodes = vec![target.name.clone()];
@ -361,7 +361,7 @@ impl Deployment {
let arc_self = self.clone(); let arc_self = self.clone();
let target = push_job.run(|job| async move { let target = push_job.run(|job| async move {
if target.host.is_none() { if target.host.is_none() {
return Err(NixError::Unsupported); return Err(ColmenaError::Unsupported);
} }
let host = target.host.as_mut().unwrap(); let host = target.host.as_mut().unwrap();
@ -383,7 +383,7 @@ impl Deployment {
/// ///
/// This will also upload keys to the node. /// This will also upload keys to the node.
async fn activate_node(self: DeploymentHandle, parent: JobHandle, mut target: TargetNode, profile: Profile) async fn activate_node(self: DeploymentHandle, parent: JobHandle, mut target: TargetNode, profile: Profile)
-> NixResult<()> -> ColmenaResult<()>
{ {
let nodes = vec![target.name.clone()]; let nodes = vec![target.name.clone()];
let target_name = target.name.clone(); let target_name = target.name.clone();
@ -437,7 +437,7 @@ impl Deployment {
if arc_self.options.force_replace_unknown_profiles { if arc_self.options.force_replace_unknown_profiles {
job.message("Warning: Remote profile is unknown, but unknown profiles are being ignored".to_string())?; job.message("Warning: Remote profile is unknown, but unknown profiles are being ignored".to_string())?;
} else { } else {
return Err(NixError::ActiveProfileUnknown { return Err(ColmenaError::ActiveProfileUnknown {
store_path: profile, store_path: profile,
}); });
} }

View file

@ -7,7 +7,7 @@ use std::process::Stdio;
use serde::Deserialize; use serde::Deserialize;
use tokio::process::Command; use tokio::process::Command;
use super::{NixCheck, NixError, NixResult}; use super::{NixCheck, ColmenaError, ColmenaResult};
/// A Nix Flake. /// A Nix Flake.
#[derive(Debug)] #[derive(Debug)]
@ -24,7 +24,7 @@ impl Flake {
/// ///
/// This will try to retrieve the resolved URL of the local flake /// This will try to retrieve the resolved URL of the local flake
/// in the specified directory. /// in the specified directory.
pub async fn from_dir<P: AsRef<Path>>(dir: P) -> NixResult<Self> { pub async fn from_dir<P: AsRef<Path>>(dir: P) -> ColmenaResult<Self> {
NixCheck::require_flake_support().await?; NixCheck::require_flake_support().await?;
let flake = dir.as_ref().as_os_str().to_str() let flake = dir.as_ref().as_os_str().to_str()
@ -39,7 +39,7 @@ impl Flake {
} }
/// Creates a flake from a Flake URI. /// Creates a flake from a Flake URI.
pub async fn from_uri(uri: String) -> NixResult<Self> { pub async fn from_uri(uri: String) -> ColmenaResult<Self> {
NixCheck::require_flake_support().await?; NixCheck::require_flake_support().await?;
Ok(Self { Ok(Self {
@ -69,7 +69,7 @@ struct FlakeMetadata {
impl FlakeMetadata { impl FlakeMetadata {
/// Resolves a flake. /// Resolves a flake.
async fn resolve(flake: &str) -> NixResult<Self> { async fn resolve(flake: &str) -> ColmenaResult<Self> {
let child = Command::new("nix") let child = Command::new("nix")
.args(&["flake", "metadata", "--json"]) .args(&["flake", "metadata", "--json"])
.args(&["--experimental-features", "nix-command flakes"]) .args(&["--experimental-features", "nix-command flakes"])
@ -86,7 +86,7 @@ impl FlakeMetadata {
serde_json::from_slice::<FlakeMetadata>(&output.stdout) serde_json::from_slice::<FlakeMetadata>(&output.stdout)
.map_err(|_| { .map_err(|_| {
let output = String::from_utf8_lossy(&output.stdout).to_string(); let output = String::from_utf8_lossy(&output.stdout).to_string();
NixError::BadOutput { output } ColmenaError::BadOutput { output }
}) })
} }
} }

View file

@ -11,7 +11,7 @@ use validator::Validate;
use super::{ use super::{
Flake, Flake,
NixResult, ColmenaResult,
NodeName, NodeName,
NodeConfig, NodeConfig,
NodeFilter, NodeFilter,
@ -39,7 +39,7 @@ pub enum HivePath {
} }
impl HivePath { impl HivePath {
pub async fn from_path<P: AsRef<Path>>(path: P) -> NixResult<Self> { pub async fn from_path<P: AsRef<Path>>(path: P) -> ColmenaResult<Self> {
let path = path.as_ref(); let path = path.as_ref();
if let Some(osstr) = path.file_name() { if let Some(osstr) = path.file_name() {
@ -87,7 +87,7 @@ pub struct Hive {
} }
impl Hive { impl Hive {
pub fn new(path: HivePath) -> NixResult<Self> { pub fn new(path: HivePath) -> ColmenaResult<Self> {
let mut eval_nix = NamedTempFile::new()?; let mut eval_nix = NamedTempFile::new()?;
eval_nix.write_all(HIVE_EVAL).unwrap(); eval_nix.write_all(HIVE_EVAL).unwrap();
@ -110,7 +110,7 @@ impl Hive {
self.show_trace = value; self.show_trace = value;
} }
pub async fn nix_options(&self) -> NixResult<Vec<String>> { pub async fn nix_options(&self) -> ColmenaResult<Vec<String>> {
let mut options = self.builder_args().await?; let mut options = self.builder_args().await?;
if self.show_trace { if self.show_trace {
@ -121,7 +121,7 @@ impl Hive {
} }
/// Convenience wrapper to filter nodes for CLI actions. /// Convenience wrapper to filter nodes for CLI actions.
pub async fn select_nodes(&self, filter: Option<NodeFilter>, ssh_config: Option<PathBuf>, ssh_only: bool) -> NixResult<HashMap<NodeName, TargetNode>> { pub async fn select_nodes(&self, filter: Option<NodeFilter>, ssh_config: Option<PathBuf>, ssh_only: bool) -> ColmenaResult<HashMap<NodeName, TargetNode>> {
let mut node_configs = None; let mut node_configs = None;
log::info!("Enumerating nodes..."); log::info!("Enumerating nodes...");
@ -197,13 +197,13 @@ impl Hive {
} }
/// Returns a list of all node names. /// Returns a list of all node names.
pub async fn node_names(&self) -> NixResult<Vec<NodeName>> { pub async fn node_names(&self) -> ColmenaResult<Vec<NodeName>> {
self.nix_instantiate("attrNames hive.nodes").eval() self.nix_instantiate("attrNames hive.nodes").eval()
.capture_json().await .capture_json().await
} }
/// Retrieve deployment info for all nodes. /// Retrieve deployment info for all nodes.
pub async fn deployment_info(&self) -> NixResult<HashMap<NodeName, NodeConfig>> { pub async fn deployment_info(&self) -> ColmenaResult<HashMap<NodeName, NodeConfig>> {
let configs: HashMap<NodeName, NodeConfig> = self.nix_instantiate("hive.deploymentConfig").eval_with_builders().await? let configs: HashMap<NodeName, NodeConfig> = self.nix_instantiate("hive.deploymentConfig").eval_with_builders().await?
.capture_json().await?; .capture_json().await?;
@ -217,14 +217,14 @@ impl Hive {
} }
/// Retrieve deployment info for a single node. /// Retrieve deployment info for a single node.
pub async fn deployment_info_single(&self, node: &NodeName) -> NixResult<Option<NodeConfig>> { pub async fn deployment_info_single(&self, node: &NodeName) -> ColmenaResult<Option<NodeConfig>> {
let expr = format!("hive.nodes.\"{}\".config.deployment or null", node.as_str()); let expr = format!("hive.nodes.\"{}\".config.deployment or null", node.as_str());
self.nix_instantiate(&expr).eval_with_builders().await? self.nix_instantiate(&expr).eval_with_builders().await?
.capture_json().await .capture_json().await
} }
/// Retrieve deployment info for a list of nodes. /// Retrieve deployment info for a list of nodes.
pub async fn deployment_info_selected(&self, nodes: &[NodeName]) -> NixResult<HashMap<NodeName, NodeConfig>> { pub async fn deployment_info_selected(&self, nodes: &[NodeName]) -> ColmenaResult<HashMap<NodeName, NodeConfig>> {
let nodes_expr = SerializedNixExpresssion::new(nodes)?; let nodes_expr = SerializedNixExpresssion::new(nodes)?;
let configs: HashMap<NodeName, NodeConfig> = self.nix_instantiate(&format!("hive.deploymentConfigSelected {}", nodes_expr.expression())) let configs: HashMap<NodeName, NodeConfig> = self.nix_instantiate(&format!("hive.deploymentConfigSelected {}", nodes_expr.expression()))
@ -246,7 +246,7 @@ impl Hive {
/// Evaluation may take up a lot of memory, so we make it possible /// Evaluation may take up a lot of memory, so we make it possible
/// to split up the evaluation process into chunks and run them /// to split up the evaluation process into chunks and run them
/// concurrently with other processes (e.g., build and apply). /// concurrently with other processes (e.g., build and apply).
pub async fn eval_selected(&self, nodes: &[NodeName], job: Option<JobHandle>) -> NixResult<HashMap<NodeName, ProfileDerivation>> { pub async fn eval_selected(&self, nodes: &[NodeName], job: Option<JobHandle>) -> ColmenaResult<HashMap<NodeName, ProfileDerivation>> {
let nodes_expr = SerializedNixExpresssion::new(nodes)?; let nodes_expr = SerializedNixExpresssion::new(nodes)?;
let expr = format!("hive.evalSelected {}", nodes_expr.expression()); let expr = format!("hive.evalSelected {}", nodes_expr.expression());
@ -267,7 +267,7 @@ impl Hive {
} }
/// 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) -> NixResult<String> { pub async fn introspect(&self, expression: String, instantiate: bool) -> ColmenaResult<String> {
if instantiate { if instantiate {
let expression = format!("hive.introspect ({})", expression); let expression = format!("hive.introspect ({})", expression);
self.nix_instantiate(&expression).instantiate_with_builders().await? self.nix_instantiate(&expression).instantiate_with_builders().await?
@ -280,7 +280,7 @@ impl Hive {
} }
/// Retrieve machinesFile setting for the hive. /// Retrieve machinesFile setting for the hive.
async fn machines_file(&self) -> NixResult<Option<String>> { async fn machines_file(&self) -> ColmenaResult<Option<String>> {
if let Some(builders_opt) = &*self.builders.read().await { if let Some(builders_opt) = &*self.builders.read().await {
return Ok(builders_opt.clone()); return Ok(builders_opt.clone());
} }
@ -296,7 +296,7 @@ impl Hive {
} }
/// Returns Nix arguments to set builders. /// Returns Nix arguments to set builders.
async fn builder_args(&self) -> NixResult<Vec<String>> { async fn builder_args(&self) -> ColmenaResult<Vec<String>> {
let mut options = Vec::new(); let mut options = Vec::new();
if let Some(machines_file) = self.machines_file().await? { if let Some(machines_file) = self.machines_file().await? {
@ -381,7 +381,7 @@ impl<'hive> NixInstantiate<'hive> {
command command
} }
async fn instantiate_with_builders(self) -> NixResult<Command> { async fn instantiate_with_builders(self) -> ColmenaResult<Command> {
let hive = self.hive; let hive = self.hive;
let mut command = self.instantiate(); let mut command = self.instantiate();
@ -391,7 +391,7 @@ impl<'hive> NixInstantiate<'hive> {
Ok(command) Ok(command)
} }
async fn eval_with_builders(self) -> NixResult<Command> { async fn eval_with_builders(self) -> ColmenaResult<Command> {
let hive = self.hive; let hive = self.hive;
let mut command = self.eval(); let mut command = self.eval();
@ -413,7 +413,7 @@ struct SerializedNixExpresssion {
} }
impl SerializedNixExpresssion { impl SerializedNixExpresssion {
pub fn new<T>(data: T) -> NixResult<Self> where T: Serialize { pub fn new<T>(data: T) -> ColmenaResult<Self> where T: Serialize {
let mut tmp = NamedTempFile::new()?; let mut tmp = NamedTempFile::new()?;
let json = serde_json::to_vec(&data).expect("Could not serialize data"); let json = serde_json::to_vec(&data).expect("Could not serialize data");
tmp.write_all(&json)?; tmp.write_all(&json)?;

View file

@ -12,8 +12,9 @@ use shell_escape::unix::escape;
use tokio::io::{AsyncWriteExt, BufReader}; use tokio::io::{AsyncWriteExt, BufReader};
use tokio::process::Child; use tokio::process::Child;
use crate::error::ColmenaResult;
use crate::job::JobHandle; use crate::job::JobHandle;
use crate::nix::{Key, NixResult}; use crate::nix::Key;
use crate::util::capture_stream; use crate::util::capture_stream;
const SCRIPT_TEMPLATE: &str = include_str!("./key_uploader.template.sh"); const SCRIPT_TEMPLATE: &str = include_str!("./key_uploader.template.sh");
@ -30,7 +31,7 @@ pub fn generate_script<'a>(key: &'a Key, destination: &'a Path, require_ownershi
escape(key_script.into()) escape(key_script.into())
} }
pub async fn feed_uploader(mut uploader: Child, key: &Key, job: Option<JobHandle>) -> NixResult<()> { pub async fn feed_uploader(mut uploader: Child, key: &Key, job: Option<JobHandle>) -> ColmenaResult<()> {
let mut reader = key.reader().await?; let mut reader = key.reader().await?;
let mut stdin = uploader.stdin.take().unwrap(); let mut stdin = uploader.stdin.take().unwrap();

View file

@ -5,10 +5,11 @@ use std::process::Stdio;
use async_trait::async_trait; use async_trait::async_trait;
use tokio::process::Command; use tokio::process::Command;
use super::{CopyDirection, CopyOptions, Host, key_uploader}; use crate::error::{ColmenaResult, ColmenaError};
use crate::nix::{StorePath, Profile, Goal, NixError, NixResult, Key, SYSTEM_PROFILE, CURRENT_PROFILE}; use crate::nix::{StorePath, Profile, Goal, Key, SYSTEM_PROFILE, CURRENT_PROFILE};
use crate::util::{CommandExecution, CommandExt}; use crate::util::{CommandExecution, CommandExt};
use crate::job::JobHandle; use crate::job::JobHandle;
use super::{CopyDirection, CopyOptions, Host, key_uploader};
/// The local machine running Colmena. /// The local machine running Colmena.
/// ///
@ -31,11 +32,11 @@ impl Local {
#[async_trait] #[async_trait]
impl Host for Local { impl Host for Local {
async fn copy_closure(&mut self, _closure: &StorePath, _direction: CopyDirection, _options: CopyOptions) -> NixResult<()> { async fn copy_closure(&mut self, _closure: &StorePath, _direction: CopyDirection, _options: CopyOptions) -> ColmenaResult<()> {
Ok(()) Ok(())
} }
async fn realize_remote(&mut self, derivation: &StorePath) -> NixResult<Vec<StorePath>> { async fn realize_remote(&mut self, derivation: &StorePath) -> ColmenaResult<Vec<StorePath>> {
let mut command = Command::new("nix-store"); let mut command = Command::new("nix-store");
command.args(self.nix_options.clone()); command.args(self.nix_options.clone());
@ -55,7 +56,7 @@ impl Host for Local {
.map(|p| p.to_string().try_into()).collect() .map(|p| p.to_string().try_into()).collect()
} }
async fn upload_keys(&mut self, keys: &HashMap<String, Key>, require_ownership: bool) -> NixResult<()> { async fn upload_keys(&mut self, keys: &HashMap<String, Key>, require_ownership: bool) -> ColmenaResult<()> {
for (name, key) in keys { for (name, key) in keys {
self.upload_key(name, key, require_ownership).await?; self.upload_key(name, key, require_ownership).await?;
} }
@ -63,9 +64,9 @@ impl Host for Local {
Ok(()) Ok(())
} }
async fn activate(&mut self, profile: &Profile, goal: Goal) -> NixResult<()> { async fn activate(&mut self, profile: &Profile, goal: Goal) -> ColmenaResult<()> {
if !goal.requires_activation() { if !goal.requires_activation() {
return Err(NixError::Unsupported); return Err(ColmenaError::Unsupported);
} }
if goal.should_switch_profile() { if goal.should_switch_profile() {
@ -91,14 +92,14 @@ impl Host for Local {
result result
} }
async fn get_main_system_profile(&mut self) -> NixResult<StorePath> { async fn get_main_system_profile(&mut self) -> ColmenaResult<StorePath> {
let paths = Command::new("sh") let paths = Command::new("sh")
.args(&["-c", &format!("readlink -e {} || readlink -e {}", SYSTEM_PROFILE, CURRENT_PROFILE)]) .args(&["-c", &format!("readlink -e {} || readlink -e {}", SYSTEM_PROFILE, CURRENT_PROFILE)])
.capture_output() .capture_output()
.await?; .await?;
let path = paths.lines().into_iter().next() let path = paths.lines().into_iter().next()
.ok_or(NixError::FailedToGetCurrentProfile)? .ok_or(ColmenaError::FailedToGetCurrentProfile)?
.to_string() .to_string()
.try_into()?; .try_into()?;
@ -112,7 +113,7 @@ impl Host for Local {
impl Local { impl Local {
/// "Uploads" a single key. /// "Uploads" a single key.
async fn upload_key(&mut self, name: &str, key: &Key, require_ownership: bool) -> NixResult<()> { async fn upload_key(&mut self, name: &str, key: &Key, require_ownership: bool) -> ColmenaResult<()> {
if let Some(job) = &self.job { if let Some(job) = &self.job {
job.message(format!("Deploying key {}", name))?; job.message(format!("Deploying key {}", name))?;
} }

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use async_trait::async_trait; use async_trait::async_trait;
use super::{StorePath, Profile, Goal, NixResult, NixError, Key}; use super::{StorePath, Profile, Goal, ColmenaResult, ColmenaError, Key};
use crate::job::JobHandle; use crate::job::JobHandle;
mod ssh; mod ssh;
@ -65,20 +65,20 @@ pub trait Host: Send + Sync + std::fmt::Debug {
/// Sends or receives the specified closure to the host /// Sends or receives the specified closure to the host
/// ///
/// The StorePath and its dependent paths will then exist on this host. /// The StorePath and its dependent paths will then exist on this host.
async fn copy_closure(&mut self, closure: &StorePath, direction: CopyDirection, options: CopyOptions) -> NixResult<()>; async fn copy_closure(&mut self, closure: &StorePath, direction: CopyDirection, options: CopyOptions) -> ColmenaResult<()>;
/// Realizes the specified derivation on the host /// Realizes the specified derivation on the host
/// ///
/// The derivation must already exist on the host. /// The derivation must already exist on the host.
/// After realization, paths in the Vec<StorePath> will then /// After realization, paths in the Vec<StorePath> will then
/// exist on the host. /// exist on the host.
async fn realize_remote(&mut self, derivation: &StorePath) -> NixResult<Vec<StorePath>>; async fn realize_remote(&mut self, derivation: &StorePath) -> ColmenaResult<Vec<StorePath>>;
/// Provides a JobHandle to use during operations. /// Provides a JobHandle to use during operations.
fn set_job(&mut self, bar: Option<JobHandle>); fn set_job(&mut self, bar: Option<JobHandle>);
/// Realizes the specified local derivation on the host then retrieves the outputs. /// Realizes the specified local derivation on the host then retrieves the outputs.
async fn realize(&mut self, derivation: &StorePath) -> NixResult<Vec<StorePath>> { async fn realize(&mut self, derivation: &StorePath) -> ColmenaResult<Vec<StorePath>> {
let options = CopyOptions::default() let options = CopyOptions::default()
.include_outputs(true); .include_outputs(true);
@ -90,7 +90,7 @@ pub trait Host: Send + Sync + std::fmt::Debug {
} }
/// Pushes and optionally activates a profile to the host. /// Pushes and optionally activates a profile to the host.
async fn deploy(&mut self, profile: &Profile, goal: Goal, copy_options: CopyOptions) -> NixResult<()> { async fn deploy(&mut self, profile: &Profile, goal: Goal, copy_options: CopyOptions) -> ColmenaResult<()> {
self.copy_closure(profile.as_store_path(), CopyDirection::ToRemote, copy_options).await?; self.copy_closure(profile.as_store_path(), CopyDirection::ToRemote, copy_options).await?;
if goal.requires_activation() { if goal.requires_activation() {
@ -106,8 +106,8 @@ pub trait Host: Send + Sync + std::fmt::Debug {
/// will not be applied if the specified user/group does not /// will not be applied if the specified user/group does not
/// exist. /// exist.
#[allow(unused_variables)] #[allow(unused_variables)]
async fn upload_keys(&mut self, keys: &HashMap<String, Key>, require_ownership: bool) -> NixResult<()> { async fn upload_keys(&mut self, keys: &HashMap<String, Key>, require_ownership: bool) -> ColmenaResult<()> {
Err(NixError::Unsupported) Err(ColmenaError::Unsupported)
} }
/// Returns the main system profile on the host. /// Returns the main system profile on the host.
@ -115,19 +115,19 @@ pub trait Host: Send + Sync + std::fmt::Debug {
/// This may _not_ be the system profile that's currently activated! /// This may _not_ be the system profile that's currently activated!
/// It will first try `/nix/var/nix/profiles/system`, falling back /// It will first try `/nix/var/nix/profiles/system`, falling back
/// to `/run/current-system` if it doesn't exist. /// to `/run/current-system` if it doesn't exist.
async fn get_main_system_profile(&mut self) -> NixResult<StorePath>; async fn get_main_system_profile(&mut self) -> ColmenaResult<StorePath>;
/// Activates a system profile on the host, if it runs NixOS. /// Activates a system profile on the host, if it runs NixOS.
/// ///
/// The profile must already exist on the host. You should probably use deploy instead. /// The profile must already exist on the host. You should probably use deploy instead.
#[allow(unused_variables)] #[allow(unused_variables)]
async fn activate(&mut self, profile: &Profile, goal: Goal) -> NixResult<()> { async fn activate(&mut self, profile: &Profile, goal: Goal) -> ColmenaResult<()> {
Err(NixError::Unsupported) Err(ColmenaError::Unsupported)
} }
/// Runs an arbitrary command on the host. /// Runs an arbitrary command on the host.
#[allow(unused_variables)] #[allow(unused_variables)]
async fn run_command(&mut self, command: &[&str]) -> NixResult<()> { async fn run_command(&mut self, command: &[&str]) -> ColmenaResult<()> {
Err(NixError::Unsupported) Err(ColmenaError::Unsupported)
} }
} }

View file

@ -6,10 +6,11 @@ use std::process::Stdio;
use async_trait::async_trait; use async_trait::async_trait;
use tokio::process::Command; use tokio::process::Command;
use super::{CopyDirection, CopyOptions, Host, key_uploader}; use crate::error::{ColmenaResult, ColmenaError};
use crate::nix::{StorePath, Profile, Goal, NixResult, NixError, Key, SYSTEM_PROFILE, CURRENT_PROFILE}; use crate::nix::{StorePath, Profile, Goal, Key, SYSTEM_PROFILE, CURRENT_PROFILE};
use crate::util::{CommandExecution, CommandExt}; use crate::util::{CommandExecution, CommandExt};
use crate::job::JobHandle; use crate::job::JobHandle;
use super::{CopyDirection, CopyOptions, Host, key_uploader};
/// A remote machine connected over SSH. /// A remote machine connected over SSH.
#[derive(Debug)] #[derive(Debug)]
@ -35,11 +36,11 @@ pub struct Ssh {
#[async_trait] #[async_trait]
impl Host for Ssh { impl Host for Ssh {
async fn copy_closure(&mut self, closure: &StorePath, direction: CopyDirection, options: CopyOptions) -> NixResult<()> { async fn copy_closure(&mut self, closure: &StorePath, direction: CopyDirection, options: CopyOptions) -> ColmenaResult<()> {
let command = self.nix_copy_closure(closure, direction, options); let command = self.nix_copy_closure(closure, direction, options);
self.run_command(command).await self.run_command(command).await
} }
async fn realize_remote(&mut self, derivation: &StorePath) -> NixResult<Vec<StorePath>> { async fn realize_remote(&mut self, derivation: &StorePath) -> ColmenaResult<Vec<StorePath>> {
let command = self.ssh(&["nix-store", "--no-gc-warning", "--realise", derivation.as_path().to_str().unwrap()]); let command = self.ssh(&["nix-store", "--no-gc-warning", "--realise", derivation.as_path().to_str().unwrap()]);
let mut execution = CommandExecution::new(command); let mut execution = CommandExecution::new(command);
@ -51,16 +52,16 @@ impl Host for Ssh {
paths.lines().map(|p| p.to_string().try_into()).collect() paths.lines().map(|p| p.to_string().try_into()).collect()
} }
async fn upload_keys(&mut self, keys: &HashMap<String, Key>, require_ownership: bool) -> NixResult<()> { async fn upload_keys(&mut self, keys: &HashMap<String, Key>, require_ownership: bool) -> ColmenaResult<()> {
for (name, key) in keys { for (name, key) in keys {
self.upload_key(name, key, require_ownership).await?; self.upload_key(name, key, require_ownership).await?;
} }
Ok(()) Ok(())
} }
async fn activate(&mut self, profile: &Profile, goal: Goal) -> NixResult<()> { async fn activate(&mut self, profile: &Profile, goal: Goal) -> ColmenaResult<()> {
if !goal.requires_activation() { if !goal.requires_activation() {
return Err(NixError::Unsupported); return Err(ColmenaError::Unsupported);
} }
if goal.should_switch_profile() { if goal.should_switch_profile() {
@ -74,11 +75,11 @@ impl Host for Ssh {
let command = self.ssh(&v); let command = self.ssh(&v);
self.run_command(command).await self.run_command(command).await
} }
async fn run_command(&mut self, command: &[&str]) -> NixResult<()> { async fn run_command(&mut self, command: &[&str]) -> ColmenaResult<()> {
let command = self.ssh(command); let command = self.ssh(command);
self.run_command(command).await self.run_command(command).await
} }
async fn get_main_system_profile(&mut self) -> NixResult<StorePath> { async fn get_main_system_profile(&mut self) -> ColmenaResult<StorePath> {
let command = format!("\"readlink -e {} || readlink -e {}\"", SYSTEM_PROFILE, CURRENT_PROFILE); let command = format!("\"readlink -e {} || readlink -e {}\"", SYSTEM_PROFILE, CURRENT_PROFILE);
let paths = self.ssh(&["sh", "-c", &command]) let paths = self.ssh(&["sh", "-c", &command])
@ -86,7 +87,7 @@ impl Host for Ssh {
.await?; .await?;
let path = paths.lines().into_iter().next() let path = paths.lines().into_iter().next()
.ok_or(NixError::FailedToGetCurrentProfile)? .ok_or(ColmenaError::FailedToGetCurrentProfile)?
.to_string() .to_string()
.try_into()?; .try_into()?;
@ -150,7 +151,7 @@ impl Ssh {
cmd cmd
} }
async fn run_command(&mut self, command: Command) -> NixResult<()> { async fn run_command(&mut self, command: Command) -> ColmenaResult<()> {
let mut execution = CommandExecution::new(command); let mut execution = CommandExecution::new(command);
execution.set_job(self.job.clone()); execution.set_job(self.job.clone());
@ -216,7 +217,7 @@ impl Ssh {
} }
/// Uploads a single key. /// Uploads a single key.
async fn upload_key(&mut self, name: &str, key: &Key, require_ownership: bool) -> NixResult<()> { async fn upload_key(&mut self, name: &str, key: &Key, require_ownership: bool) -> ColmenaResult<()> {
if let Some(job) = &self.job { if let Some(job) = &self.job {
job.message(format!("Uploading key {}", name))?; job.message(format!("Uploading key {}", name))?;
} }

View file

@ -5,7 +5,7 @@ use log::Level;
use regex::Regex; use regex::Regex;
use tokio::process::Command; use tokio::process::Command;
use super::{NixError, NixResult}; use super::{ColmenaError, ColmenaResult};
struct NixVersion { struct NixVersion {
major: usize, major: usize,
@ -89,12 +89,12 @@ impl NixCheck {
} }
} }
pub async fn require_flake_support() -> NixResult<()> { pub async fn require_flake_support() -> ColmenaResult<()> {
let check = Self::detect().await; let check = Self::detect().await;
if !check.flakes_supported() { if !check.flakes_supported() {
check.print_flakes_info(true); check.print_flakes_info(true);
Err(NixError::NoFlakesSupport) Err(ColmenaError::NoFlakesSupport)
} else { } else {
Ok(()) Ok(())
} }

View file

@ -1,15 +1,14 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::Hash; use std::hash::Hash;
use std::ops::Deref; use std::ops::Deref;
use std::os::unix::process::ExitStatusExt;
use std::path::Path; use std::path::Path;
use std::process::ExitStatus;
use serde::de; use serde::de;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use snafu::Snafu;
use users::get_current_username; use users::get_current_username;
use validator::{Validate, ValidationErrors, ValidationError as ValidationErrorType}; use validator::{Validate, ValidationError as ValidationErrorType};
use crate::error::{ColmenaResult, ColmenaError};
pub mod host; pub mod host;
pub use host::{Host, CopyDirection, CopyOptions}; pub use host::{Host, CopyDirection, CopyOptions};
@ -45,100 +44,6 @@ pub const SYSTEM_PROFILE: &str = "/nix/var/nix/profiles/system";
/// Path to the system profile that's currently active. /// Path to the system profile that's currently active.
pub const CURRENT_PROFILE: &str = "/run/current-system"; pub const CURRENT_PROFILE: &str = "/run/current-system";
pub type NixResult<T> = Result<T, NixError>;
#[non_exhaustive]
#[derive(Debug, Snafu)]
pub enum NixError {
#[snafu(display("I/O Error: {}", error))]
IoError { error: std::io::Error },
#[snafu(display("Nix returned invalid response: {}", output))]
BadOutput { output: String },
#[snafu(display("Nix exited with error code: {}", exit_code))]
NixFailure { exit_code: i32 },
#[snafu(display("Nix was killed by signal {}", signal))]
NixKilled { signal: i32 },
#[snafu(display("This operation is not supported"))]
Unsupported,
#[snafu(display("Invalid Nix store path"))]
InvalidStorePath,
#[snafu(display("Validation error"))]
ValidationError { errors: ValidationErrors },
#[snafu(display("Failed to upload keys: {}", error))]
KeyError { error: key::KeyError },
#[snafu(display("Store path {:?} is not a derivation", store_path))]
NotADerivation { store_path: StorePath },
#[snafu(display("Invalid NixOS system profile"))]
InvalidProfile,
#[snafu(display("Unknown active profile: {:?}", store_path))]
ActiveProfileUnknown { store_path: StorePath },
#[snafu(display("Could not determine current profile"))]
FailedToGetCurrentProfile,
#[snafu(display("Current Nix version does not support Flakes"))]
NoFlakesSupport,
#[snafu(display("Don't know how to connect to the node"))]
NoTargetHost,
#[snafu(display("Node name cannot be empty"))]
EmptyNodeName,
#[snafu(display("Filter rule cannot be empty"))]
EmptyFilterRule,
#[snafu(display("Deployment already executed"))]
DeploymentAlreadyExecuted,
#[snafu(display("Unknown error: {}", message))]
Unknown { message: String },
}
impl From<std::io::Error> for NixError {
fn from(error: std::io::Error) -> Self {
Self::IoError { error }
}
}
impl From<key::KeyError> for NixError {
fn from(error: key::KeyError) -> Self {
Self::KeyError { error }
}
}
impl From<ValidationErrors> for NixError {
fn from(errors: ValidationErrors) -> Self {
Self::ValidationError { errors }
}
}
impl From<ExitStatus> for NixError {
fn from(status: ExitStatus) -> Self {
match status.code() {
Some(exit_code) => Self::NixFailure { exit_code },
None => Self::NixKilled { signal: status.signal().unwrap() },
}
}
}
impl NixError {
pub fn unknown(error: Box<dyn std::error::Error>) -> Self {
let message = error.to_string();
Self::Unknown { message }
}
}
/// A node's attribute name. /// A node's attribute name.
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
#[serde(transparent)] #[serde(transparent)]
@ -183,7 +88,7 @@ impl NodeName {
} }
/// Creates a NodeName from a String. /// Creates a NodeName from a String.
pub fn new(name: String) -> NixResult<Self> { pub fn new(name: String) -> ColmenaResult<Self> {
let validated = Self::validate(name)?; let validated = Self::validate(name)?;
Ok(Self(validated)) Ok(Self(validated))
} }
@ -199,10 +104,10 @@ impl NodeName {
}) })
} }
fn validate(s: String) -> NixResult<String> { fn validate(s: String) -> ColmenaResult<String> {
// FIXME: Elaborate // FIXME: Elaborate
if s.is_empty() { if s.is_empty() {
return Err(NixError::EmptyNodeName); return Err(ColmenaError::EmptyNodeName);
} }
Ok(s) Ok(s)

View file

@ -6,7 +6,7 @@ use std::iter::{Iterator, FromIterator};
use glob::Pattern as GlobPattern; use glob::Pattern as GlobPattern;
use super::{NixError, NixResult, NodeName, NodeConfig}; use super::{ColmenaError, ColmenaResult, NodeName, NodeConfig};
/// A node filter containing a list of rules. /// A node filter containing a list of rules.
pub struct NodeFilter { pub struct NodeFilter {
@ -27,7 +27,7 @@ enum Rule {
impl NodeFilter { impl NodeFilter {
/// Creates a new filter using an expression passed using `--on`. /// Creates a new filter using an expression passed using `--on`.
pub fn new<S: AsRef<str>>(filter: S) -> NixResult<Self> { pub fn new<S: AsRef<str>>(filter: S) -> ColmenaResult<Self> {
let filter = filter.as_ref(); let filter = filter.as_ref();
let trimmed = filter.trim(); let trimmed = filter.trim();
@ -43,7 +43,7 @@ impl NodeFilter {
let pattern = pattern.trim(); let pattern = pattern.trim();
if pattern.is_empty() { if pattern.is_empty() {
return Err(NixError::EmptyFilterRule); return Err(ColmenaError::EmptyFilterRule);
} }
if let Some(tag_pattern) = pattern.strip_prefix('@') { if let Some(tag_pattern) = pattern.strip_prefix('@') {
@ -51,7 +51,7 @@ impl NodeFilter {
} else { } else {
Ok(Rule::MatchName(GlobPattern::new(pattern).unwrap())) Ok(Rule::MatchName(GlobPattern::new(pattern).unwrap()))
} }
}).collect::<Vec<NixResult<Rule>>>(); }).collect::<Vec<ColmenaResult<Rule>>>();
let rules = Result::from_iter(rules)?; let rules = Result::from_iter(rules)?;
@ -100,8 +100,8 @@ impl NodeFilter {
} }
/// Runs the filter against a set of node names and returns the matched ones. /// Runs the filter against a set of node names and returns the matched ones.
pub fn filter_node_names(&self, nodes: &[NodeName]) -> NixResult<HashSet<NodeName>> { pub fn filter_node_names(&self, nodes: &[NodeName]) -> ColmenaResult<HashSet<NodeName>> {
nodes.iter().filter_map(|name| -> Option<NixResult<NodeName>> { nodes.iter().filter_map(|name| -> Option<ColmenaResult<NodeName>> {
for rule in self.rules.iter() { for rule in self.rules.iter() {
match rule { match rule {
Rule::MatchName(pat) => { Rule::MatchName(pat) => {
@ -110,7 +110,7 @@ impl NodeFilter {
} }
} }
_ => { _ => {
return Some(Err(NixError::Unknown { return Some(Err(ColmenaError::Unknown {
message: format!("Not enough information to run rule {:?} - We only have node names", rule), message: format!("Not enough information to run rule {:?} - We only have node names", rule),
})); }));
} }

View file

@ -6,8 +6,8 @@ use tokio::process::Command;
use super::{ use super::{
Goal, Goal,
NixResult, ColmenaResult,
NixError, ColmenaError,
StorePath, StorePath,
StoreDerivation, StoreDerivation,
BuildResult, BuildResult,
@ -20,16 +20,16 @@ pub type ProfileDerivation = StoreDerivation<Profile>;
pub struct Profile(StorePath); pub struct Profile(StorePath);
impl Profile { impl Profile {
pub fn from_store_path(path: StorePath) -> NixResult<Self> { pub fn from_store_path(path: StorePath) -> ColmenaResult<Self> {
if if
!path.is_dir() || !path.is_dir() ||
!path.join("bin/switch-to-configuration").exists() !path.join("bin/switch-to-configuration").exists()
{ {
return Err(NixError::InvalidProfile); return Err(ColmenaError::InvalidProfile);
} }
if path.to_str().is_none() { if path.to_str().is_none() {
Err(NixError::InvalidProfile) Err(ColmenaError::InvalidProfile)
} else { } else {
Ok(Self(path)) Ok(Self(path))
} }
@ -63,7 +63,7 @@ impl Profile {
} }
/// Create a GC root for this profile. /// Create a GC root for this profile.
pub async fn create_gc_root(&self, path: &Path) -> NixResult<()> { pub async fn create_gc_root(&self, path: &Path) -> ColmenaResult<()> {
let mut command = Command::new("nix-store"); let mut command = Command::new("nix-store");
command.args(&["--no-build-output", "--indirect", "--add-root", path.to_str().unwrap()]); command.args(&["--no-build-output", "--indirect", "--add-root", path.to_str().unwrap()]);
command.args(&["--realise", self.as_path().to_str().unwrap()]); command.args(&["--realise", self.as_path().to_str().unwrap()]);
@ -83,19 +83,19 @@ impl Profile {
} }
impl TryFrom<BuildResult<Profile>> for Profile { impl TryFrom<BuildResult<Profile>> for Profile {
type Error = NixError; type Error = ColmenaError;
fn try_from(result: BuildResult<Self>) -> NixResult<Self> { fn try_from(result: BuildResult<Self>) -> ColmenaResult<Self> {
let paths = result.paths(); let paths = result.paths();
if paths.is_empty() { if paths.is_empty() {
return Err(NixError::BadOutput { return Err(ColmenaError::BadOutput {
output: String::from("There is no store path"), output: String::from("There is no store path"),
}); });
} }
if paths.len() > 1 { if paths.len() > 1 {
return Err(NixError::BadOutput { return Err(ColmenaError::BadOutput {
output: String::from("Build resulted in more than 1 store path"), output: String::from("Build resulted in more than 1 store path"),
}); });
} }

View file

@ -7,8 +7,9 @@ use std::fmt;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use tokio::process::Command; use tokio::process::Command;
use crate::error::{ColmenaError, ColmenaResult};
use crate::util::CommandExt; use crate::util::CommandExt;
use super::{Host, NixResult, NixError}; use super::Host;
/// A Nix store path. /// A Nix store path.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -43,7 +44,7 @@ impl StorePath {
} }
/// Returns the immediate dependencies of the store path. /// Returns the immediate dependencies of the store path.
pub async fn references(&self) -> NixResult<Vec<StorePath>> { pub async fn references(&self) -> ColmenaResult<Vec<StorePath>> {
let references = Command::new("nix-store") let references = Command::new("nix-store")
.args(&["--query", "--references"]) .args(&["--query", "--references"])
.arg(&self.0) .arg(&self.0)
@ -55,11 +56,11 @@ impl StorePath {
} }
/// Converts the store path into a store derivation. /// Converts the store path into a store derivation.
pub fn into_derivation<T: TryFrom<BuildResult<T>>>(self) -> NixResult<StoreDerivation<T>> { pub fn into_derivation<T: TryFrom<BuildResult<T>>>(self) -> ColmenaResult<StoreDerivation<T>> {
if self.is_derivation() { if self.is_derivation() {
Ok(StoreDerivation::<T>::from_store_path_unchecked(self)) Ok(StoreDerivation::<T>::from_store_path_unchecked(self))
} else { } else {
Err(NixError::NotADerivation { store_path: self }) Err(ColmenaError::NotADerivation { store_path: self })
} }
} }
} }
@ -73,13 +74,13 @@ impl Deref for StorePath {
} }
impl TryFrom<String> for StorePath { impl TryFrom<String> for StorePath {
type Error = NixError; type Error = ColmenaError;
fn try_from(s: String) -> NixResult<Self> { fn try_from(s: String) -> ColmenaResult<Self> {
if s.starts_with("/nix/store/") { if s.starts_with("/nix/store/") {
Ok(Self(s.into())) Ok(Self(s.into()))
} else { } else {
Err(NixError::InvalidStorePath) Err(ColmenaError::InvalidStorePath)
} }
} }
} }
@ -113,9 +114,9 @@ impl<T: TryFrom<BuildResult<T>>> StoreDerivation<T> {
} }
} }
impl<T: TryFrom<BuildResult<T>, Error=NixError>> StoreDerivation<T> { impl<T: TryFrom<BuildResult<T>, Error=ColmenaError>> StoreDerivation<T> {
/// Builds the store derivation on a host, resulting in a T. /// Builds the store derivation on a host, resulting in a T.
pub async fn realize(&self, host: &mut Box<dyn Host>) -> NixResult<T> { pub async fn realize(&self, host: &mut Box<dyn Host>) -> ColmenaResult<T> {
let paths: Vec<StorePath> = host.realize(&self.path).await?; let paths: Vec<StorePath> = host.realize(&self.path).await?;
let result = BuildResult { let result = BuildResult {
@ -126,7 +127,7 @@ impl<T: TryFrom<BuildResult<T>, Error=NixError>> StoreDerivation<T> {
} }
/// Builds the store derivation on a host without copying the results back. /// Builds the store derivation on a host without copying the results back.
pub async fn realize_remote(&self, host: &mut Box<dyn Host>) -> NixResult<T> { pub async fn realize_remote(&self, host: &mut Box<dyn Host>) -> ColmenaResult<T> {
let paths: Vec<StorePath> = host.realize_remote(&self.path).await?; let paths: Vec<StorePath> = host.realize_remote(&self.path).await?;
let result = BuildResult { let result = BuildResult {
@ -143,7 +144,7 @@ impl<T: TryFrom<BuildResult<T>>> fmt::Display for StoreDerivation<T> {
} }
} }
impl<T: TryFrom<BuildResult<T>, Error=NixError>> BuildResult<T> { impl<T: TryFrom<BuildResult<T>, Error=ColmenaError>> BuildResult<T> {
pub fn paths(&self) -> &[StorePath] { pub fn paths(&self) -> &[StorePath] {
self.results.as_slice() self.results.as_slice()
} }

View file

@ -13,8 +13,8 @@ use tokio::sync::mpsc::{self,
UnboundedSender as TokioSender, UnboundedSender as TokioSender,
}; };
use crate::error::ColmenaResult;
use crate::job::JobId; use crate::job::JobId;
use crate::nix::NixResult;
pub use plain::PlainOutput; pub use plain::PlainOutput;
pub use spinner::SpinnerOutput; pub use spinner::SpinnerOutput;
@ -33,7 +33,7 @@ pub enum SimpleProgressOutput {
#[async_trait] #[async_trait]
pub trait ProgressOutput : Sized { pub trait ProgressOutput : Sized {
/// Runs until a Message::Complete is received. /// Runs until a Message::Complete is received.
async fn run_until_completion(self) -> NixResult<Self>; async fn run_until_completion(self) -> ColmenaResult<Self>;
/// Returns a sender. /// Returns a sender.
/// ///
@ -109,7 +109,7 @@ impl SimpleProgressOutput {
} }
} }
pub async fn run_until_completion(self) -> NixResult<Self> { pub async fn run_until_completion(self) -> ColmenaResult<Self> {
match self { match self {
Self::Plain(o) => { Self::Plain(o) => {
o.run_until_completion().await o.run_until_completion().await

View file

@ -3,7 +3,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use console::Style as ConsoleStyle; use console::Style as ConsoleStyle;
use crate::nix::NixResult; use crate::error::ColmenaResult;
use super::{ use super::{
DEFAULT_LABEL_WIDTH, DEFAULT_LABEL_WIDTH,
ProgressOutput, ProgressOutput,
@ -81,7 +81,7 @@ impl PlainOutput {
#[async_trait] #[async_trait]
impl ProgressOutput for PlainOutput { impl ProgressOutput for PlainOutput {
async fn run_until_completion(mut self) -> NixResult<Self> { async fn run_until_completion(mut self) -> ColmenaResult<Self> {
loop { loop {
let message = self.receiver.recv().await; let message = self.receiver.recv().await;

View file

@ -6,8 +6,8 @@ use std::time::Instant;
use async_trait::async_trait; use async_trait::async_trait;
use indicatif::{MultiProgress, ProgressStyle, ProgressBar}; use indicatif::{MultiProgress, ProgressStyle, ProgressBar};
use crate::error::ColmenaResult;
use crate::job::JobId; use crate::job::JobId;
use crate::nix::NixResult;
use super::{ use super::{
DEFAULT_LABEL_WIDTH, DEFAULT_LABEL_WIDTH,
ProgressOutput, ProgressOutput,
@ -171,7 +171,7 @@ impl SpinnerOutput {
#[async_trait] #[async_trait]
impl ProgressOutput for SpinnerOutput { impl ProgressOutput for SpinnerOutput {
async fn run_until_completion(mut self) -> NixResult<Self> { async fn run_until_completion(mut self) -> ColmenaResult<Self> {
let meta_bar = self.multi.add(self.meta_bar.clone()); let meta_bar = self.multi.add(self.meta_bar.clone());
meta_bar.enable_steady_tick(100); meta_bar.enable_steady_tick(100);

View file

@ -7,12 +7,12 @@ use std::future::Future;
use clap::ArgMatches; use clap::ArgMatches;
use crate::nix::NixError; use crate::error::ColmenaError;
/// Runs a closure and tries to troubleshoot if it returns an error. /// Runs a closure and tries to troubleshoot if it returns an error.
pub async fn run_wrapped<'a, F, U, T>(global_args: &'a ArgMatches, local_args: &'a ArgMatches, f: U) -> T pub async fn run_wrapped<'a, F, U, T>(global_args: &'a ArgMatches, local_args: &'a ArgMatches, f: U) -> T
where U: FnOnce(&'a ArgMatches, &'a ArgMatches) -> F, where U: FnOnce(&'a ArgMatches, &'a ArgMatches) -> F,
F: Future<Output = Result<T, NixError>>, F: Future<Output = Result<T, ColmenaError>>,
{ {
match f(global_args, local_args).await { match f(global_args, local_args).await {
Ok(r) => r, Ok(r) => r,
@ -30,8 +30,8 @@ pub async fn run_wrapped<'a, F, U, T>(global_args: &'a ArgMatches, local_args: &
} }
} }
fn troubleshoot(global_args: &ArgMatches, _local_args: &ArgMatches, error: &NixError) -> Result<(), NixError> { fn troubleshoot(global_args: &ArgMatches, _local_args: &ArgMatches, error: &ColmenaError) -> Result<(), ColmenaError> {
if let NixError::NoFlakesSupport = error { if let ColmenaError::NoFlakesSupport = error {
// People following the tutorial might put hive.nix directly // People following the tutorial might put hive.nix directly
// in their Colmena checkout, and encounter NoFlakesSupport // in their Colmena checkout, and encounter NoFlakesSupport
// because Colmena always prefers flake.nix when it exists. // because Colmena always prefers flake.nix when it exists.

View file

@ -9,7 +9,8 @@ use serde::de::DeserializeOwned;
use tokio::io::{AsyncRead, AsyncBufReadExt, BufReader}; use tokio::io::{AsyncRead, AsyncBufReadExt, BufReader};
use tokio::process::Command; use tokio::process::Command;
use super::nix::{Flake, Hive, HivePath, NixResult, NixError, StorePath}; use super::error::{ColmenaResult, ColmenaError};
use super::nix::{Flake, Hive, HivePath, StorePath};
use super::nix::deployment::TargetNodeMap; use super::nix::deployment::TargetNodeMap;
use super::job::JobHandle; use super::job::JobHandle;
@ -26,16 +27,16 @@ pub struct CommandExecution {
#[async_trait] #[async_trait]
pub trait CommandExt { pub trait CommandExt {
/// Runs the command with stdout and stderr passed through to the user. /// Runs the command with stdout and stderr passed through to the user.
async fn passthrough(&mut self) -> NixResult<()>; async fn passthrough(&mut self) -> ColmenaResult<()>;
/// Runs the command, capturing the output as a String. /// Runs the command, capturing the output as a String.
async fn capture_output(&mut self) -> NixResult<String>; async fn capture_output(&mut self) -> ColmenaResult<String>;
/// Runs the command, capturing deserialized output from JSON. /// Runs the command, capturing deserialized output from JSON.
async fn capture_json<T>(&mut self) -> NixResult<T> where T: DeserializeOwned; async fn capture_json<T>(&mut self) -> ColmenaResult<T> where T: DeserializeOwned;
/// Runs the command, capturing a single store path. /// Runs the command, capturing a single store path.
async fn capture_store_path(&mut self) -> NixResult<StorePath>; async fn capture_store_path(&mut self) -> ColmenaResult<StorePath>;
} }
impl CommandExecution { impl CommandExecution {
@ -65,7 +66,7 @@ impl CommandExecution {
} }
/// Runs the command. /// Runs the command.
pub async fn run(&mut self) -> NixResult<()> { pub async fn run(&mut self) -> ColmenaResult<()> {
self.command.stdin(Stdio::null()); self.command.stdin(Stdio::null());
self.command.stdout(Stdio::piped()); self.command.stdout(Stdio::piped());
self.command.stderr(Stdio::piped()); self.command.stderr(Stdio::piped());
@ -103,7 +104,7 @@ impl CommandExecution {
#[async_trait] #[async_trait]
impl CommandExt for Command { impl CommandExt for Command {
/// Runs the command with stdout and stderr passed through to the user. /// Runs the command with stdout and stderr passed through to the user.
async fn passthrough(&mut self) -> NixResult<()> { async fn passthrough(&mut self) -> ColmenaResult<()> {
let exit = self let exit = self
.spawn()? .spawn()?
.wait() .wait()
@ -117,7 +118,7 @@ impl CommandExt for Command {
} }
/// Captures output as a String. /// Captures output as a String.
async fn capture_output(&mut self) -> NixResult<String> { async fn capture_output(&mut self) -> ColmenaResult<String> {
// We want the user to see the raw errors // We want the user to see the raw errors
let output = self let output = self
.stdout(Stdio::piped()) .stdout(Stdio::piped())
@ -135,15 +136,15 @@ impl CommandExt for Command {
} }
/// Captures deserialized output from JSON. /// Captures deserialized output from JSON.
async fn capture_json<T>(&mut self) -> NixResult<T> where T: DeserializeOwned { async fn capture_json<T>(&mut self) -> ColmenaResult<T> where T: DeserializeOwned {
let output = self.capture_output().await?; let output = self.capture_output().await?;
serde_json::from_str(&output).map_err(|_| NixError::BadOutput { serde_json::from_str(&output).map_err(|_| ColmenaError::BadOutput {
output: output.clone() output: output.clone()
}) })
} }
/// Captures a single store path. /// Captures a single store path.
async fn capture_store_path(&mut self) -> NixResult<StorePath> { async fn capture_store_path(&mut self) -> ColmenaResult<StorePath> {
let output = self.capture_output().await?; let output = self.capture_output().await?;
let path = output.trim_end().to_owned(); let path = output.trim_end().to_owned();
StorePath::try_from(path) StorePath::try_from(path)
@ -152,12 +153,12 @@ impl CommandExt for Command {
#[async_trait] #[async_trait]
impl CommandExt for CommandExecution { impl CommandExt for CommandExecution {
async fn passthrough(&mut self) -> NixResult<()> { async fn passthrough(&mut self) -> ColmenaResult<()> {
self.run().await self.run().await
} }
/// Captures output as a String. /// Captures output as a String.
async fn capture_output(&mut self) -> NixResult<String> { async fn capture_output(&mut self) -> ColmenaResult<String> {
self.run().await?; self.run().await?;
let (stdout, _) = self.get_logs(); let (stdout, _) = self.get_logs();
@ -165,22 +166,22 @@ impl CommandExt for CommandExecution {
} }
/// Captures deserialized output from JSON. /// Captures deserialized output from JSON.
async fn capture_json<T>(&mut self) -> NixResult<T> where T: DeserializeOwned { async fn capture_json<T>(&mut self) -> ColmenaResult<T> where T: DeserializeOwned {
let output = self.capture_output().await?; let output = self.capture_output().await?;
serde_json::from_str(&output).map_err(|_| NixError::BadOutput { serde_json::from_str(&output).map_err(|_| ColmenaError::BadOutput {
output: output.clone() output: output.clone()
}) })
} }
/// Captures a single store path. /// Captures a single store path.
async fn capture_store_path(&mut self) -> NixResult<StorePath> { async fn capture_store_path(&mut self) -> ColmenaResult<StorePath> {
let output = self.capture_output().await?; let output = self.capture_output().await?;
let path = output.trim_end().to_owned(); let path = output.trim_end().to_owned();
StorePath::try_from(path) StorePath::try_from(path)
} }
} }
pub async fn hive_from_args(args: &ArgMatches) -> NixResult<Hive> { pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult<Hive> {
let path = match args.occurrences_of("config") { let path = match args.occurrences_of("config") {
0 => { 0 => {
// traverse upwards until we find hive.nix // traverse upwards until we find hive.nix
@ -281,7 +282,7 @@ fn canonicalize_cli_path(path: &str) -> PathBuf {
} }
} }
pub async fn capture_stream<R>(mut stream: BufReader<R>, job: Option<JobHandle>, stderr: bool) -> NixResult<String> pub async fn capture_stream<R>(mut stream: BufReader<R>, job: Option<JobHandle>, stderr: bool) -> ColmenaResult<String>
where R: AsyncRead + Unpin where R: AsyncRead + Unpin
{ {
let mut log = String::new(); let mut log = String::new();