feat: v0.7.0 — secrets partagés par provider : am providers set-token/token, namespace keyring providers/<nom>, résolution @secret en cascade agent → provider (closes #89)
Corrige au passage deux bugs du keyring (#36) : features natifs manquants du crate keyring (set() no-op silencieux, rien ne persistait) + index __index__ jamais purgé au remove (list fantômes).
This commit is contained in:
@@ -1,34 +1,65 @@
|
||||
//! secret: manage secrets in the OS keyring (values never shown or logged).
|
||||
//! Secrets are scoped to an agent (`agent/NAME`, issue #36) or to a provider
|
||||
//! (`providers/<provider>/<VAR>`, issue #89) — the provider namespace is
|
||||
//! shared by every agent of that provider.
|
||||
|
||||
use super::*;
|
||||
use crate::cli::SecretCmd;
|
||||
use crate::output::print_json;
|
||||
use crate::secrets::{self, SecretStore};
|
||||
|
||||
/// Resolve the keyring key and the display scope for a secret name.
|
||||
/// Exactly one of (agent, provider) must be given.
|
||||
fn scope_key(
|
||||
name: &str,
|
||||
agent: Option<&str>,
|
||||
provider: Option<&str>,
|
||||
) -> Result<(String, String)> {
|
||||
match (provider, agent) {
|
||||
(Some(p), _) => Ok((secrets::key_for_provider(p, name), format!("provider {p}"))),
|
||||
(None, Some(a)) => Ok((secrets::key_for(a, name), format!("agent {a}"))),
|
||||
(None, None) => bail!(
|
||||
"{}",
|
||||
crate::i18n::tr(
|
||||
"usage: secret set <nom> --agent <agent> --value <valeur> | secret set <nom> --provider <provider> --value <valeur>"
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(app: &App, sub: &SecretCmd) -> Result<i32> {
|
||||
match sub {
|
||||
SecretCmd::Set { name, agent, value } => {
|
||||
SecretCmd::Set {
|
||||
name,
|
||||
agent,
|
||||
provider,
|
||||
value,
|
||||
} => {
|
||||
let (key, scope) = scope_key(name, agent.as_deref(), provider.as_deref())?;
|
||||
if app.dry_run() {
|
||||
app.log.dry(format!("would store secret {name} for {agent} in the OS keyring"));
|
||||
app.log.dry(format!("would store secret {name} for {scope} in the OS keyring"));
|
||||
return Ok(0);
|
||||
}
|
||||
let key = secrets::key_for(agent, name);
|
||||
secrets::store().set(&key, value)?;
|
||||
app.log.success(&format!("secret {name} stored for {agent}"));
|
||||
app.log.success(&crate::tr_fmt!("secret '{}' stocké pour {}", name, scope));
|
||||
app.log.info("use it with --env NAME=@secret (injected at start/run)");
|
||||
Ok(0)
|
||||
}
|
||||
SecretCmd::Unset { name, agent } => {
|
||||
let key = secrets::key_for(agent, name);
|
||||
SecretCmd::Unset {
|
||||
name,
|
||||
agent,
|
||||
provider,
|
||||
} => {
|
||||
let (key, scope) = scope_key(name, agent.as_deref(), provider.as_deref())?;
|
||||
if app.dry_run() {
|
||||
app.log.dry(format!("would remove secret {key}"));
|
||||
return Ok(0);
|
||||
}
|
||||
let removed = secrets::store().remove(&key)?;
|
||||
if removed {
|
||||
app.log.success(&format!("secret {name} removed for {agent}"));
|
||||
app.log.success(&crate::tr_fmt!("secret '{}' supprimé pour {}", name, scope));
|
||||
} else {
|
||||
app.log.info(&format!("secret {name} does not exist for {agent}"));
|
||||
app.log.info(&crate::tr_fmt!("secret '{}' inexistant pour {}", name, scope));
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
@@ -42,6 +73,7 @@ pub fn run(app: &App, sub: &SecretCmd) -> Result<i32> {
|
||||
app.log.info("no secrets stored — see: am secret set NAME --agent AGENT --value ...");
|
||||
return Ok(0);
|
||||
}
|
||||
// Both namespaces appear here: agent/NAME and providers/<p>/<VAR>.
|
||||
let mut table = crate::output::Table::new(vec!["SECRET"]);
|
||||
for k in all {
|
||||
if k != "__index__" {
|
||||
|
||||
Reference in New Issue
Block a user