Skip to main content

greentic_deployer/env_packs/local_process/
deployer.rs

1//! [`Deployer`] impl for the local-process env-pack.
2//!
3//! The local-process deployer has **no provider side**. Bundles live on
4//! the operator's filesystem; `greentic-start` reads them directly; the
5//! in-process dispatcher reads `runtime-config.v1`; nothing is uploaded,
6//! provisioned, or torn down. Every verb on the trait is therefore a
7//! pure-spec precondition check followed by `Ok(...)`.
8//!
9//! This impl is the reference shape Phase D's K8s/AWS/GCP/Azure slices
10//! follow: validate the pure preconditions explicitly (revision exists,
11//! split sums to 10000bps, split exists for the deployment) and ONLY
12//! then perform provider work. Reusing the conformance bench
13//! ([`crate::env_packs::deployer::run_conformance`]) catches a future
14//! deployer that forgets one of the precondition checks.
15
16use async_trait::async_trait;
17use greentic_deploy_spec::{DeploymentId, Environment, RevisionId};
18use serde_json::Value;
19
20use super::LocalProcessDeployerHandler;
21use crate::env_packs::deployer::{
22    ArchiveOutcome, Deployer, DeployerError, DrainOutcome, StageOutcome, TrafficSplitOutcome,
23    WarmOutcome, enforce_split_invariants, require_revision,
24};
25
26#[async_trait]
27impl Deployer for LocalProcessDeployerHandler {
28    async fn stage_revision(
29        &self,
30        env: &Environment,
31        revision_id: RevisionId,
32    ) -> Result<StageOutcome, DeployerError> {
33        require_revision(env, revision_id)?;
34        Ok(StageOutcome::default())
35    }
36
37    async fn warm_revision(
38        &self,
39        env: &Environment,
40        revision_id: RevisionId,
41        _answers: Option<&Value>,
42    ) -> Result<WarmOutcome, DeployerError> {
43        require_revision(env, revision_id)?;
44        Ok(WarmOutcome::default())
45    }
46
47    async fn drain_revision(
48        &self,
49        env: &Environment,
50        revision_id: RevisionId,
51    ) -> Result<DrainOutcome, DeployerError> {
52        require_revision(env, revision_id)?;
53        Ok(DrainOutcome::default())
54    }
55
56    async fn archive_revision(
57        &self,
58        env: &Environment,
59        revision_id: RevisionId,
60        _answers: Option<&Value>,
61    ) -> Result<ArchiveOutcome, DeployerError> {
62        require_revision(env, revision_id)?;
63        Ok(ArchiveOutcome::default())
64    }
65
66    async fn apply_traffic_split(
67        &self,
68        env: &Environment,
69        deployment_id: DeploymentId,
70        _answers: Option<&Value>,
71    ) -> Result<TrafficSplitOutcome, DeployerError> {
72        // No provider side — the in-process dispatcher reads the
73        // runtime-config materialization on its own. The shared helper
74        // enforces the sum-invariant and constructs the outcome.
75        enforce_split_invariants(env, deployment_id)
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use crate::env_packs::deployer::run_conformance;
83
84    #[tokio::test]
85    async fn local_process_passes_conformance() {
86        let handler = LocalProcessDeployerHandler::default();
87        run_conformance(&handler)
88            .await
89            .expect("local-process deployer satisfies the Phase D conformance contract");
90    }
91
92    /// Belt-and-braces: verify the handler exposes its `Deployer` impl
93    /// through the `EnvPackHandler::as_deployer` seam. A future
94    /// refactor that breaks this binding would otherwise leave the
95    /// conformance test passing while the registry returns `None`.
96    #[test]
97    fn handler_exposes_deployer_via_trait_method() {
98        use crate::env_packs::slot::EnvPackHandler;
99        let h = LocalProcessDeployerHandler::default();
100        assert!(
101            (&h as &dyn EnvPackHandler).as_deployer().is_some(),
102            "EnvPackHandler::as_deployer must surface the local-process Deployer impl",
103        );
104    }
105}