diff --git a/wfe-yaml/src/compiler.rs b/wfe-yaml/src/compiler.rs index 5733204..f5c6146 100644 --- a/wfe-yaml/src/compiler.rs +++ b/wfe-yaml/src/compiler.rs @@ -2,6 +2,7 @@ use std::time::Duration; use serde::Serialize; use wfe_core::models::error_behavior::ErrorBehavior; +use wfe_core::models::service::{ReadinessCheck, ReadinessProbe, ServiceDefinition, ServicePort}; use wfe_core::models::workflow_definition::{StepOutcome, WorkflowDefinition, WorkflowStep}; use wfe_core::traits::StepBody; @@ -53,6 +54,9 @@ pub fn compile(spec: &WorkflowSpec) -> Result, +) -> Result, YamlWorkflowError> { + let mut services = Vec::new(); + + for (name, yaml_svc) in yaml_services { + let readiness = yaml_svc.readiness.as_ref().map(|r| { + let check = if let Some(ref exec_cmd) = r.exec { + ReadinessCheck::Exec(exec_cmd.clone()) + } else if let Some(tcp_port) = r.tcp { + ReadinessCheck::TcpSocket(tcp_port) + } else if let Some(ref http) = r.http { + ReadinessCheck::HttpGet { + port: http.port, + path: http.path.clone(), + } + } else { + // Default: TCP check on first port. + ReadinessCheck::TcpSocket( + yaml_svc.ports.first().copied().unwrap_or(0), + ) + }; + + let interval_ms = r + .interval + .as_ref() + .and_then(|s| parse_duration_ms(s)) + .unwrap_or(5000); + let timeout_ms = r + .timeout + .as_ref() + .and_then(|s| parse_duration_ms(s)) + .unwrap_or(60000); + + ReadinessProbe { + check, + interval_ms, + timeout_ms, + retries: r.retries.unwrap_or(12), + } + }); + + services.push(ServiceDefinition { + name: name.clone(), + image: yaml_svc.image.clone(), + ports: yaml_svc + .ports + .iter() + .map(|&p| ServicePort::tcp(p)) + .collect(), + env: yaml_svc.env.clone(), + readiness, + command: yaml_svc.command.clone().unwrap_or_default(), + args: yaml_svc.args.clone().unwrap_or_default(), + memory: yaml_svc.memory.clone(), + cpu: yaml_svc.cpu.clone(), + }); + } + + Ok(services) +} + fn map_error_behavior(eb: &YamlErrorBehavior) -> Result { match eb.behavior_type.as_str() { "retry" => { diff --git a/wfe-yaml/src/schema.rs b/wfe-yaml/src/schema.rs index 2adf98b..6a6f8de 100644 --- a/wfe-yaml/src/schema.rs +++ b/wfe-yaml/src/schema.rs @@ -87,11 +87,69 @@ pub struct WorkflowSpec { /// Typed output schema: { field_name: type_string }. #[serde(default)] pub outputs: HashMap, + /// Infrastructure services required by this workflow. + #[serde(default)] + pub services: HashMap, /// Allow unknown top-level keys (e.g. `_templates`) for YAML anchors. #[serde(flatten)] pub _extra: HashMap, } +/// A service definition in YAML format. +#[derive(Debug, Deserialize)] +pub struct YamlService { + pub image: String, + #[serde(default)] + pub ports: Vec, + #[serde(default)] + pub env: HashMap, + #[serde(default)] + pub readiness: Option, + #[serde(default)] + pub memory: Option, + #[serde(default)] + pub cpu: Option, + #[serde(default)] + pub command: Option>, + #[serde(default)] + pub args: Option>, +} + +/// Readiness probe configuration in YAML format. +#[derive(Debug, Deserialize)] +pub struct YamlReadiness { + /// Execute a command to check readiness. + #[serde(default)] + pub exec: Option>, + /// Check if a TCP port is accepting connections. + #[serde(default)] + pub tcp: Option, + /// HTTP GET health check. + #[serde(default)] + pub http: Option, + /// Poll interval (e.g., "5s", "2s"). + #[serde(default)] + pub interval: Option, + /// Total timeout (e.g., "60s", "30s"). + #[serde(default)] + pub timeout: Option, + /// Max retries before giving up. + #[serde(default)] + pub retries: Option, +} + +/// HTTP GET readiness check. +#[derive(Debug, Deserialize)] +pub struct YamlHttpGet { + pub port: u16, + #[serde(default = "default_health_path")] + pub path: String, +} + +fn default_health_path() -> String { + "/".into() +} + #[derive(Debug, Deserialize)] pub struct YamlStep { pub name: String,