forked from DGNum/colmena
Make flake evaluation pure
This seems to be the easiest way to get pure-eval working with existing evaluation mechinery (nix-instantiate, nix-eval-jobs). Now `--pure-eval` is forced for flakes with user being able to add `--impure` as needed.
This commit is contained in:
parent
092e5848ab
commit
8aca525788
7 changed files with 70 additions and 29 deletions
|
@ -2,6 +2,7 @@
|
|||
|
||||
## Release 0.4.0 (Unreleased)
|
||||
|
||||
- Flake evaluation is now actually pure by default. To enable impure expressions, pass `--impure`.
|
||||
- `--reboot` is added to trigger a reboot and wait for the node to come back up.
|
||||
- The target user is no longer explicitly set when `deployment.targetUser` is null ([#91](https://github.com/zhaofengli/colmena/pull/91)).
|
||||
- In `apply-local`, we now only escalate privileges during activation ([#85](https://github.com/zhaofengli/colmena/issues/85)).
|
||||
|
|
|
@ -10,7 +10,7 @@ use tokio::process::Command;
|
|||
use super::{ColmenaError, ColmenaResult, NixCheck};
|
||||
|
||||
/// A Nix Flake.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Flake {
|
||||
/// The flake metadata.
|
||||
metadata: FlakeMetadata,
|
||||
|
@ -20,7 +20,7 @@ pub struct Flake {
|
|||
}
|
||||
|
||||
/// A `nix flake metadata --json` invocation.
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct FlakeMetadata {
|
||||
/// The resolved URL of the flake.
|
||||
#[serde(rename = "resolvedUrl")]
|
||||
|
@ -105,3 +105,20 @@ impl FlakeMetadata {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Quietly locks the dependencies of a flake.
|
||||
pub async fn lock_flake_quiet(uri: &str) -> ColmenaResult<()> {
|
||||
let status = Command::new("nix")
|
||||
.args(&["flake", "lock"])
|
||||
.args(&["--experimental-features", "nix-command flakes"])
|
||||
.arg(uri)
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.await?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(status.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -13,8 +13,9 @@ use tempfile::TempDir;
|
|||
|
||||
use super::{Flake, HivePath};
|
||||
use crate::error::ColmenaResult;
|
||||
use crate::nix::flake::lock_flake_quiet;
|
||||
|
||||
const FLAKE_NIX: &[u8] = include_bytes!("flake.nix");
|
||||
const FLAKE_NIX: &str = include_str!("flake.nix");
|
||||
const EVAL_NIX: &[u8] = include_bytes!("eval.nix");
|
||||
const OPTIONS_NIX: &[u8] = include_bytes!("options.nix");
|
||||
const MODULES_NIX: &[u8] = include_bytes!("modules.nix");
|
||||
|
@ -22,6 +23,9 @@ const MODULES_NIX: &[u8] = include_bytes!("modules.nix");
|
|||
/// Static files required to evaluate a Hive configuration.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Assets {
|
||||
/// Path to the hive being evaluated.
|
||||
hive_path: HivePath,
|
||||
|
||||
/// Temporary directory holding the files.
|
||||
temp_dir: TempDir,
|
||||
|
||||
|
@ -30,7 +34,7 @@ pub(super) struct Assets {
|
|||
}
|
||||
|
||||
impl Assets {
|
||||
pub async fn new(flake: bool) -> ColmenaResult<Self> {
|
||||
pub async fn new(hive_path: HivePath) -> ColmenaResult<Self> {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
create_file(&temp_dir, "eval.nix", false, EVAL_NIX)?;
|
||||
|
@ -39,27 +43,30 @@ impl Assets {
|
|||
|
||||
let mut assets_flake_uri = None;
|
||||
|
||||
if flake {
|
||||
if let HivePath::Flake(hive_flake) = &hive_path {
|
||||
// Emit a temporary flake, then resolve the locked URI
|
||||
create_file(&temp_dir, "flake.nix", false, FLAKE_NIX)?;
|
||||
let flake_nix = FLAKE_NIX.replace("%hive%", hive_flake.locked_uri());
|
||||
create_file(&temp_dir, "flake.nix", false, flake_nix.as_bytes())?;
|
||||
|
||||
// We explicitly specify `path:` instead of letting Nix resolve
|
||||
// automatically, which would involve checking parent directories
|
||||
// for a git repository.
|
||||
let uri = format!("path:{}", temp_dir.path().to_str().unwrap());
|
||||
let _ = lock_flake_quiet(&uri).await;
|
||||
let assets_flake = Flake::from_uri(uri).await?;
|
||||
assets_flake_uri = Some(assets_flake.locked_uri().to_owned());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
hive_path,
|
||||
temp_dir,
|
||||
assets_flake_uri,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the base expression from which the evaluated Hive can be used.
|
||||
pub fn get_base_expression(&self, hive_path: &HivePath) -> String {
|
||||
match hive_path {
|
||||
pub fn get_base_expression(&self) -> String {
|
||||
match &self.hive_path {
|
||||
HivePath::Legacy(path) => {
|
||||
format!(
|
||||
"with builtins; let eval = import {eval_nix}; hive = eval {{ rawHive = import {path}; colmenaOptions = import {options_nix}; colmenaModules = import {modules_nix}; }}; in ",
|
||||
|
@ -69,11 +76,10 @@ impl Assets {
|
|||
modules_nix = self.get_path("modules.nix"),
|
||||
)
|
||||
}
|
||||
HivePath::Flake(flake) => {
|
||||
HivePath::Flake(_) => {
|
||||
format!(
|
||||
"with builtins; let assets = getFlake \"{assets_flake_uri}\"; hive = assets.lib.colmenaEval {{ flakeUri = \"{flake_uri}\"; }}; in ",
|
||||
"with builtins; let assets = getFlake \"{assets_flake_uri}\"; hive = assets.colmenaEval; in ",
|
||||
assets_flake_uri = self.assets_flake_uri.as_ref().expect("The assets flake must have been initialized"),
|
||||
flake_uri = flake.locked_uri(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ rawHive ? null # Colmena Hive attrset
|
||||
, flakeUri ? null # Nix Flake URI with `outputs.colmena`
|
||||
, hermetic ? flakeUri != null # Whether we are allowed to use <nixpkgs>
|
||||
, rawFlake ? null # Nix Flake attrset with `outputs.colmena`
|
||||
, hermetic ? rawFlake != null # Whether we are allowed to use <nixpkgs>
|
||||
, colmenaOptions
|
||||
, colmenaModules
|
||||
}:
|
||||
|
@ -19,10 +19,8 @@ let
|
|||
|
||||
|
||||
uncheckedHive = let
|
||||
flakeToHive = flakeUri: let
|
||||
flake = builtins.getFlake flakeUri;
|
||||
hive = if flake.outputs ? colmena then flake.outputs.colmena else throw "Flake must define outputs.colmena.";
|
||||
in hive;
|
||||
flakeToHive = rawFlake:
|
||||
if rawFlake.outputs ? colmena then rawFlake.outputs.colmena else throw "Flake must define outputs.colmena.";
|
||||
|
||||
rawToHive = rawHive:
|
||||
if typeOf rawHive == "lambda" || rawHive ? __functor then rawHive {}
|
||||
|
@ -30,8 +28,8 @@ let
|
|||
else throw "The config must evaluate to an attribute set.";
|
||||
in
|
||||
if rawHive != null then rawToHive rawHive
|
||||
else if flakeUri != null then flakeToHive flakeUri
|
||||
else throw "Either an attribute set or a flake URI must be specified.";
|
||||
else if rawFlake != null then flakeToHive rawFlake
|
||||
else throw "Either a plain Hive attribute set or a Nix Flake attribute set must be specified.";
|
||||
|
||||
uncheckedUserMeta =
|
||||
if uncheckedHive ? meta && uncheckedHive ? network then
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
description = "Internal Colmena expressions";
|
||||
|
||||
outputs = { ... }: {
|
||||
lib.colmenaEval = {
|
||||
rawHive ? null,
|
||||
flakeUri ? null,
|
||||
hermetic ? flakeUri != null,
|
||||
}: import ./eval.nix {
|
||||
inherit rawHive flakeUri hermetic;
|
||||
inputs = {
|
||||
hive.url = "%hive%";
|
||||
};
|
||||
|
||||
outputs = { self, hive }: {
|
||||
colmenaEval = import ./eval.nix {
|
||||
rawFlake = hive;
|
||||
colmenaOptions = import ./options.nix;
|
||||
colmenaModules = import ./modules.nix;
|
||||
};
|
||||
|
|
|
@ -21,7 +21,7 @@ use crate::job::JobHandle;
|
|||
use crate::util::{CommandExecution, CommandExt};
|
||||
use assets::Assets;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HivePath {
|
||||
/// A Nix Flake.
|
||||
///
|
||||
|
@ -93,7 +93,7 @@ impl HivePath {
|
|||
impl Hive {
|
||||
pub async fn new(path: HivePath) -> ColmenaResult<Self> {
|
||||
let context_dir = path.context_dir();
|
||||
let assets = Assets::new(path.is_flake()).await?;
|
||||
let assets = Assets::new(path.clone()).await?;
|
||||
|
||||
Ok(Self {
|
||||
path,
|
||||
|
@ -127,6 +127,7 @@ impl Hive {
|
|||
pub fn nix_options(&self) -> NixOptions {
|
||||
let mut options = NixOptions::default();
|
||||
options.set_show_trace(self.show_trace);
|
||||
options.set_pure_eval(self.path.is_flake());
|
||||
options
|
||||
}
|
||||
|
||||
|
@ -361,7 +362,7 @@ impl Hive {
|
|||
|
||||
/// Returns the base expression from which the evaluated Hive can be used.
|
||||
fn get_base_expression(&self) -> String {
|
||||
self.assets.get_base_expression(self.path())
|
||||
self.assets.get_base_expression()
|
||||
}
|
||||
|
||||
/// Returns whether this Hive is a flake.
|
||||
|
|
|
@ -97,6 +97,12 @@ pub struct NixOptions {
|
|||
/// 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>.
|
||||
|
@ -187,6 +193,10 @@ impl NixOptions {
|
|||
self.show_trace = show_trace;
|
||||
}
|
||||
|
||||
pub fn set_pure_eval(&mut self, pure_eval: bool) {
|
||||
self.pure_eval = pure_eval;
|
||||
}
|
||||
|
||||
pub fn set_builders(&mut self, builders: Option<String>) {
|
||||
self.builders = builders;
|
||||
}
|
||||
|
@ -206,6 +216,14 @@ impl NixOptions {
|
|||
options.push("--show-trace".to_string());
|
||||
}
|
||||
|
||||
if self.pure_eval {
|
||||
options.push("--pure-eval".to_string());
|
||||
}
|
||||
|
||||
if self.impure {
|
||||
options.push("--impure".to_string());
|
||||
}
|
||||
|
||||
options
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue