k8s-maestro 1.0.0

A Kubernetes job orchestrator tool library
Documentation
use crate::clients::MaestroK8sClient;
use crate::steps::kubernetes::{KubeJobStep, KubePodStep};
use crate::steps::result::StepResult;
use crate::steps::traits::{WorkFlowStep, KubeWorkFlowStep};
use anyhow::Result;
use k8s_openapi::api::batch::v1::Job;
use k8s_openapi::api::core::v1::Pod;
use kube::Api;

pub struct StepExecutor {
    client: MaestroK8sClient,
}

impl StepExecutor {
    pub fn new(client: MaestroK8sClient) -> Self {
        Self { client }
    }

    pub async fn execute_step(&self, step: &dyn WorkFlowStep) -> Result<StepResult> {
        if let Some(kube_job) = step.as_any().downcast_ref::<KubeJobStep>() {
            self.execute_kube_step(kube_job).await
        } else if let Some(kube_pod) = step.as_any().downcast_ref::<KubePodStep>() {
            self.execute_pod_step(kube_pod).await
        } else {
            anyhow::bail!("Unsupported step type: {}", step.step_id())
        }
    }

    pub async fn execute_kube_step(&self, step: &KubeJobStep) -> Result<StepResult> {
        let step_id = step.step_id().to_string();
        let namespace = step.namespace().to_string();
        let resource_name = step.resource_name().to_string();

        let client = self.client.inner().clone();
        let jobs: Api<Job> = Api::namespaced(client, &namespace);

        let job = jobs.get(&resource_name).await.map_err(|e| {
            anyhow::anyhow!("Failed to get job {}: {}", resource_name, e)
        })?;

        let status = job
            .status
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("Job has no status"))?;

        let succeeded = status.succeeded.unwrap_or(0);
        let failed = status.failed.unwrap_or(0);

        let step_result = StepResult::new(&step_id)
            .with_status(if failed > 0 {
                crate::steps::result::StepStatus::Failure
            } else {
                crate::steps::result::StepStatus::Success
            })
            .with_exit_code(if failed > 0 { 1 } else { 0 })
            .with_output("succeeded", serde_json::json!(succeeded))
            .with_output("failed", serde_json::json!(failed));

        Ok(step_result)
    }

    pub async fn execute_pod_step(&self, step: &KubePodStep) -> Result<StepResult> {
        let step_id = step.step_id().to_string();
        let namespace = step.namespace().to_string();
        let resource_name = step.resource_name().to_string();

        let client = self.client.inner().clone();
        let pods: Api<Pod> = Api::namespaced(client, &namespace);

        let pod = pods.get(&resource_name).await.map_err(|e| {
            anyhow::anyhow!("Failed to get pod {}: {}", resource_name, e)
        })?;

        let phase = pod
            .status
            .as_ref()
            .and_then(|s| s.phase.as_ref())
            .ok_or_else(|| anyhow::anyhow!("Pod has no phase"))?;

        let step_result = StepResult::new(&step_id)
            .with_status(match phase.as_str() {
                "Succeeded" => crate::steps::result::StepStatus::Success,
                "Failed" => crate::steps::result::StepStatus::Failure,
                _ => crate::steps::result::StepStatus::Failure,
            })
            .with_exit_code(if phase == "Succeeded" { 0 } else { 1 })
            .with_output("phase", serde_json::json!(phase));

        Ok(step_result)
    }
}