From af53e0dee3e1c38c907b92733239b1cca448e0b0 Mon Sep 17 00:00:00 2001 From: tv Date: Mon, 26 Oct 2026 05:30:09 +0100 Subject: allow force only in args, not in files --- src/config/mod.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/config/mod.rs b/src/config/mod.rs index 0aaccd6..b8a2ba0 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -122,7 +122,7 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[ Kind::List, "NAME,[NAME...]", None, - "Override checks that otherwise refuse to start: true for all, or names: swarmId (a document to start on is of another swarm than the state file remembers), badDocumentFile (an unusable document file: moved aside, start without it), replaceDocumentFile (recover-from while the document file holds a document).", + "Override checks that otherwise refuse to start (command line only: it is for one start): true for all, or names: swarmId (a document to start on is of another swarm than the state file remembers), badDocumentFile (an unusable document file: moved aside, start without it), replaceDocumentFile (recover-from while the document file holds a document).", ), option( "recover-from", @@ -902,6 +902,16 @@ pub(crate) fn load() -> Result { } fn resolve(merged: Layer, files: Vec) -> Result { + // An override is for the start it was given for; in a file it would silently apply + // to every start, e.g. replacing the document file with a backup each time. + if let Some(entry) = merged.get("force").filter(|entry| { + !entry.value.is_null() && matches!(entry.origin.provenance, Provenance::File(_)) + }) { + bail!( + "force can only be given on the command line: it overrides checks for one start, and in a file ({}) it would apply to every start", + entry.origin + ); + } // Unset options take their defaults. let object: serde_json::Map = merged .iter() @@ -957,6 +967,26 @@ mod tests { .collect() } + #[test] + fn force_only_on_the_command_line() { + let from = |provenance| Entry { + value: json!(["swarmId"]), + origin: Origin { + provenance, + unset: false, + imports: Vec::new(), + }, + }; + let mut merged = layer(json!({ "htpasswd": "u" })); + merged.insert( + "force".to_owned(), + from(Provenance::File(PathBuf::from("continuum.toml"))), + ); + assert!(resolve(merged.clone(), Vec::new()).is_err()); + merged.insert("force".to_owned(), from(Provenance::Argument)); + assert!(resolve(merged, Vec::new()).is_ok()); + } + #[test] fn options_and_settings_agree() { // Every option is a Settings field, and every field an option. -- cgit v1.3.1