colmena/src/nix/mod.rs

342 lines
9 KiB
Rust
Raw Normal View History

use std::collections::HashMap;
use std::convert::TryFrom;
2021-11-20 23:34:52 -08:00
use std::hash::Hash;
use std::ops::Deref;
2021-04-28 15:09:40 -07:00
use std::os::unix::process::ExitStatusExt;
use std::path::Path;
2021-04-28 15:09:40 -07:00
use std::process::{ExitStatus, Stdio};
2020-12-15 20:21:26 -08:00
use async_trait::async_trait;
2021-11-20 23:34:52 -08:00
use serde::de::{self, DeserializeOwned};
use serde::{Deserialize, Deserializer, Serialize};
2020-12-15 20:21:26 -08:00
use snafu::Snafu;
use tokio::process::Command;
use users::get_current_username;
use validator::{Validate, ValidationErrors, ValidationError as ValidationErrorType};
use crate::util::CommandExecution;
2020-12-18 01:27:44 -08:00
pub mod host;
pub use host::{Host, CopyDirection, CopyOptions};
use host::Ssh;
2020-12-15 20:21:26 -08:00
pub mod hive;
pub use hive::{Hive, HivePath};
pub mod store;
pub use store::{StorePath, StoreDerivation};
pub mod key;
pub use key::Key;
pub mod profile;
pub use profile::{Profile, ProfileMap};
pub mod deployment;
pub use deployment::Goal;
2020-12-15 20:21:26 -08:00
2021-06-29 01:02:43 -07:00
pub mod info;
pub use info::NixCheck;
pub mod flake;
pub use flake::Flake;
2021-11-20 23:34:52 -08:00
pub mod node_filter;
pub use node_filter::NodeFilter;
2021-02-11 11:55:45 -08:00
#[cfg(test)]
mod tests;
2021-11-23 13:33:23 -08:00
pub const SYSTEM_PROFILE: &str = "/nix/var/nix/profiles/system";
2020-12-19 16:28:34 -08:00
2020-12-18 01:27:44 -08:00
pub type NixResult<T> = Result<T, NixError>;
#[non_exhaustive]
2020-12-18 01:27:44 -08:00
#[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 },
2021-04-28 15:09:40 -07:00
#[snafu(display("Nix was killed by signal {}", signal))]
NixKilled { signal: i32 },
2020-12-18 01:27:44 -08:00
#[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("Invalid NixOS system profile"))]
InvalidProfile,
#[snafu(display("Unknown active profile: {}", store_path))]
ActiveProfileUnknown { store_path: String },
2021-06-29 01:02:43 -07:00
#[snafu(display("Current Nix version does not support Flakes"))]
NoFlakesSupport,
#[snafu(display("Don't know how to connect to the node"))]
NoTargetHost,
2021-11-20 23:34:52 -08:00
#[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))]
2020-12-18 01:27:44 -08:00
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 }
}
}
2021-04-28 15:09:40 -07:00
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 }
}
}
2021-11-20 23:34:52 -08:00
/// A node's attribute name.
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
#[serde(transparent)]
pub struct NodeName(
#[serde(deserialize_with = "NodeName::deserialize")]
String
);
#[derive(Debug, Clone, Validate, Deserialize)]
pub struct NodeConfig {
2020-12-15 20:21:26 -08:00
#[serde(rename = "targetHost")]
target_host: Option<String>,
2020-12-15 20:21:26 -08:00
#[serde(rename = "targetUser")]
target_user: Option<String>,
#[serde(rename = "targetPort")]
target_port: Option<u16>,
#[serde(rename = "allowLocalDeployment")]
allow_local_deployment: bool,
2020-12-15 20:21:26 -08:00
tags: Vec<String>,
#[serde(rename = "replaceUnknownProfiles")]
replace_unknown_profiles: bool,
#[serde(rename = "privilegeEscalationCommand")]
privilege_escalation_command: Vec<String>,
#[validate(custom = "validate_keys")]
keys: HashMap<String, Key>,
2020-12-15 20:21:26 -08:00
}
#[async_trait]
trait NixCommand {
async fn passthrough(&mut self) -> NixResult<()>;
async fn capture_output(&mut self) -> NixResult<String>;
async fn capture_json<T>(&mut self) -> NixResult<T> where T: DeserializeOwned;
async fn capture_store_path(&mut self) -> NixResult<StorePath>;
}
2021-11-20 23:34:52 -08:00
impl NodeName {
/// Returns the string.
pub fn as_str(&self) -> &str {
&self.0
}
/// Creates a NodeName from a String.
pub fn new(name: String) -> NixResult<Self> {
let validated = Self::validate(name)?;
Ok(Self(validated))
}
/// Deserializes a potentially-invalid node name.
fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
where D: Deserializer<'de>
{
use de::Error;
String::deserialize(deserializer)
.and_then(|s| {
Self::validate(s).map_err(|e| Error::custom(e.to_string()))
})
}
fn validate(s: String) -> NixResult<String> {
// FIXME: Elaborate
2021-11-23 13:33:23 -08:00
if s.is_empty() {
2021-11-20 23:34:52 -08:00
return Err(NixError::EmptyNodeName);
}
Ok(s)
}
}
impl Deref for NodeName {
type Target = str;
fn deref(&self) -> &str {
self.0.as_str()
}
}
impl NodeConfig {
2020-12-18 01:27:44 -08:00
pub fn tags(&self) -> &[String] { &self.tags }
pub fn allows_local_deployment(&self) -> bool { self.allow_local_deployment }
pub fn to_ssh_host(&self) -> Option<Ssh> {
self.target_host.as_ref().map(|target_host| {
let username =
match &self.target_user {
Some(uname) => uname.clone(),
2021-10-23 20:49:14 -07:00
None => get_current_username().unwrap().into_string().unwrap(),
};
2021-11-23 13:33:23 -08:00
let mut host = Ssh::new(username, target_host.clone());
host.set_privilege_escalation_command(self.privilege_escalation_command.clone());
if let Some(target_port) = self.target_port {
host.set_port(target_port);
}
host
})
2020-12-18 01:27:44 -08:00
}
2020-12-15 20:21:26 -08:00
}
#[async_trait]
impl NixCommand for Command {
/// Runs the command with stdout and stderr passed through to the user.
async fn passthrough(&mut self) -> NixResult<()> {
let exit = self
2020-12-18 01:27:44 -08:00
.spawn()?
2020-12-15 20:21:26 -08:00
.wait()
2020-12-18 01:27:44 -08:00
.await?;
2020-12-15 20:21:26 -08:00
if exit.success() {
Ok(())
} else {
2021-04-28 15:09:40 -07:00
Err(exit.into())
2020-12-15 20:21:26 -08:00
}
}
/// Captures output as a String.
async fn capture_output(&mut self) -> NixResult<String> {
// We want the user to see the raw errors
let output = self
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
2020-12-18 01:27:44 -08:00
.spawn()?
2020-12-15 20:21:26 -08:00
.wait_with_output()
2020-12-18 01:27:44 -08:00
.await?;
2020-12-15 20:21:26 -08:00
if output.status.success() {
// FIXME: unwrap
Ok(String::from_utf8(output.stdout).unwrap())
} else {
2021-04-28 15:09:40 -07:00
Err(output.status.into())
2020-12-15 20:21:26 -08:00
}
}
/// Captures deserialized output from JSON.
async fn capture_json<T>(&mut self) -> NixResult<T> where T: DeserializeOwned {
let output = self.capture_output().await?;
serde_json::from_str(&output).map_err(|_| NixError::BadOutput {
output: output.clone()
})
}
/// Captures a single store path.
async fn capture_store_path(&mut self) -> NixResult<StorePath> {
let output = self.capture_output().await?;
let path = output.trim_end().to_owned();
StorePath::try_from(path)
2020-12-15 20:21:26 -08:00
}
}
#[async_trait]
impl NixCommand for CommandExecution {
async fn passthrough(&mut self) -> NixResult<()> {
self.run().await
2020-12-18 01:27:44 -08:00
}
/// Captures output as a String.
async fn capture_output(&mut self) -> NixResult<String> {
self.run().await?;
let (stdout, _) = self.get_logs();
2020-12-18 01:27:44 -08:00
Ok(stdout.unwrap().to_owned())
2020-12-15 20:21:26 -08:00
}
/// Captures deserialized output from JSON.
async fn capture_json<T>(&mut self) -> NixResult<T> where T: DeserializeOwned {
let output = self.capture_output().await?;
serde_json::from_str(&output).map_err(|_| NixError::BadOutput {
output: output.clone()
2020-12-15 20:21:26 -08:00
})
}
/// Captures a single store path.
async fn capture_store_path(&mut self) -> NixResult<StorePath> {
let output = self.capture_output().await?;
let path = output.trim_end().to_owned();
StorePath::try_from(path)
2020-12-15 20:21:26 -08:00
}
}
fn validate_keys(keys: &HashMap<String, Key>) -> Result<(), ValidationErrorType> {
// Bad secret names:
// - /etc/passwd
// - ../../../../../etc/passwd
for name in keys.keys() {
let path = Path::new(name);
if path.has_root() {
return Err(ValidationErrorType::new("Secret key name cannot be absolute"));
}
2021-11-23 13:33:23 -08:00
if path.components().count() != 1 {
return Err(ValidationErrorType::new("Secret key name cannot contain path separators"));
}
}
Ok(())
}