//! `pic config --effective` — read-only view of an app's resolved //! configuration, with secret values masked (§4.6). //! //! Single-app today: "config" means the app's secrets, cross-referenced //! against the manifest so an operator can see at a glance which declared //! secrets are still unset and which live secrets aren't declared. Values are //! never fetched or shown — only `` / ``. The command is shaped to //! grow an `--explain` mode and inherited `vars` once groups/vars land (the //! Phase-3 multi-level resolution). use std::collections::BTreeSet; use std::path::Path; use anyhow::{bail, Result}; use crate::client::Client; use crate::config; use crate::manifest::Manifest; use crate::output::{OutputMode, Table}; pub async fn run( manifest_path: &Path, effective: bool, env: Option<&str>, mode: OutputMode, ) -> Result<()> { if !effective { bail!("`pic config` currently supports only `--effective`"); } let creds = config::resolve()?; let client = Client::from_creds(&creds)?; // Resolve against the same env overlay as `pic plan`/`apply` so the // masked report targets the app those commands would act on — not the // base slug — when an overlay re-points slug/secrets. let manifest = Manifest::load_with_env(manifest_path, env)?; let declared: BTreeSet = manifest.secrets.names.iter().cloned().collect(); let on_server: BTreeSet = client .secrets_list(&manifest.app.slug) .await? .secrets .into_iter() .map(|s| s.name) .collect(); let mut table = Table::new(["secret", "value", "status"]); for name in declared.union(&on_server) { let (value, status) = match (declared.contains(name), on_server.contains(name)) { (true, true) => ("", "managed"), (true, false) => ("", "declared, not pushed — `pic secret set`"), (false, true) => ("", "on server, not in manifest"), (false, false) => unreachable!("name came from one of the two sets"), }; table.row([name.clone(), value.to_string(), status.to_string()]); } table.print(mode); Ok(()) }