feat(wfe-yaml): services block in workflow YAML definitions
This commit is contained in:
@@ -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<CompiledWorkflow, YamlWorkflowErro
|
||||
|
||||
compile_steps(&spec.steps, &mut definition, &mut factories, &mut next_id)?;
|
||||
|
||||
// Compile services.
|
||||
definition.services = compile_services(&spec.services)?;
|
||||
|
||||
Ok(CompiledWorkflow {
|
||||
definition,
|
||||
step_factories: factories,
|
||||
@@ -922,6 +926,68 @@ fn build_kubernetes_config(
|
||||
Ok((step_config, cluster_config))
|
||||
}
|
||||
|
||||
fn compile_services(
|
||||
yaml_services: &std::collections::HashMap<String, crate::schema::YamlService>,
|
||||
) -> Result<Vec<ServiceDefinition>, 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<ErrorBehavior, YamlWorkflowError> {
|
||||
match eb.behavior_type.as_str() {
|
||||
"retry" => {
|
||||
|
||||
@@ -87,11 +87,69 @@ pub struct WorkflowSpec {
|
||||
/// Typed output schema: { field_name: type_string }.
|
||||
#[serde(default)]
|
||||
pub outputs: HashMap<String, String>,
|
||||
/// Infrastructure services required by this workflow.
|
||||
#[serde(default)]
|
||||
pub services: HashMap<String, YamlService>,
|
||||
/// Allow unknown top-level keys (e.g. `_templates`) for YAML anchors.
|
||||
#[serde(flatten)]
|
||||
pub _extra: HashMap<String, serde_yaml::Value>,
|
||||
}
|
||||
|
||||
/// A service definition in YAML format.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct YamlService {
|
||||
pub image: String,
|
||||
#[serde(default)]
|
||||
pub ports: Vec<u16>,
|
||||
#[serde(default)]
|
||||
pub env: HashMap<String, String>,
|
||||
#[serde(default)]
|
||||
pub readiness: Option<YamlReadiness>,
|
||||
#[serde(default)]
|
||||
pub memory: Option<String>,
|
||||
#[serde(default)]
|
||||
pub cpu: Option<String>,
|
||||
#[serde(default)]
|
||||
pub command: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub args: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Readiness probe configuration in YAML format.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct YamlReadiness {
|
||||
/// Execute a command to check readiness.
|
||||
#[serde(default)]
|
||||
pub exec: Option<Vec<String>>,
|
||||
/// Check if a TCP port is accepting connections.
|
||||
#[serde(default)]
|
||||
pub tcp: Option<u16>,
|
||||
/// HTTP GET health check.
|
||||
#[serde(default)]
|
||||
pub http: Option<YamlHttpGet>,
|
||||
/// Poll interval (e.g., "5s", "2s").
|
||||
#[serde(default)]
|
||||
pub interval: Option<String>,
|
||||
/// Total timeout (e.g., "60s", "30s").
|
||||
#[serde(default)]
|
||||
pub timeout: Option<String>,
|
||||
/// Max retries before giving up.
|
||||
#[serde(default)]
|
||||
pub retries: Option<u32>,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
|
||||
Reference in New Issue
Block a user