From 8f08426ae3a2e5f0ecc5d010bc6469b41e3d7678 Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 14 Nov 2026 00:24:45 +0000 Subject: populate: a command line instead of a spec The program takes populate's arguments on its command line, like other programs: populate [OPTION]... SOURCES [TARGET], with the sources as JSON in the file SOURCES, or on standard input for -. TARGET has the syntax of krops' lib's mkTarget, [USER@][HOST][:PORT][PATH], parsed the same way, and what it leaves out gets filled in when populate runs, as mkTarget would, from LOGNAME and the machine's name. Options set what the syntax can't express, or override its parts: --sudo, --ssh-arg, --user, --host, --port, --path, and --local or --remote for whether the target is the machine populate runs on, which populate otherwise decides like isLocalTarget. The sources carry no version. pkgs.populate passes a target attribute set, as mkTarget returns it, part by part, with --local or --remote as evaluation decides, so nothing changes for krops. A target string goes to the program as it is. The program also refuses target hosts and users starting with -, which ssh would take for options, now that a command line can give them. --- pkgs/populate/src/check.rs | 64 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 10 deletions(-) (limited to 'pkgs/populate/src/check.rs') diff --git a/pkgs/populate/src/check.rs b/pkgs/populate/src/check.rs index cb67865..ebe5dbf 100644 --- a/pkgs/populate/src/check.rs +++ b/pkgs/populate/src/check.rs @@ -1,13 +1,16 @@ -//! Checks of the spec, the same as checks.nix makes during evaluation, so a -//! spec from anywhere else gets checked as well. Messages are the same. +//! Checks of the sources and the target, the same as checks.nix makes during +//! evaluation, so sources from anywhere else get checked as well. Messages +//! are the same. use crate::spec::{Source, Spec}; use serde_json::{Map, Value}; use std::collections::BTreeMap; -/// The spec's checks in the order checks.nix makes them, the first failing -/// one's message. +/// The checks in the order checks.nix makes them, the first failing one's +/// message. The target's host and user, which a command line gives, come +/// first. pub fn spec(spec: &Spec) -> Result<(), String> { + target_host(&spec.target.host, spec.target.user.as_deref())?; target_path(&spec.target.path)?; for (name, source) in &spec.source { source_name(name)?; @@ -16,6 +19,24 @@ pub fn spec(spec: &Spec) -> Result<(), String> { inputs(&spec.source) } +/// The target's host and user, which ssh gets as arguments, mustn't be +/// taken for its options, and the host can't be empty. +fn target_host(host: &str, user: Option<&str>) -> Result<(), String> { + if host.is_empty() || host.starts_with('-') { + return Err(format!( + "krops: target host {} can't be used: it must not be empty or start with -", + quote(host) + )); + } + if user.is_some_and(|u| u.starts_with('-')) { + return Err(format!( + "krops: target user {} can't be used: it must not start with -", + quote(user.unwrap_or_default()) + )); + } + Ok(()) +} + /// What a setting holds. #[derive(Clone, Copy)] enum Kind { @@ -364,15 +385,38 @@ mod tests { ); } + #[test] + fn target_hosts() { + assert!(target_host("host", Some("root")).is_ok()); + assert!(target_host("2001:db8::1", None).is_ok()); + assert_eq!( + target_host("-oProxyCommand=x", None).unwrap_err(), + r#"krops: target host "-oProxyCommand=x" can't be used: it must not be empty or start with -"# + ); + assert!(target_host("", None).is_err()); + assert!(target_host("host", Some("-x")).is_err()); + } + #[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() + Spec { + backup: false, + force: false, + prune: false, + source: serde_json::from_str(&format!( + r#"{{"x": {{"type": "file", "file": {file}}}}}"# + )) + .unwrap(), + target: crate::spec::Target { + user: None, + host: "h".to_string(), + port: None, + path: "/a".to_string(), + sudo: false, + extra_options: vec![], + }, + } }; assert!(super::spec(&spec(r#"{"path": "/src"}"#)).is_ok()); assert!(super::spec(&spec(r#"{"path": "--rsync-path=x"}"#)).is_err()); -- cgit v1.3.1