diff options
Diffstat (limited to 'pkgs/populate/src')
| -rw-r--r-- | pkgs/populate/src/check.rs | 67 |
1 files changed, 63 insertions, 4 deletions
diff --git a/pkgs/populate/src/check.rs b/pkgs/populate/src/check.rs index 875d852..476cd75 100644 --- a/pkgs/populate/src/check.rs +++ b/pkgs/populate/src/check.rs @@ -8,12 +8,37 @@ use std::collections::BTreeMap; /// one's message. pub fn spec(spec: &Spec) -> Result<(), String> { target_path(&spec.target.path)?; - for name in spec.source.keys() { + for (name, source) in &spec.source { source_name(name)?; + settings(name, source)?; } inputs(&spec.source) } +/// The settings that are paths on the deploying machine, which populate +/// passes to commands, must be absolute, as lib/types/populate.nix demands, +/// so none can be taken for an option. +pub fn settings(name: &str, source: &Source) -> Result<(), String> { + let keys: &[&str] = match source.kind.as_str() { + "file" => &["path"], + "pass" => &["dir"], + "pipe" => &["command"], + _ => &[], + }; + for key in keys { + // A derivation's path is a string in the spec, like other paths. + let value = source.settings.get(*key).and_then(|v| v.as_str()); + if !value.is_some_and(|v| v.starts_with('/')) { + return Err(format!( + "krops: source {name}: the {key} of a {} source must be an absolute path, not {}", + source.kind, + value.map_or("missing".to_string(), quote) + )); + } + } + Ok(()) +} + /// Like JSON, the way Nix' toJSON quotes strings in messages. fn quote(s: &str) -> String { serde_json::to_string(s).expect("strings serialize") @@ -89,17 +114,18 @@ pub fn inputs(sources: &BTreeMap<String, Source>) -> Result<(), String> { } } // Depth-first search, with the path of sources on the way, which repeats - // a source if drawing goes round in circles. + // a source if drawing goes round in circles. The message names the + // whole path, the way into the circle included, like checks.nix. fn visit<'a>( name: &'a str, drawn_from: &'a BTreeMap<&str, Vec<String>>, done: &mut Vec<&'a str>, path: &mut Vec<&'a str>, ) -> Result<(), String> { - if let Some(i) = path.iter().position(|p| *p == name) { + if path.contains(&name) { return Err(format!( "krops: sources draw inputs from each other: {}", - path[i..].join(" ") + path.join(" ") )); } if done.contains(&name) { @@ -206,4 +232,37 @@ mod tests { "krops: sources draw inputs from each other: a b" ); } + + #[test] + fn settings() { + let spec = |file: &str| -> Spec { + serde_json::from_str(&format!( + r#"{{"version": 1, "target": {{"host": "h", "path": "/a"}}, + "source": {{"x": {{"type": "file", "file": {}}}}}}}"#, + file + )) + .unwrap() + }; + assert!(super::spec(&spec(r#"{"path": "/src"}"#)).is_ok()); + assert!(super::spec(&spec(r#"{"path": "--rsync-path=x"}"#)).is_err()); + assert_eq!( + super::spec(&spec(r#"{"path": "relative"}"#)).unwrap_err(), + r#"krops: source x: the path of a file source must be an absolute path, not "relative""# + ); + } + + #[test] + fn cycle_reached_from_another_source() { + let cycle = sources( + r#"{ + "a": {"type": "file", "file": {"path": "/a", "inputs": {"i": {"follows": "b"}}}}, + "b": {"type": "file", "file": {"path": "/b", "inputs": {"i": {"follows": "c"}}}}, + "c": {"type": "file", "file": {"path": "/c", "inputs": {"i": {"follows": "b"}}}} + }"#, + ); + assert_eq!( + inputs(&cycle).unwrap_err(), + "krops: sources draw inputs from each other: a b c" + ); + } } |
