2021-01-24 14:08:48 -08:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::io::Write;
|
|
|
|
use std::path::{Path, PathBuf};
|
2021-06-29 01:02:43 -07:00
|
|
|
use std::convert::AsRef;
|
2021-01-24 14:08:48 -08:00
|
|
|
|
|
|
|
use tempfile::{NamedTempFile, TempPath};
|
|
|
|
use tokio::process::Command;
|
2021-07-13 01:38:12 -07:00
|
|
|
use tokio::sync::RwLock;
|
2021-01-24 14:08:48 -08:00
|
|
|
use serde::Serialize;
|
2021-02-08 18:38:14 -08:00
|
|
|
use validator::Validate;
|
2021-01-24 14:08:48 -08:00
|
|
|
|
|
|
|
use super::{
|
|
|
|
StoreDerivation,
|
|
|
|
NixResult,
|
2021-06-29 01:02:43 -07:00
|
|
|
NixError,
|
2021-01-24 14:08:48 -08:00
|
|
|
NodeConfig,
|
|
|
|
ProfileMap,
|
|
|
|
};
|
2021-06-29 01:02:43 -07:00
|
|
|
use super::{NixCommand, NixCheck};
|
2021-01-24 14:08:48 -08:00
|
|
|
use crate::util::CommandExecution;
|
2021-02-10 10:38:03 -08:00
|
|
|
use crate::progress::TaskProgress;
|
2021-01-24 14:08:48 -08:00
|
|
|
|
|
|
|
const HIVE_EVAL: &'static [u8] = include_bytes!("eval.nix");
|
|
|
|
|
2021-06-29 01:02:43 -07:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum HivePath {
|
|
|
|
/// A Nix Flake URI.
|
|
|
|
///
|
|
|
|
/// The flake must contain the `colmena` output.
|
|
|
|
Flake(String),
|
|
|
|
|
|
|
|
/// A regular .nix file
|
|
|
|
Legacy(PathBuf),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl HivePath {
|
|
|
|
pub fn from_path<P: AsRef<Path>>(path: P) -> Self {
|
|
|
|
let path = path.as_ref();
|
|
|
|
|
|
|
|
if let Some(osstr) = path.file_name() {
|
|
|
|
if osstr == "flake.nix" {
|
|
|
|
let parent = path.parent().unwrap().to_str().unwrap();
|
|
|
|
let uri = format!("path:{}", parent);
|
|
|
|
|
|
|
|
return Self::Flake(uri);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Self::Legacy(path.to_owned())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn context_dir(&self) -> Option<PathBuf> {
|
|
|
|
match self {
|
|
|
|
Self::Legacy(p) => {
|
|
|
|
if let Some(parent) = p.parent() {
|
|
|
|
return Some(parent.to_owned());
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
2021-06-29 01:02:43 -07:00
|
|
|
|
|
|
|
fn is_flake(&self) -> bool {
|
|
|
|
if let Self::Flake(_) = self {
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2021-06-29 01:02:43 -07:00
|
|
|
}
|
|
|
|
|
2021-01-24 14:08:48 -08:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Hive {
|
2021-06-29 01:02:43 -07:00
|
|
|
/// Path to the hive.
|
|
|
|
path: HivePath,
|
|
|
|
|
|
|
|
/// Path to the context directory.
|
|
|
|
///
|
|
|
|
/// Normally this is directory containing the "hive.nix"
|
|
|
|
/// or "flake.nix".
|
|
|
|
context_dir: Option<PathBuf>,
|
|
|
|
|
|
|
|
/// Path to temporary file containing eval.nix.
|
2021-01-24 14:08:48 -08:00
|
|
|
eval_nix: TempPath,
|
2021-06-29 01:02:43 -07:00
|
|
|
|
|
|
|
/// Whether to pass --show-trace in Nix commands.
|
2021-01-24 14:08:48 -08:00
|
|
|
show_trace: bool,
|
2021-07-13 01:38:12 -07:00
|
|
|
|
|
|
|
/// The cached --builders expression.
|
|
|
|
builders: RwLock<Option<Option<String>>>,
|
2021-01-24 14:08:48 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Hive {
|
2021-06-29 01:02:43 -07:00
|
|
|
pub fn new(path: HivePath) -> NixResult<Self> {
|
2021-02-08 18:38:14 -08:00
|
|
|
let mut eval_nix = NamedTempFile::new()?;
|
2021-01-24 14:08:48 -08:00
|
|
|
eval_nix.write_all(HIVE_EVAL).unwrap();
|
|
|
|
|
2021-06-29 01:02:43 -07:00
|
|
|
let context_dir = path.context_dir();
|
|
|
|
|
2021-01-24 14:08:48 -08:00
|
|
|
Ok(Self {
|
2021-06-29 01:02:43 -07:00
|
|
|
path,
|
|
|
|
context_dir,
|
2021-01-24 14:08:48 -08:00
|
|
|
eval_nix: eval_nix.into_temp_path(),
|
|
|
|
show_trace: false,
|
2021-07-13 01:38:12 -07:00
|
|
|
builders: RwLock::new(None),
|
2021-01-24 14:08:48 -08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-06-29 01:02:43 -07:00
|
|
|
pub fn context_dir(&self) -> Option<&Path> {
|
|
|
|
self.context_dir.as_ref().map(|p| p.as_ref())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_show_trace(&mut self, value: bool) {
|
2021-01-24 14:08:48 -08:00
|
|
|
self.show_trace = value;
|
|
|
|
}
|
|
|
|
|
2021-04-09 23:43:31 -07:00
|
|
|
pub async fn nix_options(&self) -> NixResult<Vec<String>> {
|
2021-07-13 01:38:12 -07:00
|
|
|
let mut options = self.builder_args().await?;
|
2021-04-09 23:43:31 -07:00
|
|
|
|
|
|
|
if self.show_trace {
|
2021-07-13 01:38:12 -07:00
|
|
|
options.push("--show-trace".to_owned());
|
2021-04-09 23:43:31 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(options)
|
|
|
|
}
|
|
|
|
|
2021-03-18 15:03:58 -07:00
|
|
|
/// Retrieve deployment info for all nodes.
|
2021-01-24 14:08:48 -08:00
|
|
|
pub async fn deployment_info(&self) -> NixResult<HashMap<String, NodeConfig>> {
|
2021-06-29 01:02:43 -07:00
|
|
|
let nix_check = NixCheck::detect().await;
|
|
|
|
|
|
|
|
if self.path.is_flake() && !nix_check.flakes_supported() {
|
|
|
|
nix_check.print_flakes_info(true);
|
|
|
|
return Err(NixError::NoFlakesSupport);
|
|
|
|
}
|
|
|
|
|
2021-01-24 14:08:48 -08:00
|
|
|
// FIXME: Really ugly :(
|
2021-07-13 01:38:12 -07:00
|
|
|
let s: String = self.nix_instantiate("hive.deploymentConfigJson").eval_with_builders().await?
|
2021-01-24 14:08:48 -08:00
|
|
|
.capture_json().await?;
|
|
|
|
|
2021-02-08 18:38:14 -08:00
|
|
|
let configs: HashMap<String, NodeConfig> = serde_json::from_str(&s).unwrap();
|
|
|
|
for config in configs.values() {
|
|
|
|
config.validate()?;
|
|
|
|
for key in config.keys.values() {
|
|
|
|
key.validate()?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(configs)
|
2021-01-24 14:08:48 -08:00
|
|
|
}
|
|
|
|
|
2021-03-18 15:03:58 -07:00
|
|
|
/// Retrieve deployment info for a single node.
|
|
|
|
pub async fn deployment_info_for(&self, node: &str) -> NixResult<Option<NodeConfig>> {
|
|
|
|
let expr = format!("toJSON (hive.nodes.\"{}\".config.deployment or null)", node);
|
2021-07-13 01:38:12 -07:00
|
|
|
let s: String = self.nix_instantiate(&expr).eval_with_builders().await?
|
2021-03-18 15:03:58 -07:00
|
|
|
.capture_json().await?;
|
|
|
|
|
|
|
|
Ok(serde_json::from_str(&s).unwrap())
|
|
|
|
}
|
|
|
|
|
2021-01-24 14:08:48 -08:00
|
|
|
/// Evaluates selected nodes.
|
|
|
|
///
|
|
|
|
/// Evaluation may take up a lot of memory, so we make it possible
|
|
|
|
/// to split up the evaluation process into chunks and run them
|
|
|
|
/// concurrently with other processes (e.g., build and apply).
|
2021-02-10 10:38:03 -08:00
|
|
|
pub async fn eval_selected(&self, nodes: &Vec<String>, progress_bar: TaskProgress) -> (NixResult<StoreDerivation<ProfileMap>>, Option<String>) {
|
2021-01-28 23:58:54 -08:00
|
|
|
// FIXME: The return type is ugly...
|
|
|
|
|
|
|
|
let nodes_expr = SerializedNixExpresssion::new(nodes);
|
|
|
|
if let Err(e) = nodes_expr {
|
|
|
|
return (Err(e), None);
|
|
|
|
}
|
|
|
|
let nodes_expr = nodes_expr.unwrap();
|
|
|
|
|
2021-01-24 14:08:48 -08:00
|
|
|
let expr = format!("hive.buildSelected {{ names = {}; }}", nodes_expr.expression());
|
|
|
|
|
2021-07-13 01:38:12 -07:00
|
|
|
let command = match self.nix_instantiate(&expr).instantiate_with_builders().await {
|
|
|
|
Ok(command) => command,
|
|
|
|
Err(e) => {
|
|
|
|
return (Err(e), None);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-02-09 19:28:45 -08:00
|
|
|
let mut execution = CommandExecution::new(command);
|
|
|
|
execution.set_progress_bar(progress_bar);
|
2021-01-24 14:08:48 -08:00
|
|
|
|
|
|
|
let eval = execution
|
2021-01-28 23:58:54 -08:00
|
|
|
.capture_store_path().await;
|
2021-01-24 14:08:48 -08:00
|
|
|
|
2021-01-28 23:58:54 -08:00
|
|
|
let (_, stderr) = execution.get_logs();
|
|
|
|
|
|
|
|
match eval {
|
|
|
|
Ok(path) => {
|
|
|
|
let drv = path.to_derivation()
|
|
|
|
.expect("The result should be a store derivation");
|
|
|
|
|
|
|
|
(Ok(drv), stderr.cloned())
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
(Err(e), stderr.cloned())
|
|
|
|
}
|
|
|
|
}
|
2021-01-24 14:08:48 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Evaluates an expression using values from the configuration
|
|
|
|
pub async fn introspect(&self, expression: String) -> NixResult<String> {
|
|
|
|
let expression = format!("toJSON (hive.introspect ({}))", expression);
|
2021-07-13 01:38:12 -07:00
|
|
|
self.nix_instantiate(&expression).eval_with_builders().await?
|
2021-01-24 14:08:48 -08:00
|
|
|
.capture_json().await
|
|
|
|
}
|
|
|
|
|
2021-07-13 01:38:12 -07:00
|
|
|
/// Retrieve machinesFile setting for the hive.
|
|
|
|
async fn machines_file(&self) -> NixResult<Option<String>> {
|
|
|
|
if let Some(builders_opt) = &*self.builders.read().await {
|
|
|
|
return Ok(builders_opt.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
let expr = "toJSON (hive.meta.machinesFile or null)";
|
|
|
|
let s: String = self.nix_instantiate(&expr).eval()
|
|
|
|
.capture_json().await?;
|
|
|
|
|
|
|
|
let parsed: Option<String> = serde_json::from_str(&s).unwrap();
|
|
|
|
self.builders.write().await.replace(parsed.clone());
|
|
|
|
|
|
|
|
Ok(parsed)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns Nix arguments to set builders.
|
|
|
|
async fn builder_args(&self) -> NixResult<Vec<String>> {
|
|
|
|
let mut options = Vec::new();
|
|
|
|
|
2021-08-26 19:59:22 -07:00
|
|
|
if let Some(machines_file) = self.machines_file().await? {
|
2021-07-13 01:38:12 -07:00
|
|
|
options.append(&mut vec![
|
|
|
|
"--option".to_owned(),
|
|
|
|
"builders".to_owned(),
|
|
|
|
format!("@{}", machines_file).to_owned()
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(options)
|
|
|
|
}
|
|
|
|
|
2021-01-24 14:08:48 -08:00
|
|
|
fn nix_instantiate(&self, expression: &str) -> NixInstantiate {
|
|
|
|
NixInstantiate::new(&self, expression.to_owned())
|
|
|
|
}
|
2021-06-29 01:02:43 -07:00
|
|
|
|
|
|
|
fn path(&self) -> &HivePath {
|
|
|
|
&self.path
|
|
|
|
}
|
2021-01-24 14:08:48 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
struct NixInstantiate<'hive> {
|
|
|
|
hive: &'hive Hive,
|
|
|
|
expression: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'hive> NixInstantiate<'hive> {
|
|
|
|
fn new(hive: &'hive Hive, expression: String) -> Self {
|
|
|
|
Self {
|
|
|
|
hive,
|
|
|
|
expression,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn instantiate(self) -> Command {
|
|
|
|
// FIXME: unwrap
|
|
|
|
// Technically filenames can be arbitrary byte strings (OsStr),
|
|
|
|
// but Nix may not like it...
|
|
|
|
|
|
|
|
let mut command = Command::new("nix-instantiate");
|
2021-06-29 01:02:43 -07:00
|
|
|
|
|
|
|
match self.hive.path() {
|
|
|
|
HivePath::Legacy(path) => {
|
|
|
|
command
|
|
|
|
.arg("--no-gc-warning")
|
|
|
|
.arg("-E")
|
|
|
|
.arg(format!(
|
|
|
|
"with builtins; let eval = import {}; hive = eval {{ rawHive = import {}; }}; in {}",
|
|
|
|
self.hive.eval_nix.to_str().unwrap(),
|
|
|
|
path.to_str().unwrap(),
|
|
|
|
self.expression,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
HivePath::Flake(uri) => {
|
|
|
|
command
|
|
|
|
.args(&["--experimental-features", "flakes"])
|
|
|
|
.arg("--no-gc-warning")
|
|
|
|
.arg("-E")
|
|
|
|
.arg(format!(
|
|
|
|
"with builtins; let eval = import {}; hive = eval {{ flakeUri = \"{}\"; }}; in {}",
|
|
|
|
self.hive.eval_nix.to_str().unwrap(),
|
|
|
|
&uri,
|
|
|
|
self.expression,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
2021-01-24 14:08:48 -08:00
|
|
|
|
|
|
|
if self.hive.show_trace {
|
|
|
|
command.arg("--show-trace");
|
|
|
|
}
|
|
|
|
|
|
|
|
command
|
|
|
|
}
|
|
|
|
|
|
|
|
fn eval(self) -> Command {
|
|
|
|
let mut command = self.instantiate();
|
|
|
|
command.arg("--eval").arg("--json");
|
|
|
|
command
|
|
|
|
}
|
2021-07-13 01:38:12 -07:00
|
|
|
|
|
|
|
async fn instantiate_with_builders(self) -> NixResult<Command> {
|
|
|
|
let hive = self.hive;
|
|
|
|
let mut command = self.instantiate();
|
|
|
|
|
|
|
|
let builder_args = hive.builder_args().await?;
|
|
|
|
command.args(&builder_args);
|
|
|
|
|
|
|
|
Ok(command)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn eval_with_builders(self) -> NixResult<Command> {
|
|
|
|
let hive = self.hive;
|
|
|
|
let mut command = self.eval();
|
|
|
|
|
|
|
|
let builder_args = hive.builder_args().await?;
|
|
|
|
command.args(&builder_args);
|
|
|
|
|
|
|
|
Ok(command)
|
|
|
|
}
|
2021-01-24 14:08:48 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// A serialized Nix expression.
|
|
|
|
///
|
|
|
|
/// Very hacky and involves an Import From Derivation, so should be
|
|
|
|
/// avoided as much as possible. But I suppose it's more robust than attempting
|
|
|
|
/// to generate Nix expressions directly or escaping a JSON string to strip
|
|
|
|
/// off Nix interpolation.
|
|
|
|
struct SerializedNixExpresssion {
|
|
|
|
json_file: TempPath,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SerializedNixExpresssion {
|
|
|
|
pub fn new<'de, T>(data: T) -> NixResult<Self> where T: Serialize {
|
|
|
|
let mut tmp = NamedTempFile::new()?;
|
|
|
|
let json = serde_json::to_vec(&data).expect("Could not serialize data");
|
|
|
|
tmp.write_all(&json)?;
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
json_file: tmp.into_temp_path(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn expression(&self) -> String {
|
|
|
|
format!("(builtins.fromJSON (builtins.readFile {}))", self.json_file.to_str().unwrap())
|
|
|
|
}
|
|
|
|
}
|