summaryrefslogtreecommitdiffstats
path: root/src/config/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/config/mod.rs')
-rw-r--r--src/config/mod.rs90
1 files changed, 89 insertions, 1 deletions
diff --git a/src/config/mod.rs b/src/config/mod.rs
index f042fb8..e62b2b6 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -436,6 +436,7 @@ fn culprit(merged: &Layer) -> Option<(&String, &Entry)> {
// --- command line --------------------------------------------------------------
const CONFIG: &str = "config";
+const PRINT_CONFIG: &str = "print-config";
fn command() -> Command {
let mut command = Command::new("continuum")
@@ -453,6 +454,12 @@ fn command() -> Command {
.value_name("PATH")
.action(ArgAction::Append)
.help("Configuration file (.toml or .json); repeatable, later files win."),
+ )
+ .arg(
+ Arg::new(PRINT_CONFIG)
+ .long(PRINT_CONFIG)
+ .action(ArgAction::SetTrue)
+ .help("Print the effective configuration as JSON (with each value's source) and exit."),
);
for spec in OPTIONS {
let help = match spec.default {
@@ -529,12 +536,69 @@ pub(crate) struct Loaded {
pub(crate) settings: Settings,
pub(crate) origins: BTreeMap<String, Origin>,
pub(crate) files: Vec<PathBuf>,
+ /// `--print-config`: print [`Loaded::report`] and exit.
+ pub(crate) print_only: bool,
+}
+
+impl Loaded {
+ /// The effective configuration with each value's source, for `--print-config` and
+ /// `GET /admin/config`. Imported values are left out (they may be secret); their
+ /// importers are listed instead.
+ pub(crate) fn report(&self) -> JsonValue {
+ let values = match serde_json::to_value(&self.settings) {
+ Ok(JsonValue::Object(values)) => values,
+ _ => serde_json::Map::new(),
+ };
+ let options: serde_json::Map<String, JsonValue> = OPTIONS
+ .iter()
+ .filter(|spec| !spec.hidden || self.origins.contains_key(spec.name))
+ .map(|spec| {
+ let mut entry = serde_json::Map::new();
+ match self.origins.get(spec.name) {
+ None => {
+ entry.insert("source".into(), "default".into());
+ }
+ Some(origin) => {
+ match &origin.provenance {
+ Provenance::File(path) => {
+ entry.insert("source".into(), "file".into());
+ entry.insert("file".into(), path.display().to_string().into());
+ }
+ Provenance::Argument => {
+ entry.insert("source".into(), "argument".into());
+ }
+ }
+ if origin.is_imported() {
+ entry.insert("imported".into(), origin.imports.clone().into());
+ }
+ }
+ }
+ let imported = self.origins.get(spec.name).is_some_and(Origin::is_imported);
+ if !imported {
+ entry.insert(
+ "value".into(),
+ values.get(spec.name).cloned().unwrap_or(JsonValue::Null),
+ );
+ }
+ (spec.name.to_owned(), JsonValue::Object(entry))
+ })
+ .collect();
+ serde_json::json!({
+ "files": self.files.iter().map(|path| path.display().to_string()).collect::<Vec<_>>(),
+ "options": options,
+ })
+ }
}
/// Reads the command line and the configuration files it names. Exits on `--help`,
/// `--version` and command-line syntax errors, like any command.
pub(crate) fn load() -> Result<Loaded> {
let matches = command().get_matches();
+ let print_only = matches.get_flag(PRINT_CONFIG);
+ if print_only {
+ // Logs go to stdout too; the report says where each value came from anyway.
+ let _ = crate::logging::set_filter("off");
+ }
let files: Vec<PathBuf> = matches
.get_many::<String>(CONFIG)
.into_iter()
@@ -568,7 +632,9 @@ pub(crate) fn load() -> Result<Loaded> {
}
merged.insert(name, entry);
}
- resolve(merged, files)
+ let mut loaded = resolve(merged, files)?;
+ loaded.print_only = print_only;
+ Ok(loaded)
}
fn resolve(merged: Layer, files: Vec<PathBuf>) -> Result<Loaded> {
@@ -595,6 +661,7 @@ fn resolve(merged: Layer, files: Vec<PathBuf>) -> Result<Loaded> {
.map(|(name, entry)| (name, entry.origin))
.collect(),
files,
+ print_only: false,
})
}
@@ -651,6 +718,27 @@ mod tests {
}
#[test]
+ fn report_hides_imported_values() {
+ let mut values = layer(json!({ "htpasswd": "users", "alias": "secret-ish" }));
+ values.get_mut("alias").unwrap().origin.imports = vec!["env:HOSTNAME".into()];
+ let report = resolve(values, Vec::new()).unwrap().report();
+ let options = &report["options"];
+ assert_eq!(
+ options["htpasswd"],
+ json!({ "source": "argument", "value": "users" })
+ );
+ assert_eq!(
+ options["alias"],
+ json!({ "source": "argument", "imported": ["env:HOSTNAME"] })
+ );
+ assert_eq!(
+ options["neighbours"],
+ json!({ "source": "default", "value": 12 })
+ );
+ assert!(options.get("debug-epoch").is_none());
+ }
+
+ #[test]
fn text_values() {
assert_eq!(from_text(Kind::Integer, " 30 ").unwrap(), json!(30));
assert!(from_text(Kind::Integer, "-1").is_err());