use greentic_deploy_spec::{Environment, RevisionLifecycle};
use serde_json::Value;
use super::K8sDeployerHandler;
use super::manifests::{self, K8sParams};
use crate::env_packs::render::{ManifestRenderer, RenderError};
fn has_cluster_presence(lifecycle: RevisionLifecycle) -> bool {
matches!(
lifecycle,
RevisionLifecycle::Warming | RevisionLifecycle::Ready | RevisionLifecycle::Draining
)
}
impl ManifestRenderer for K8sDeployerHandler {
fn render_environment(
&self,
env: &Environment,
answers: Option<&serde_json::Value>,
) -> Result<Vec<Value>, RenderError> {
let params = K8sParams::from_answers(env, answers).map_err(RenderError::InvalidAnswers)?;
let mut objects = manifests::render_environment_manifests(env, ¶ms);
for revision in &env.revisions {
if has_cluster_presence(revision.lifecycle) {
objects.extend(manifests::render_worker_manifests(env, revision, ¶ms));
}
}
Ok(objects)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::env_packs::EnvPackHandler;
use crate::env_packs::deployer::conformance::build_fixture_env;
fn rendered_names(objects: &[Value]) -> Vec<String> {
objects
.iter()
.map(|o| {
o.pointer("/metadata/name")
.and_then(Value::as_str)
.expect("every rendered object has metadata.name")
.to_string()
})
.collect()
}
#[test]
fn handler_exposes_manifest_renderer() {
let h = K8sDeployerHandler::default();
assert!(
(&h as &dyn EnvPackHandler).as_manifest_renderer().is_some(),
"EnvPackHandler::as_manifest_renderer must surface the K8s renderer"
);
}
#[test]
fn renders_env_level_set_then_present_revision_workers() {
let env = build_fixture_env();
let params = K8sParams::for_env(&env);
let env_level = manifests::render_environment_manifests(&env, ¶ms);
let handler = K8sDeployerHandler::default();
let objects = handler.render_environment(&env, None).unwrap();
assert_eq!(
&objects[..env_level.len()],
&env_level[..],
"env-level objects come first, unchanged from the apply path"
);
assert_eq!(objects.len(), env_level.len() + 2 * 2);
let names = rendered_names(&objects);
for revision in &env.revisions {
let worker = manifests::worker_name(revision);
let expected = has_cluster_presence(revision.lifecycle);
assert_eq!(
names.iter().filter(|n| **n == worker).count() == 2,
expected,
"revision `{}` ({:?}) presence mismatch",
revision.revision_id,
revision.lifecycle
);
}
}
#[test]
fn presence_policy_matches_the_b7_two_state_model() {
use RevisionLifecycle::*;
for (lifecycle, present) in [
(Inactive, false),
(Staged, false),
(Warming, true),
(Ready, true),
(Draining, true),
(Failed, false),
(Archived, false),
] {
assert_eq!(
has_cluster_presence(lifecycle),
present,
"{lifecycle:?} presence policy drifted"
);
}
}
#[test]
fn render_environment_is_deterministic() {
let env = build_fixture_env();
let handler = K8sDeployerHandler::default();
assert_eq!(
handler.render_environment(&env, None).unwrap(),
handler.render_environment(&env, None).unwrap()
);
}
#[test]
fn invalid_answers_surface_render_error() {
let env = build_fixture_env();
let handler = K8sDeployerHandler::default();
let bad = serde_json::json!("not an object");
let err = handler.render_environment(&env, Some(&bad)).unwrap_err();
assert!(
matches!(err, RenderError::InvalidAnswers(_)),
"expected InvalidAnswers, got {err:?}"
);
}
}