colmena/src/nix/mod.rs
ThinkChaos 90c88ddd17 refactor: rename NixOptions to NixFlags
This helps differentiate in the code Nix's `--option` and other CLI
flags (previously referred to as options).
2022-11-13 16:53:04 -07:00

268 lines
6.5 KiB
Rust

use std::collections::HashMap;
use std::hash::Hash;
use std::ops::Deref;
use std::path::Path;
use serde::de;
use serde::{Deserialize, Deserializer, Serialize};
use validator::{Validate, ValidationError as ValidationErrorType};
use crate::error::{ColmenaError, ColmenaResult};
pub mod host;
use host::Ssh;
pub use host::{CopyDirection, CopyOptions, Host, RebootOptions};
pub mod hive;
pub use hive::{Hive, HivePath};
pub mod store;
pub use store::{BuildResult, StoreDerivation, StorePath};
pub mod key;
pub use key::Key;
pub mod profile;
pub use profile::{Profile, ProfileDerivation};
pub mod deployment;
pub use deployment::Goal;
pub mod info;
pub use info::NixCheck;
pub mod flake;
pub use flake::Flake;
pub mod node_filter;
pub use node_filter::NodeFilter;
pub mod evaluator;
pub mod expression;
pub use expression::{NixExpression, SerializedNixExpression};
/// Path to the main system profile.
pub const SYSTEM_PROFILE: &str = "/nix/var/nix/profiles/system";
/// Path to the system profile that's currently active.
pub const CURRENT_PROFILE: &str = "/run/current-system";
/// 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 {
#[serde(rename = "targetHost")]
target_host: Option<String>,
#[serde(rename = "targetUser")]
target_user: Option<String>,
#[serde(rename = "targetPort")]
target_port: Option<u16>,
#[serde(rename = "allowLocalDeployment")]
allow_local_deployment: bool,
#[serde(rename = "buildOnTarget")]
build_on_target: bool,
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>,
}
#[derive(Debug, Clone, Validate, Deserialize)]
pub struct MetaConfig {
#[serde(rename = "allowApplyAll")]
pub allow_apply_all: bool,
#[serde(rename = "machinesFile")]
pub machines_file: Option<String>,
}
/// Nix CLI flags.
#[derive(Debug, Clone, Default)]
pub struct NixFlags {
/// Whether to pass --show-trace.
show_trace: bool,
/// Whether to pass --pure-eval.
pure_eval: bool,
/// Whether to pass --impure.
impure: bool,
/// Designated builders.
///
/// See <https://nixos.org/manual/nix/stable/advanced-topics/distributed-builds.html>.
///
/// Valid examples:
/// - `@/path/to/machines`
/// - `builder@host.tld riscv64-linux /home/nix/.ssh/keys/builder.key 8 1 kvm`
builders: Option<String>,
/// Options to pass as --option name value.
options: HashMap<String, String>,
}
impl NodeName {
/// Returns the string.
pub fn as_str(&self) -> &str {
&self.0
}
/// Creates a NodeName from a String.
pub fn new(name: String) -> ColmenaResult<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) -> ColmenaResult<String> {
// FIXME: Elaborate
if s.is_empty() {
return Err(ColmenaError::EmptyNodeName);
}
Ok(s)
}
}
impl Deref for NodeName {
type Target = str;
fn deref(&self) -> &str {
self.0.as_str()
}
}
impl NodeConfig {
pub fn tags(&self) -> &[String] {
&self.tags
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub fn allows_local_deployment(&self) -> bool {
self.allow_local_deployment
}
pub fn privilege_escalation_command(&self) -> &Vec<String> {
&self.privilege_escalation_command
}
pub fn build_on_target(&self) -> bool {
self.build_on_target
}
pub fn set_build_on_target(&mut self, enable: bool) {
self.build_on_target = enable;
}
pub fn to_ssh_host(&self) -> Option<Ssh> {
self.target_host.as_ref().map(|target_host| {
let mut host = Ssh::new(self.target_user.clone(), 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
})
}
}
impl NixFlags {
pub fn set_show_trace(&mut self, show_trace: bool) {
self.show_trace = show_trace;
}
pub fn set_pure_eval(&mut self, pure_eval: bool) {
self.pure_eval = pure_eval;
}
pub fn set_impure(&mut self, impure: bool) {
self.impure = impure;
}
pub fn set_builders(&mut self, builders: Option<String>) {
self.builders = builders;
}
pub fn set_options(&mut self, options: HashMap<String, String>) {
self.options = options;
}
pub fn to_args(&self) -> Vec<String> {
let mut args = Vec::new();
if let Some(builders) = &self.builders {
args.append(&mut vec![
"--option".to_string(),
"builders".to_string(),
builders.clone(),
]);
}
if self.show_trace {
args.push("--show-trace".to_string());
}
if self.pure_eval {
args.push("--pure-eval".to_string());
}
if self.impure {
args.push("--impure".to_string());
}
for (name, value) in self.options.iter() {
args.push("--option".to_string());
args.push(name.to_string());
args.push(value.to_string());
}
args
}
}
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",
));
}
if path.components().count() != 1 {
return Err(ValidationErrorType::new(
"Secret key name cannot contain path separators",
));
}
}
Ok(())
}