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 serde::Serialize;
|
||||||
use wfe_core::models::error_behavior::ErrorBehavior;
|
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::models::workflow_definition::{StepOutcome, WorkflowDefinition, WorkflowStep};
|
||||||
use wfe_core::traits::StepBody;
|
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_steps(&spec.steps, &mut definition, &mut factories, &mut next_id)?;
|
||||||
|
|
||||||
|
// Compile services.
|
||||||
|
definition.services = compile_services(&spec.services)?;
|
||||||
|
|
||||||
Ok(CompiledWorkflow {
|
Ok(CompiledWorkflow {
|
||||||
definition,
|
definition,
|
||||||
step_factories: factories,
|
step_factories: factories,
|
||||||
@@ -922,6 +926,68 @@ fn build_kubernetes_config(
|
|||||||
Ok((step_config, cluster_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> {
|
fn map_error_behavior(eb: &YamlErrorBehavior) -> Result<ErrorBehavior, YamlWorkflowError> {
|
||||||
match eb.behavior_type.as_str() {
|
match eb.behavior_type.as_str() {
|
||||||
"retry" => {
|
"retry" => {
|
||||||
|
|||||||
@@ -87,11 +87,69 @@ pub struct WorkflowSpec {
|
|||||||
/// Typed output schema: { field_name: type_string }.
|
/// Typed output schema: { field_name: type_string }.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub outputs: HashMap<String, String>,
|
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.
|
/// Allow unknown top-level keys (e.g. `_templates`) for YAML anchors.
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub _extra: HashMap<String, serde_yaml::Value>,
|
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)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct YamlStep {
|
pub struct YamlStep {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
Reference in New Issue
Block a user