Make flake resolution (slightly) less terrible
Instead of using `path:` which always copies the entire directory, we now try to resolve the Flake URI using `nix flake metadata` which may give us a `git+file:`.
This commit is contained in:
parent
b48753239a
commit
0e0a1e84f0
10 changed files with 137 additions and 48 deletions
|
@ -115,7 +115,7 @@ pub fn subcommand() -> App<'static, 'static> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
|
pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
|
||||||
let hive = util::hive_from_args(local_args).unwrap();
|
let hive = util::hive_from_args(local_args).await.unwrap();
|
||||||
|
|
||||||
log::info!("Enumerating nodes...");
|
log::info!("Enumerating nodes...");
|
||||||
let all_nodes = hive.deployment_info().await.unwrap();
|
let all_nodes = hive.deployment_info().await.unwrap();
|
||||||
|
|
|
@ -89,7 +89,7 @@ pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let hive = util::hive_from_args(local_args).unwrap();
|
let hive = util::hive_from_args(local_args).await.unwrap();
|
||||||
let hostname = if local_args.is_present("node") {
|
let hostname = if local_args.is_present("node") {
|
||||||
local_args.value_of("node").unwrap().to_owned()
|
local_args.value_of("node").unwrap().to_owned()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -55,7 +55,7 @@ It's recommended to use -- to separate Colmena options from the command to run.
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
|
pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
|
||||||
let hive = util::hive_from_args(local_args).unwrap();
|
let hive = util::hive_from_args(local_args).await.unwrap();
|
||||||
|
|
||||||
log::info!("Enumerating nodes...");
|
log::info!("Enumerating nodes...");
|
||||||
let all_nodes = hive.deployment_info().await.unwrap();
|
let all_nodes = hive.deployment_info().await.unwrap();
|
||||||
|
|
|
@ -28,7 +28,7 @@ For example, to retrieve the configuration of one node, you may write something
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
|
pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
|
||||||
let hive = util::hive_from_args(local_args).unwrap();
|
let hive = util::hive_from_args(local_args).await.unwrap();
|
||||||
|
|
||||||
if !(local_args.is_present("expression") ^ local_args.is_present("expression_file")) {
|
if !(local_args.is_present("expression") ^ local_args.is_present("expression_file")) {
|
||||||
log::error!("Either an expression (-E) or a .nix file containing an expression should be specified, not both.");
|
log::error!("Either an expression (-E) or a .nix file containing an expression should be specified, not both.");
|
||||||
|
|
92
src/nix/flake.rs
Normal file
92
src/nix/flake.rs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
//! Nix Flake utilities.
|
||||||
|
|
||||||
|
use std::convert::AsRef;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Stdio;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use super::{NixCheck, NixError, NixResult};
|
||||||
|
|
||||||
|
/// A Nix Flake.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Flake {
|
||||||
|
/// The Flake URI.
|
||||||
|
uri: String,
|
||||||
|
|
||||||
|
/// The directory the flake lives in, if it's a local flake.
|
||||||
|
local_dir: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Flake {
|
||||||
|
/// Creates a flake from the given directory.
|
||||||
|
///
|
||||||
|
/// This will try to retrieve the resolved URL of the local flake
|
||||||
|
/// in the specified directory.
|
||||||
|
pub async fn from_dir<P: AsRef<Path>>(dir: P) -> NixResult<Self> {
|
||||||
|
NixCheck::require_flake_support().await?;
|
||||||
|
|
||||||
|
let flake = dir.as_ref().as_os_str().to_str()
|
||||||
|
.expect("Flake directory path contains non-UTF-8 characters");
|
||||||
|
|
||||||
|
let info = FlakeMetadata::resolve(flake).await?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
uri: info.resolved_url,
|
||||||
|
local_dir: Some(dir.as_ref().to_owned()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a flake from a Flake URI.
|
||||||
|
pub async fn from_uri(uri: String) -> NixResult<Self> {
|
||||||
|
NixCheck::require_flake_support().await?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
uri,
|
||||||
|
local_dir: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the URI.
|
||||||
|
pub fn uri(&self) -> &str {
|
||||||
|
&self.uri
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the local directory, if it exists.
|
||||||
|
pub fn local_dir(&self) -> Option<&Path> {
|
||||||
|
self.local_dir.as_ref().map(|d| d.as_path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `nix flake metadata --json` invocation.
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct FlakeMetadata {
|
||||||
|
/// The resolved URL of the flake.
|
||||||
|
#[serde(rename = "resolvedUrl")]
|
||||||
|
resolved_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlakeMetadata {
|
||||||
|
/// Resolves a flake.
|
||||||
|
async fn resolve(flake: &str) -> NixResult<Self> {
|
||||||
|
let child = Command::new("nix")
|
||||||
|
.args(&["flake", "metadata", "--json"])
|
||||||
|
.args(&["--experimental-features", "nix-command flakes"])
|
||||||
|
.arg(flake)
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
let output = child.wait_with_output().await?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(output.status.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_json::from_slice::<FlakeMetadata>(&output.stdout)
|
||||||
|
.map_err(|_| {
|
||||||
|
let output = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
NixError::BadOutput { output }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,13 +10,13 @@ use serde::Serialize;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
Flake,
|
||||||
StoreDerivation,
|
StoreDerivation,
|
||||||
NixResult,
|
NixResult,
|
||||||
NixError,
|
|
||||||
NodeConfig,
|
NodeConfig,
|
||||||
ProfileMap,
|
ProfileMap,
|
||||||
};
|
};
|
||||||
use super::{NixCommand, NixCheck};
|
use super::NixCommand;
|
||||||
use crate::util::CommandExecution;
|
use crate::util::CommandExecution;
|
||||||
use crate::progress::TaskProgress;
|
use crate::progress::TaskProgress;
|
||||||
|
|
||||||
|
@ -24,49 +24,38 @@ const HIVE_EVAL: &'static [u8] = include_bytes!("eval.nix");
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum HivePath {
|
pub enum HivePath {
|
||||||
/// A Nix Flake URI.
|
/// A Nix Flake.
|
||||||
///
|
///
|
||||||
/// The flake must contain the `colmena` output.
|
/// The flake must contain the `colmena` output.
|
||||||
Flake(String),
|
Flake(Flake),
|
||||||
|
|
||||||
/// A regular .nix file
|
/// A regular .nix file
|
||||||
Legacy(PathBuf),
|
Legacy(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HivePath {
|
impl HivePath {
|
||||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Self {
|
pub async fn from_path<P: AsRef<Path>>(path: P) -> NixResult<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() {
|
||||||
if osstr == "flake.nix" {
|
if osstr == "flake.nix" {
|
||||||
let parent = path.parent().unwrap().canonicalize().unwrap();
|
let parent = path.parent().unwrap();
|
||||||
let parent = parent.to_str().unwrap();
|
let flake = Flake::from_dir(parent).await?;
|
||||||
let uri = format!("path:{}", parent);
|
return Ok(Self::Flake(flake));
|
||||||
|
|
||||||
return Self::Flake(uri);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::Legacy(path.to_owned())
|
Ok(Self::Legacy(path.to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn context_dir(&self) -> Option<PathBuf> {
|
fn context_dir(&self) -> Option<PathBuf> {
|
||||||
match self {
|
match self {
|
||||||
Self::Legacy(p) => {
|
Self::Legacy(p) => {
|
||||||
if let Some(parent) = p.parent() {
|
p.parent().map(|d| d.to_owned())
|
||||||
return Some(parent.to_owned());
|
}
|
||||||
}
|
Self::Flake(flake) => {
|
||||||
None
|
flake.local_dir().map(|d| d.to_owned())
|
||||||
}
|
}
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_flake(&self) -> bool {
|
|
||||||
if let Self::Flake(_) = self {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,13 +117,6 @@ impl Hive {
|
||||||
|
|
||||||
/// Retrieve deployment info for all nodes.
|
/// Retrieve deployment info for all nodes.
|
||||||
pub async fn deployment_info(&self) -> NixResult<HashMap<String, NodeConfig>> {
|
pub async fn deployment_info(&self) -> NixResult<HashMap<String, NodeConfig>> {
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Really ugly :(
|
// FIXME: Really ugly :(
|
||||||
let s: String = self.nix_instantiate("hive.deploymentConfigJson").eval_with_builders().await?
|
let s: String = self.nix_instantiate("hive.deploymentConfigJson").eval_with_builders().await?
|
||||||
.capture_json().await?;
|
.capture_json().await?;
|
||||||
|
@ -281,7 +263,7 @@ impl<'hive> NixInstantiate<'hive> {
|
||||||
self.expression,
|
self.expression,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
HivePath::Flake(uri) => {
|
HivePath::Flake(flake) => {
|
||||||
command
|
command
|
||||||
.args(&["--experimental-features", "flakes"])
|
.args(&["--experimental-features", "flakes"])
|
||||||
.arg("--no-gc-warning")
|
.arg("--no-gc-warning")
|
||||||
|
@ -289,7 +271,7 @@ impl<'hive> NixInstantiate<'hive> {
|
||||||
.arg(format!(
|
.arg(format!(
|
||||||
"with builtins; let eval = import {}; hive = eval {{ flakeUri = \"{}\"; }}; in {}",
|
"with builtins; let eval = import {}; hive = eval {{ flakeUri = \"{}\"; }}; in {}",
|
||||||
self.hive.eval_nix.to_str().unwrap(),
|
self.hive.eval_nix.to_str().unwrap(),
|
||||||
&uri,
|
flake.uri(),
|
||||||
self.expression,
|
self.expression,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,10 @@ use std::process::Stdio;
|
||||||
|
|
||||||
use log::Level;
|
use log::Level;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use super::{NixError, NixResult};
|
||||||
|
|
||||||
struct NixVersion {
|
struct NixVersion {
|
||||||
major: usize,
|
major: usize,
|
||||||
minor: usize,
|
minor: usize,
|
||||||
|
@ -88,6 +89,17 @@ impl NixCheck {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn require_flake_support() -> NixResult<()> {
|
||||||
|
let check = Self::detect().await;
|
||||||
|
|
||||||
|
if !check.flakes_supported() {
|
||||||
|
check.print_flakes_info(true);
|
||||||
|
Err(NixError::NoFlakesSupport)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn print_version_info(&self) {
|
pub fn print_version_info(&self) {
|
||||||
if let Some(v) = &self.version {
|
if let Some(v) = &self.version {
|
||||||
log::info!("Nix Version: {}", v);
|
log::info!("Nix Version: {}", v);
|
||||||
|
|
|
@ -36,6 +36,9 @@ pub use deployment::{Goal, Target, Deployment};
|
||||||
pub mod info;
|
pub mod info;
|
||||||
pub use info::NixCheck;
|
pub use info::NixCheck;
|
||||||
|
|
||||||
|
pub mod flake;
|
||||||
|
pub use flake::Flake;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ impl TempHive {
|
||||||
let mut temp_file = NamedTempFile::new().unwrap();
|
let mut temp_file = NamedTempFile::new().unwrap();
|
||||||
temp_file.write_all(text.as_bytes()).unwrap();
|
temp_file.write_all(text.as_bytes()).unwrap();
|
||||||
|
|
||||||
let hive_path = HivePath::from_path(temp_file.path());
|
let hive_path = block_on(HivePath::from_path(temp_file.path())).unwrap();
|
||||||
let hive = Hive::new(hive_path).unwrap();
|
let hive = Hive::new(hive_path).unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -149,11 +149,10 @@ fn test_parse_simple() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_flake() {
|
fn test_parse_flake() {
|
||||||
let flake_path = {
|
let flake_dir = PathBuf::from("./src/nix/tests/simple-flake");
|
||||||
let p = PathBuf::from("./src/nix/tests/simple-flake");
|
let flake = block_on(Flake::from_dir(flake_dir)).unwrap();
|
||||||
p.canonicalize().unwrap()
|
|
||||||
};
|
let hive_path = HivePath::Flake(flake);
|
||||||
let hive_path = HivePath::Flake(format!("path:{}", flake_path.to_str().unwrap()));
|
|
||||||
let mut hive = Hive::new(hive_path).unwrap();
|
let mut hive = Hive::new(hive_path).unwrap();
|
||||||
|
|
||||||
hive.set_show_trace(true);
|
hive.set_show_trace(true);
|
||||||
|
|
|
@ -9,7 +9,7 @@ use glob::Pattern as GlobPattern;
|
||||||
use tokio::io::{AsyncRead, AsyncBufReadExt, BufReader};
|
use tokio::io::{AsyncRead, AsyncBufReadExt, BufReader};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use super::nix::{NodeConfig, Hive, HivePath, NixResult};
|
use super::nix::{Flake, NodeConfig, Hive, HivePath, NixResult};
|
||||||
use super::progress::TaskProgress;
|
use super::progress::TaskProgress;
|
||||||
|
|
||||||
enum NodeFilter {
|
enum NodeFilter {
|
||||||
|
@ -79,7 +79,7 @@ impl CommandExecution {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hive_from_args(args: &ArgMatches<'_>) -> NixResult<Hive> {
|
pub async fn hive_from_args(args: &ArgMatches<'_>) -> NixResult<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
|
||||||
|
@ -142,7 +142,8 @@ pub fn hive_from_args(args: &ArgMatches<'_>) -> NixResult<Hive> {
|
||||||
|
|
||||||
if !fpath.exists() && path.contains(":") {
|
if !fpath.exists() && path.contains(":") {
|
||||||
// Treat as flake URI
|
// Treat as flake URI
|
||||||
let hive_path = HivePath::Flake(path);
|
let flake = Flake::from_uri(path).await?;
|
||||||
|
let hive_path = HivePath::Flake(flake);
|
||||||
let mut hive = Hive::new(hive_path)?;
|
let mut hive = Hive::new(hive_path)?;
|
||||||
|
|
||||||
if args.is_present("show-trace") {
|
if args.is_present("show-trace") {
|
||||||
|
@ -156,7 +157,7 @@ pub fn hive_from_args(args: &ArgMatches<'_>) -> NixResult<Hive> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let hive_path = HivePath::from_path(path);
|
let hive_path = HivePath::from_path(path).await?;
|
||||||
let mut hive = Hive::new(hive_path)?;
|
let mut hive = Hive::new(hive_path)?;
|
||||||
|
|
||||||
if args.is_present("show-trace") {
|
if args.is_present("show-trace") {
|
||||||
|
|
Loading…
Add table
Reference in a new issue