greentic_deployer/env_packs/render.rs
1//! Declarative manifest rendering seam (plan §6 step 10).
2//!
3//! [`ManifestRenderer`] is the deployer-side contract behind
4//! `gtc op env render`: it turns an [`Environment`] (plus optional wizard
5//! answers) into the ordered list of declarative manifest documents the
6//! deployer would apply, without applying anything. This is what lets an
7//! operator choose direct apply, GitOps repository handoff, or rendered-
8//! manifest handoff — the rendered artifact and the applied resources come
9//! from the same functions.
10//!
11//! The trait deliberately sits NEXT TO [`Deployer`](super::deployer::Deployer)
12//! rather than on it: rendering only makes sense for deployers whose
13//! desired state is expressible as declarative documents (K8s). Imperative
14//! deployers (local-process, AWS-ECS) simply don't implement it — their
15//! handlers return `None` from
16//! [`EnvPackHandler::as_manifest_renderer`](super::slot::EnvPackHandler::as_manifest_renderer)
17//! and `op env render` reports the kind as non-renderable.
18
19use greentic_deploy_spec::Environment;
20use serde_json::Value;
21
22/// Renders an environment's full declarative desired state.
23///
24/// Contract:
25/// - **Pure and deterministic** — same `(env, answers)` pair, same
26/// documents, same order. No I/O, no provider calls, no clock.
27/// - **Apply order** — consumers may feed the list to `kubectl apply -f`
28/// (or commit it to a GitOps repo) as-is; dependencies come before
29/// dependents (e.g. Namespace before namespaced objects).
30/// - Each [`Value`] is one manifest document following the K8s object
31/// convention (`apiVersion` / `kind` / `metadata.name`).
32/// - The set covers environment-level objects AND per-revision workload
33/// objects for revisions whose persisted lifecycle implies presence in
34/// the desired state — the exact lifecycle policy is the impl's to
35/// define and document.
36pub trait ManifestRenderer: Send + Sync {
37 /// Render the env's declarative desired state, in apply order.
38 ///
39 /// `answers` is the deployer binding's recorded wizard answers as a
40 /// flat JSON object keyed by wizard question id (the ecosystem's
41 /// qa-spec answers convention). `None` when the binding records no
42 /// answers — the impl must fall back to sandbox defaults.
43 fn render_environment(
44 &self,
45 env: &Environment,
46 answers: Option<&serde_json::Value>,
47 ) -> Result<Vec<Value>, RenderError>;
48}
49
50/// Errors from [`ManifestRenderer::render_environment`].
51#[derive(Debug, thiserror::Error)]
52pub enum RenderError {
53 /// The binding's recorded answers are malformed or fail validation.
54 #[error("invalid binding answers: {0}")]
55 InvalidAnswers(String),
56}