forked from DGNum/colmena
Directly serialize Nix expressions as quoted strings
This commit is contained in:
parent
271d9ae576
commit
092e5848ab
3 changed files with 80 additions and 57 deletions
73
src/nix/expression.rs
Normal file
73
src/nix/expression.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
//! Nix expression serializer.
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
/// A Nix expression.
|
||||||
|
pub trait NixExpression: Send + Sync {
|
||||||
|
/// Returns the full Nix expression to be evaluated.
|
||||||
|
fn expression(&self) -> String;
|
||||||
|
|
||||||
|
/// Returns whether this expression requires the use of flakes.
|
||||||
|
fn requires_flakes(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A serialized Nix expression.
|
||||||
|
pub struct SerializedNixExpression(String);
|
||||||
|
|
||||||
|
impl NixExpression for String {
|
||||||
|
fn expression(&self) -> String {
|
||||||
|
self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerializedNixExpression {
|
||||||
|
pub fn new<T>(data: T) -> Self
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
let json = serde_json::to_string(&data).expect("Could not serialize data");
|
||||||
|
let quoted = nix_quote(&json);
|
||||||
|
|
||||||
|
Self(quoted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NixExpression for SerializedNixExpression {
|
||||||
|
fn expression(&self) -> String {
|
||||||
|
format!("(builtins.fromJSON {})", &self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns a string into a quoted Nix string expression.
|
||||||
|
fn nix_quote(s: &str) -> String {
|
||||||
|
let inner = s
|
||||||
|
.replace('\\', r#"\\"#)
|
||||||
|
.replace('"', r#"\""#)
|
||||||
|
.replace("${", r#"\${"#);
|
||||||
|
|
||||||
|
format!("\"{}\"", inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nix_quote() {
|
||||||
|
let cases = [
|
||||||
|
(r#"["a", "b"]"#, r#""[\"a\", \"b\"]""#),
|
||||||
|
(
|
||||||
|
r#"["\"a\"", "\"b\""]"#,
|
||||||
|
r#""[\"\\\"a\\\"\", \"\\\"b\\\"\"]""#,
|
||||||
|
),
|
||||||
|
(r#"${dontExpandMe}"#, r#""\${dontExpandMe}""#),
|
||||||
|
(r#"\${dontExpandMe}"#, r#""\\\${dontExpandMe}""#),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (orig, quoted) in cases {
|
||||||
|
assert_eq!(quoted, nix_quote(orig));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,11 +5,8 @@ mod tests;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::AsRef;
|
use std::convert::AsRef;
|
||||||
use std::io::Write;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use serde::Serialize;
|
|
||||||
use tempfile::{NamedTempFile, TempPath};
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
@ -17,7 +14,7 @@ use validator::Validate;
|
||||||
use super::deployment::TargetNode;
|
use super::deployment::TargetNode;
|
||||||
use super::{
|
use super::{
|
||||||
Flake, MetaConfig, NixExpression, NixOptions, NodeConfig, NodeFilter, NodeName,
|
Flake, MetaConfig, NixExpression, NixOptions, NodeConfig, NodeFilter, NodeName,
|
||||||
ProfileDerivation, StorePath,
|
ProfileDerivation, SerializedNixExpression, StorePath,
|
||||||
};
|
};
|
||||||
use crate::error::ColmenaResult;
|
use crate::error::ColmenaResult;
|
||||||
use crate::job::JobHandle;
|
use crate::job::JobHandle;
|
||||||
|
@ -60,15 +57,6 @@ struct NixInstantiate<'hive> {
|
||||||
expression: String,
|
expression: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A serialized Nix expression.
|
|
||||||
///
|
|
||||||
/// Very hacky 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 SerializedNixExpression {
|
|
||||||
json_file: TempPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An expression to evaluate the system profiles of selected nodes.
|
/// An expression to evaluate the system profiles of selected nodes.
|
||||||
struct EvalSelectedExpression<'hive> {
|
struct EvalSelectedExpression<'hive> {
|
||||||
hive: &'hive Hive,
|
hive: &'hive Hive,
|
||||||
|
@ -290,7 +278,7 @@ impl Hive {
|
||||||
&self,
|
&self,
|
||||||
nodes: &[NodeName],
|
nodes: &[NodeName],
|
||||||
) -> ColmenaResult<HashMap<NodeName, NodeConfig>> {
|
) -> ColmenaResult<HashMap<NodeName, NodeConfig>> {
|
||||||
let nodes_expr = SerializedNixExpression::new(nodes)?;
|
let nodes_expr = SerializedNixExpression::new(nodes);
|
||||||
|
|
||||||
let configs: HashMap<NodeName, NodeConfig> = self
|
let configs: HashMap<NodeName, NodeConfig> = self
|
||||||
.nix_instantiate(&format!(
|
.nix_instantiate(&format!(
|
||||||
|
@ -322,7 +310,7 @@ impl Hive {
|
||||||
nodes: &[NodeName],
|
nodes: &[NodeName],
|
||||||
job: Option<JobHandle>,
|
job: Option<JobHandle>,
|
||||||
) -> ColmenaResult<HashMap<NodeName, ProfileDerivation>> {
|
) -> ColmenaResult<HashMap<NodeName, ProfileDerivation>> {
|
||||||
let nodes_expr = SerializedNixExpression::new(nodes)?;
|
let nodes_expr = SerializedNixExpression::new(nodes);
|
||||||
|
|
||||||
let expr = format!("hive.evalSelectedDrvPaths {}", nodes_expr.expression());
|
let expr = format!("hive.evalSelectedDrvPaths {}", nodes_expr.expression());
|
||||||
|
|
||||||
|
@ -344,7 +332,7 @@ impl Hive {
|
||||||
|
|
||||||
/// Returns the expression to evaluate selected nodes.
|
/// Returns the expression to evaluate selected nodes.
|
||||||
pub fn eval_selected_expr(&self, nodes: &[NodeName]) -> ColmenaResult<impl NixExpression + '_> {
|
pub fn eval_selected_expr(&self, nodes: &[NodeName]) -> ColmenaResult<impl NixExpression + '_> {
|
||||||
let nodes_expr = SerializedNixExpression::new(nodes)?;
|
let nodes_expr = SerializedNixExpression::new(nodes);
|
||||||
|
|
||||||
Ok(EvalSelectedExpression {
|
Ok(EvalSelectedExpression {
|
||||||
hive: self,
|
hive: self,
|
||||||
|
@ -446,30 +434,6 @@ impl<'hive> NixInstantiate<'hive> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SerializedNixExpression {
|
|
||||||
pub fn new<T>(data: T) -> ColmenaResult<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(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NixExpression for SerializedNixExpression {
|
|
||||||
fn expression(&self) -> String {
|
|
||||||
format!(
|
|
||||||
"(builtins.fromJSON (builtins.readFile {}))",
|
|
||||||
self.json_file.to_str().unwrap()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'hive> NixExpression for EvalSelectedExpression<'hive> {
|
impl<'hive> NixExpression for EvalSelectedExpression<'hive> {
|
||||||
fn expression(&self) -> String {
|
fn expression(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
|
|
|
@ -39,6 +39,9 @@ pub use node_filter::NodeFilter;
|
||||||
|
|
||||||
pub mod evaluator;
|
pub mod evaluator;
|
||||||
|
|
||||||
|
pub mod expression;
|
||||||
|
pub use expression::{NixExpression, SerializedNixExpression};
|
||||||
|
|
||||||
/// Path to the main system profile.
|
/// Path to the main system profile.
|
||||||
pub const SYSTEM_PROFILE: &str = "/nix/var/nix/profiles/system";
|
pub const SYSTEM_PROFILE: &str = "/nix/var/nix/profiles/system";
|
||||||
|
|
||||||
|
@ -104,17 +107,6 @@ pub struct NixOptions {
|
||||||
builders: Option<String>,
|
builders: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Nix expression.
|
|
||||||
pub trait NixExpression: Send + Sync {
|
|
||||||
/// Returns the full Nix expression to be evaluated.
|
|
||||||
fn expression(&self) -> String;
|
|
||||||
|
|
||||||
/// Returns whether this expression requires the use of flakes.
|
|
||||||
fn requires_flakes(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NodeName {
|
impl NodeName {
|
||||||
/// Returns the string.
|
/// Returns the string.
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
|
@ -218,12 +210,6 @@ impl NixOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NixExpression for String {
|
|
||||||
fn expression(&self) -> String {
|
|
||||||
self.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_keys(keys: &HashMap<String, Key>) -> Result<(), ValidationErrorType> {
|
fn validate_keys(keys: &HashMap<String, Key>) -> Result<(), ValidationErrorType> {
|
||||||
// Bad secret names:
|
// Bad secret names:
|
||||||
// - /etc/passwd
|
// - /etc/passwd
|
||||||
|
|
Loading…
Reference in a new issue