Four review fixes on the `pic` surface: - `config --effective` gains `--env`: it now resolves through the same overlay as `plan`/`apply`, so the masked report targets the app those commands act on instead of silently reading the base slug's secrets. - `pull` gains `--force` and refuses to overwrite an existing `picloud.toml` (and colliding `scripts/*.rhai`) without it — fail-fast before any network call or write, mirroring `init`. - Overlays (`ManifestOverlay`/`OverlayApp`) get `deny_unknown_fields`: a `[[scripts]]`/`[[routes]]`/`[[triggers]]` table or a typo'd key in an overlay now errors loudly instead of being silently dropped. +unit test. - `apply` 409 (StateMoved — the only 409 the endpoint emits) surfaces an actionable hint (re-run `pic plan`, or `--force`) instead of a bare `HTTP 409`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
59 lines
2.2 KiB
Rust
59 lines
2.2 KiB
Rust
//! `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 `<set>` / `<unset>`. 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<String> = manifest.secrets.names.iter().cloned().collect();
|
|
let on_server: BTreeSet<String> = 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) => ("<set>", "managed"),
|
|
(true, false) => ("<unset>", "declared, not pushed — `pic secret set`"),
|
|
(false, true) => ("<set>", "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(())
|
|
}
|