From c199391e3cd2dddb74bd318fb185f875a5d72d9d Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 16 Jul 2026 10:58:05 +0200 Subject: [PATCH 1/3] Propagate policy reload warnings to the CLI --- crates/api/src/lib.rs | 1 + crates/cli/src/commands/policy.rs | 9 +++++++++ src/main.rs | 7 +++++++ src/policy/mod.rs | 24 ++++++++++++++++++++---- src/units/http_server.rs | 4 +++- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 108fa6ab..37dc394c 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -844,6 +844,7 @@ impl Display for PolicyReloadError { #[derive(Deserialize, Serialize, Debug, Clone)] pub struct PolicyChanges { pub changes: Vec<(String, PolicyChange)>, + pub warnings: Vec, } #[derive(Deserialize, Serialize, Debug, Clone)] diff --git a/crates/cli/src/commands/policy.rs b/crates/cli/src/commands/policy.rs index a51e9354..73153540 100644 --- a/crates/cli/src/commands/policy.rs +++ b/crates/cli/src/commands/policy.rs @@ -4,6 +4,7 @@ use cascade_api::{ AutoConfigPolicyInfo, KeyManagerPolicyInfo, LoaderPolicyInfo, ReviewPolicyMode, ServerPolicyInfo, SignerPolicyInfo, }; +use tracing::warn; use crate::{ ansi, @@ -102,6 +103,14 @@ impl Policy { reset = ansi::RESET ); } + + if !res.warnings.is_empty() { + println!(); + } + + for w in res.warnings { + warn!("{w}"); + } } } Ok(()) diff --git a/src/main.rs b/src/main.rs index 9a205110..0afb77aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -223,6 +223,8 @@ fn main() -> ExitCode { } } + let mut warnings = Vec::new(); + // Load all policies. let mut updates = Vec::new(); let res = policy::reload_all( @@ -232,8 +234,13 @@ fn main() -> ExitCode { |name, _| { updates.push(name.clone()); }, + &mut warnings, ); + // We don't need to use the warnings as they have already + // been logged. + drop(warnings); + if let Err(err) = res { error!("Cascade couldn't load all policies: {err}"); return ExitCode::FAILURE; diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 01ca1d34..b83d8fdd 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -11,7 +11,7 @@ use domain::base::Name; use domain::base::Ttl; use domain::tsig::KeyName; use serde::{Deserialize, Serialize}; -use tracing::{debug, error, info, warn}; +use tracing::{info, warn}; use crate::tsig::TsigStore; use crate::{api::PolicyReloadError, config::Config}; @@ -61,8 +61,9 @@ pub fn reload_all( config: &Config, tsig_store: &TsigStore, mut on_change: impl FnMut(&Box, PolicyChange), + warnings: &mut Vec, ) -> Result<(), PolicyReloadError> { - let new_versions = load_all(policies, config, tsig_store)?; + let new_versions = load_all(policies, config, tsig_store, warnings)?; let mut new_policies = foldhash::HashMap::default(); @@ -96,9 +97,12 @@ pub fn reload_all( for (name, policy) in policies.drain() { // If any zones are using this policy, keep it. if !policy.zones.is_empty() { - error!( + warn!( "The file backing policy '{name}' has been removed, but some zones are still using it; Cascade will preserve its internal copy" ); + warnings.push(format!( + "The file backing policy '{name}' has been removed, but some zones are still using it; Cascade will preserve its internal copy" + )); let prev = new_policies.insert(name, policy); assert!( prev.is_none(), @@ -131,6 +135,7 @@ pub fn load_all( policies: &foldhash::HashMap, Policy>, config: &Config, tsig_store: &TsigStore, + warnings: &mut Vec, ) -> Result, PolicyVersion>, PolicyReloadError> { // Write the loaded policies to a new hashmap, so policies that no longer // exist can be detected easily. @@ -149,6 +154,10 @@ pub fn load_all( "Ignoring potential policy '{}' as the path is non-UTF-8", entry.path().display() ); + warnings.push(format!( + "Ignoring potential policy '{}' as the path is non-UTF-8", + entry.path().display() + )); continue; }; @@ -158,7 +167,8 @@ pub fn load_all( .expect("this path has a known parent directory") .starts_with('.') { - debug!("Ignoring hidden file '{path}' among policies"); + warn!("Ignoring hidden file '{path}' among policies"); + warnings.push(format!("Ignoring hidden file '{path}' among policies")); continue; } @@ -168,6 +178,9 @@ pub fn load_all( .is_none_or(|e| !e.eq_ignore_ascii_case("toml")) { warn!("Ignoring potential policy '{path}'; policies must end in '.toml'"); + warnings.push(format!( + "Ignoring potential policy '{path}'; policies must end in '.toml'" + )); continue; } @@ -180,6 +193,9 @@ pub fn load_all( // Ignore a directory ending in '.toml'. Err(err) if err.kind() == io::ErrorKind::IsADirectory => { warn!("Ignoring potential policy '{path}'; policies must be files"); + warnings.push(format!( + "Ignoring potential policy '{path}'; policies must be files" + )); continue; } Err(err) => return Err(PolicyReloadError::Io(path, err.to_string())), diff --git a/src/units/http_server.rs b/src/units/http_server.rs index c624f4e4..2a92c9af 100644 --- a/src/units/http_server.rs +++ b/src/units/http_server.rs @@ -905,6 +905,7 @@ impl HttpServer { .collect::>(); let mut changed = false; let mut updates = Vec::new(); + let mut warnings = Vec::new(); let res = crate::policy::reload_all( &mut state.policies, ¢er.config, @@ -923,6 +924,7 @@ impl HttpServer { updates.push((name.clone(), change)); }, + &mut warnings, ); if let Err(err) = res { @@ -971,7 +973,7 @@ impl HttpServer { changes.into_iter().map(|(p, c)| (p.into(), c)).collect(); changes.sort_unstable_by(|l, r| l.0.cmp(&r.0)); - Json(Ok(PolicyChanges { changes })) + Json(Ok(PolicyChanges { changes, warnings })) } async fn policy_show( From d1915cb1f29abb47122bf360167052a474ff0217 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 16 Jul 2026 11:19:03 +0200 Subject: [PATCH 2/3] Improve policy reload warnings a bit --- crates/cli/src/commands/policy.rs | 2 +- src/main.rs | 6 +++--- src/policy/mod.rs | 12 +----------- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/crates/cli/src/commands/policy.rs b/crates/cli/src/commands/policy.rs index 73153540..f7c5e3dc 100644 --- a/crates/cli/src/commands/policy.rs +++ b/crates/cli/src/commands/policy.rs @@ -109,7 +109,7 @@ impl Policy { } for w in res.warnings { - warn!("{w}"); + println!("{}WARNING{}: {w}", ansi::YELLOW, ansi::RESET); } } } diff --git a/src/main.rs b/src/main.rs index 0afb77aa..16cd2aa3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -237,9 +237,9 @@ fn main() -> ExitCode { &mut warnings, ); - // We don't need to use the warnings as they have already - // been logged. - drop(warnings); + for w in warnings { + warn!("{w}"); + } if let Err(err) = res { error!("Cascade couldn't load all policies: {err}"); diff --git a/src/policy/mod.rs b/src/policy/mod.rs index b83d8fdd..37ac61f9 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -11,7 +11,7 @@ use domain::base::Name; use domain::base::Ttl; use domain::tsig::KeyName; use serde::{Deserialize, Serialize}; -use tracing::{info, warn}; +use tracing::info; use crate::tsig::TsigStore; use crate::{api::PolicyReloadError, config::Config}; @@ -97,9 +97,6 @@ pub fn reload_all( for (name, policy) in policies.drain() { // If any zones are using this policy, keep it. if !policy.zones.is_empty() { - warn!( - "The file backing policy '{name}' has been removed, but some zones are still using it; Cascade will preserve its internal copy" - ); warnings.push(format!( "The file backing policy '{name}' has been removed, but some zones are still using it; Cascade will preserve its internal copy" )); @@ -150,10 +147,6 @@ pub fn load_all( // Filter for UTF-8 paths. let Ok(path) = Utf8PathBuf::from_path_buf(entry.path()) else { - warn!( - "Ignoring potential policy '{}' as the path is non-UTF-8", - entry.path().display() - ); warnings.push(format!( "Ignoring potential policy '{}' as the path is non-UTF-8", entry.path().display() @@ -167,7 +160,6 @@ pub fn load_all( .expect("this path has a known parent directory") .starts_with('.') { - warn!("Ignoring hidden file '{path}' among policies"); warnings.push(format!("Ignoring hidden file '{path}' among policies")); continue; } @@ -177,7 +169,6 @@ pub fn load_all( .extension() .is_none_or(|e| !e.eq_ignore_ascii_case("toml")) { - warn!("Ignoring potential policy '{path}'; policies must end in '.toml'"); warnings.push(format!( "Ignoring potential policy '{path}'; policies must end in '.toml'" )); @@ -192,7 +183,6 @@ pub fn load_all( Ok(spec) => spec, // Ignore a directory ending in '.toml'. Err(err) if err.kind() == io::ErrorKind::IsADirectory => { - warn!("Ignoring potential policy '{path}'; policies must be files"); warnings.push(format!( "Ignoring potential policy '{path}'; policies must be files" )); From 8f6f361ef720ab64dbbe84a1f395bcc5dcffdce8 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 16 Jul 2026 12:44:58 +0200 Subject: [PATCH 3/3] Fix clippy warning --- crates/cli/src/commands/policy.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/cli/src/commands/policy.rs b/crates/cli/src/commands/policy.rs index f7c5e3dc..fbb3c820 100644 --- a/crates/cli/src/commands/policy.rs +++ b/crates/cli/src/commands/policy.rs @@ -4,7 +4,6 @@ use cascade_api::{ AutoConfigPolicyInfo, KeyManagerPolicyInfo, LoaderPolicyInfo, ReviewPolicyMode, ServerPolicyInfo, SignerPolicyInfo, }; -use tracing::warn; use crate::{ ansi,