Skip to main content

greentic_deployer/env_packs/local_process/
mod.rs

1//! Local-process deployer env-pack (C2 reference impl).
2//!
3//! Backs the default `local` env's `Deployer` slot binding
4//! (`greentic.deployer.local-process@0.1.0` per [`crate::defaults`]).
5//! Unlike the metadata-only [`BuiltinHandler`](super::slot::BuiltinHandler)
6//! that previously stood in here, this handler ships a
7//! [`DeployerCredentials`](crate::credentials::DeployerCredentials) impl
8//! so `gtc op credentials requirements local` returns real probe results
9//! today (C1 + C2 together).
10//!
11//! Reference shape for downstream deployers (Phase D AWS / K8s / GCP /
12//! Azure). The structure is:
13//!
14//! - `mod.rs` (this file) wires the [`EnvPackHandler`] surface — slot,
15//!   descriptor path, supported versions, and the accessor returning the
16//!   credentials handler.
17//! - [`credentials`] holds the [`DeployerCredentials`] impl
18//!   ([`LocalProcessCredentials`]) with the per-capability probes.
19
20pub mod credentials;
21pub mod deployer;
22
23use greentic_deploy_spec::CapabilitySlot;
24use semver::VersionReq;
25
26use super::slot::EnvPackHandler;
27use crate::tool_check::ToolCheck;
28
29pub use credentials::LocalProcessCredentials;
30
31/// Native handler for the local-process deployer env-pack.
32#[derive(Debug, Default)]
33pub struct LocalProcessDeployerHandler {
34    creds: LocalProcessCredentials,
35}
36
37impl LocalProcessDeployerHandler {
38    /// Version-independent descriptor path used as the registry key.
39    /// Must stay in lock-step with [`crate::defaults::LOCAL_DEPLOYER_PACK`]'s
40    /// path component (the part before `@`).
41    pub const DESCRIPTOR_PATH: &'static str = "greentic.deployer.local-process";
42
43    /// Descriptor versions this handler implements. Same `^0.1.0`
44    /// requirement the metadata-only `BuiltinHandler` previously declared,
45    /// so the existing `local` env's `greentic.deployer.local-process@0.1.0`
46    /// binding continues to resolve.
47    pub const VERSION_REQ: &'static str = "^0.1.0";
48
49    pub fn new() -> Self {
50        Self::default()
51    }
52
53    /// Construct with an overridden port range for the
54    /// `local-process.port.available` capability probe. Lets tests
55    /// exercise the probe with a known-free or known-busy range without
56    /// gambling on the default `8080..=8090`.
57    pub fn with_port_range(range: std::ops::RangeInclusive<u16>) -> Self {
58        Self {
59            creds: LocalProcessCredentials::with_port_range(range),
60        }
61    }
62}
63
64impl EnvPackHandler for LocalProcessDeployerHandler {
65    fn slot(&self) -> CapabilitySlot {
66        CapabilitySlot::Deployer
67    }
68
69    fn descriptor_path(&self) -> &str {
70        Self::DESCRIPTOR_PATH
71    }
72
73    fn supported_versions(&self) -> VersionReq {
74        Self::VERSION_REQ
75            .parse()
76            .expect("local-process version-req is valid (guarded by tests)")
77    }
78
79    fn preflight(&self) -> Vec<ToolCheck> {
80        // No external tools — local-process spawns child processes
81        // directly and does not shell out to a provider CLI. Matches the
82        // honest answer the prior `BuiltinHandler` returned (default).
83        Vec::new()
84    }
85
86    fn deployer_credentials(&self) -> Option<&dyn crate::credentials::DeployerCredentials> {
87        Some(&self.creds)
88    }
89
90    fn wizard_qaspec_yaml(&self) -> Option<&'static str> {
91        Some(include_str!("wizard.qaspec.yaml"))
92    }
93
94    fn as_deployer(&self) -> Option<&dyn crate::env_packs::deployer::Deployer> {
95        Some(self)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use crate::defaults::LOCAL_DEPLOYER_PACK;
103    use greentic_deploy_spec::PackDescriptor;
104
105    #[test]
106    fn handler_serves_deployer_slot_with_local_process_path() {
107        let h = LocalProcessDeployerHandler::default();
108        assert_eq!(h.slot(), CapabilitySlot::Deployer);
109        assert_eq!(h.descriptor_path(), "greentic.deployer.local-process");
110        // Version req is valid.
111        let _ = h.supported_versions();
112    }
113
114    #[test]
115    fn version_req_accepts_default_local_binding_descriptor() {
116        let h = LocalProcessDeployerHandler::default();
117        let pd = PackDescriptor::try_new(LOCAL_DEPLOYER_PACK).expect("descriptor parses");
118        assert!(
119            h.supported_versions().matches(&pd.version().0),
120            "version req {} must accept the default {} binding's version",
121            h.supported_versions(),
122            LOCAL_DEPLOYER_PACK
123        );
124    }
125
126    #[test]
127    fn exposes_credentials_contract() {
128        let h = LocalProcessDeployerHandler::default();
129        let creds = h
130            .deployer_credentials()
131            .expect("local-process handler must expose credentials");
132        // Sanity: contract returns the two C2 capabilities.
133        let caps = creds.required_capabilities();
134        assert_eq!(caps.len(), 2);
135    }
136
137    /// C6: pins this handler's wizard YAML to its canonical `id` so a
138    /// renamed YAML or a wrong `include_str!` path surfaces here.
139    /// (Round-trip `qa_spec::FormSpec` deserialization is covered by
140    /// the registry-level parametrized test in `registry.rs`.)
141    #[test]
142    fn wizard_qaspec_yaml_id_matches_canonical() {
143        let yaml = LocalProcessDeployerHandler::default()
144            .wizard_qaspec_yaml()
145            .expect("local-process handler ships a wizard QASpec");
146        let spec: qa_spec::FormSpec =
147            serde_yaml_bw::from_str(yaml).expect("wizard.qaspec.yaml parses as FormSpec");
148        assert_eq!(spec.id, "greentic.deployer.local-process.wizard");
149    }
150}