feat(manifest): optional secret_env entries
A secret_env entry with optional: true is skipped when its secret file is missing, unreadable, or empty, instead of failing the whole resolution and taking the app down. Needed for per-node integrations like btcpay's internal-LND connection string: nodes without LND must still run btcpay. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
140b703838
commit
bb0875f7f2
@ -279,6 +279,12 @@ pub struct DerivedEnv {
|
||||
pub struct SecretEnv {
|
||||
pub key: String,
|
||||
pub secret_file: String,
|
||||
/// When true, a missing/unreadable/empty secret skips this entry instead
|
||||
/// of failing the whole resolution. For integrations that exist on some
|
||||
/// nodes only (btcpay's internal-LND connection string: nodes without
|
||||
/// LND must still run btcpay, just without the internal node).
|
||||
#[serde(default)]
|
||||
pub optional: bool,
|
||||
}
|
||||
|
||||
/// A fully resolved secret env entry, produced at apply time. `value` lives
|
||||
@ -1353,10 +1359,18 @@ impl ContainerConfig {
|
||||
) -> Result<Vec<(String, String)>, ManifestError> {
|
||||
let mut out = Vec::with_capacity(self.secret_env.len());
|
||||
for e in &self.secret_env {
|
||||
let v = provider.read(&e.secret_file)?;
|
||||
let v = match provider.read(&e.secret_file) {
|
||||
Ok(v) => v,
|
||||
Err(_) if e.optional => continue,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
// An empty secret produces e.g. `-rpcpassword=` and crashes
|
||||
// the container on auth before logs are useful. Fail loud.
|
||||
// the container on auth before logs are useful. Fail loud —
|
||||
// unless the entry is optional, where empty means "not set".
|
||||
if v.trim().is_empty() {
|
||||
if e.optional {
|
||||
continue;
|
||||
}
|
||||
return Err(ManifestError::Invalid(format!(
|
||||
"secret_env {} resolved to empty value (file: {})",
|
||||
e.key, e.secret_file
|
||||
@ -2034,10 +2048,12 @@ app:
|
||||
SecretEnv {
|
||||
key: "FM_BITCOIND_PASSWORD".to_string(),
|
||||
secret_file: "bitcoin-rpc-password".to_string(),
|
||||
optional: false,
|
||||
},
|
||||
SecretEnv {
|
||||
key: "FM_GATEWAY_PASSWORD".to_string(),
|
||||
secret_file: "fedimint-gateway-password".to_string(),
|
||||
optional: false,
|
||||
},
|
||||
],
|
||||
generated_secrets: vec![],
|
||||
@ -2079,6 +2095,7 @@ app:
|
||||
secret_env: vec![SecretEnv {
|
||||
key: "BITCOIN_RPC_PASS".to_string(),
|
||||
secret_file: "bitcoin-rpc-password".to_string(),
|
||||
optional: false,
|
||||
}],
|
||||
generated_secrets: vec![],
|
||||
generated_certs: vec![],
|
||||
@ -2099,6 +2116,51 @@ app:
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_secret_env_skips_missing_or_empty_optional_entries() {
|
||||
let c = ContainerConfig {
|
||||
image: Some("x:latest".to_string()),
|
||||
image_signature: None,
|
||||
pull_policy: "if-not-present".to_string(),
|
||||
build: None,
|
||||
network: None,
|
||||
network_aliases: vec![],
|
||||
custom_args: vec![],
|
||||
entrypoint: None,
|
||||
derived_env: vec![],
|
||||
secret_env: vec![
|
||||
SecretEnv {
|
||||
key: "REQUIRED".to_string(),
|
||||
secret_file: "present".to_string(),
|
||||
optional: false,
|
||||
},
|
||||
SecretEnv {
|
||||
key: "OPT_MISSING".to_string(),
|
||||
secret_file: "does-not-exist".to_string(),
|
||||
optional: true,
|
||||
},
|
||||
SecretEnv {
|
||||
key: "OPT_EMPTY".to_string(),
|
||||
secret_file: "empty".to_string(),
|
||||
optional: true,
|
||||
},
|
||||
],
|
||||
generated_secrets: vec![],
|
||||
generated_certs: vec![],
|
||||
data_uid: None,
|
||||
secret_env_refs: vec![],
|
||||
secret_env_hash: None,
|
||||
};
|
||||
let p = MapSecretsProvider {
|
||||
data: HashMap::from([
|
||||
("present".to_string(), "value".to_string()),
|
||||
("empty".to_string(), " \n".to_string()),
|
||||
]),
|
||||
};
|
||||
let out = c.resolve_secret_env(&p).unwrap();
|
||||
assert_eq!(out, vec!["REQUIRED=value".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsafe_manifest_values_are_rejected() {
|
||||
let cases = [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user