From cc626d686cceed84e45d21bf32514a3a3f8e2b11 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Wed, 4 Jan 2023 13:38:28 +0100 Subject: feat(tvix/derivation): implement Derivation::validate() Change-Id: I87dfadda872439e108e5f678a5da63dd5b1915d1 Reviewed-on: https://cl.tvl.fyi/c/depot/+/7732 Reviewed-by: tazjin Tested-by: BuildkiteCI --- tvix/derivation/src/lib.rs | 4 +- tvix/derivation/src/output.rs | 6 +++ tvix/derivation/src/tests/mod.rs | 10 ++++ tvix/derivation/src/validate.rs | 104 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 tvix/derivation/src/validate.rs (limited to 'tvix/derivation/src') diff --git a/tvix/derivation/src/lib.rs b/tvix/derivation/src/lib.rs index a8360fbafc5e..f0a654e111a2 100644 --- a/tvix/derivation/src/lib.rs +++ b/tvix/derivation/src/lib.rs @@ -1,9 +1,9 @@ +mod derivation; mod nix_hash; mod output; mod string_escape; +mod validate; mod write; -mod derivation; - #[cfg(test)] mod tests; diff --git a/tvix/derivation/src/output.rs b/tvix/derivation/src/output.rs index 0d668c3e9952..2839110971d2 100644 --- a/tvix/derivation/src/output.rs +++ b/tvix/derivation/src/output.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use tvix_store::nixpath::NixPath; #[derive(Serialize, Deserialize)] pub struct Output { @@ -20,4 +21,9 @@ impl Output { pub fn is_fixed(&self) -> bool { self.hash.is_some() } + + pub fn validate(&self) -> anyhow::Result<()> { + NixPath::from_absolute_path(&self.path)?; + Ok(()) + } } diff --git a/tvix/derivation/src/tests/mod.rs b/tvix/derivation/src/tests/mod.rs index 435d82144067..1b55c6e3e751 100644 --- a/tvix/derivation/src/tests/mod.rs +++ b/tvix/derivation/src/tests/mod.rs @@ -30,6 +30,16 @@ fn check_serizaliation(path_to_drv_file: &str) { assert_eq!(expected, serialized_derivation); } +#[test_resources("src/tests/derivation_tests/*.drv")] +fn validate(path_to_drv_file: &str) { + let data = read_file(&format!("{}.json", path_to_drv_file)); + let derivation: Derivation = serde_json::from_str(&data).expect("JSON was not well-formatted"); + + derivation + .validate() + .expect("derivation failed to validate") +} + #[test_resources("src/tests/derivation_tests/*.drv")] fn check_to_string(path_to_drv_file: &str) { let data = read_file(&format!("{}.json", path_to_drv_file)); diff --git a/tvix/derivation/src/validate.rs b/tvix/derivation/src/validate.rs new file mode 100644 index 000000000000..e0f62a323fcf --- /dev/null +++ b/tvix/derivation/src/validate.rs @@ -0,0 +1,104 @@ +use crate::{derivation::Derivation, write::DOT_FILE_EXT}; +use anyhow::bail; +use tvix_store::nixpath::NixPath; + +impl Derivation { + /// validate ensures a Derivation struct is properly populated, + /// and returns an error if not. + /// TODO(flokli): make this proper errors + pub fn validate(&self) -> anyhow::Result<()> { + // Ensure the number of outputs is > 1 + if self.outputs.is_empty() { + bail!("0 outputs"); + } + + // Validate all outputs + for (output_name, output) in &self.outputs { + if output_name.is_empty() { + bail!("output_name from outputs may not be empty") + } + + if output.is_fixed() { + if self.outputs.len() != 1 { + bail!("encountered fixed-output, but there's more than 1 output in total"); + } + if output_name != "out" { + bail!("the fixed-output output name must be called 'out'"); + } + + break; + } + + output.validate()?; + } + + // Validate all input_derivations + for (input_derivation_path, output_names) in &self.input_derivations { + // Validate input_derivation_path + NixPath::from_absolute_path(input_derivation_path)?; + if !input_derivation_path.ends_with(DOT_FILE_EXT) { + bail!( + "derivation {} does not end with .drv", + input_derivation_path + ); + } + + if output_names.is_empty() { + bail!( + "output_names list for {} may not be empty", + input_derivation_path + ); + } + + for (i, output_name) in output_names.iter().enumerate() { + if output_name.is_empty() { + bail!( + "output name entry for {} may not be empty", + input_derivation_path + ) + } + // if i is at least 1, peek at the previous element to ensure output_names are sorted. + if i > 0 && (output_names[i - 1] >= *output_name) { + bail!( + "invalid input derivation output order: {} < {}", + output_name, + output_names[i - 1], + ); + } + } + } + + // Validate all input_sources + for (i, input_source) in self.input_sources.iter().enumerate() { + NixPath::from_absolute_path(input_source)?; + + if i > 0 && self.input_sources[i - 1] >= *input_source { + bail!( + "invalid input source order: {} < {}", + input_source, + self.input_sources[i - 1], + ); + } + } + + // validate platform + if self.system.is_empty() { + bail!("required attribute 'platform' missing"); + } + + // validate builder + if self.builder.is_empty() { + bail!("required attribute 'builder' missing"); + } + + // validate env, none of the keys may be empty. + // We skip the `name` validation seen in go-nix. + for k in self.environment.keys() { + if k.is_empty() { + bail!("found empty environment variable key"); + } + } + + Ok(()) + } +} -- cgit 1.4.1