conductor/src/definition.rs

418 lines
12 KiB
Rust

use crate::job::{Job, Jobs};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Default)]
#[serde(default)]
pub struct Project {
pub components: HashMap<String, Component>,
pub groups: Vec<Group>,
pub env: HashMap<String, String>,
pub tasks: Vec<TaskDefinition>,
}
impl Project {
fn get_absolute_task(&self, name: &str) -> Option<Jobs> {
self.tasks
.iter()
.find(|t| t.name == name)
.map(|task| {
let job = self.build_project_task_job(task);
let mut jobs = task
.before
.iter()
.map(|name| self.get_absolute_task(name))
.flatten()
.map(|jobs| jobs.to_vec())
.flatten()
.collect::<Vec<_>>();
jobs.push(job);
Jobs::new(jobs)
})
.or_else(|| {
self.components.iter().find_map(|(c_name, component)| {
component
.tasks
.iter()
.find(|t| format!("{}:{}", c_name, t.name) == name)
.map(|task| {
let job = self.build_component_task_job(c_name, component, task);
let mut jobs = task
.before
.iter()
.map(|name| self.get_absolute_task(name))
.flatten()
.map(|jobs| jobs.to_vec())
.flatten()
.collect::<Vec<_>>();
jobs.push(job);
Jobs::new(jobs)
})
})
})
}
fn get_relative_task(&self, task_name: &str, component_name: &str) -> Option<Jobs> {
if let Some(component) = self.components.get(component_name) {
component
.tasks
.iter()
.find(|t| t.name == task_name)
.map(|task| {
let job = self.build_component_task_job(component_name, component, task);
let mut jobs = task
.before
.iter()
.map(|name| self.get_by_name(name))
.flatten()
.map(|jobs| jobs.to_vec())
.flatten()
.collect::<Vec<_>>();
jobs.push(job);
Jobs::new(jobs)
})
} else {
None
}
}
fn get_component(&self, component_name: &str) -> Option<Jobs> {
self.components.get(component_name).map(|c| {
let component_job = self.build_component_job(component_name, c);
let mut absolute_tasks = c
.before
.iter()
.map(|name| self.get_absolute_task(name))
.flatten()
.map(|jobs| jobs.to_vec())
.flatten()
.collect::<Vec<_>>();
let mut relative_tasks = c
.before
.iter()
.map(|task_name| self.get_relative_task(task_name, &component_name))
.flatten()
.map(|jobs| jobs.to_vec())
.flatten()
.collect::<Vec<_>>();
absolute_tasks.append(&mut relative_tasks);
absolute_tasks.push(component_job);
Jobs::new(absolute_tasks)
})
}
fn get_group(&self, name: &str) -> Option<Vec<Jobs>> {
self.groups.iter().find(|g| g.name == name).map(|group| {
group
.components
.iter()
.map(|name| self.get_by_name(&name))
.flatten()
.collect()
})
}
fn build_component_job(&self, name: &str, c: &Component) -> Job {
let mut env = self.env.clone();
env.extend(c.env.clone());
Job {
name: name.to_string(),
env,
..Job::default()
}
}
fn build_project_task_job(&self, task: &TaskDefinition) -> Job {
let mut env = self.env.clone();
env.extend(task.env.clone());
Job {
name: task.name.clone(),
..Job::default()
}
}
fn build_component_task_job(
&self,
cmp_name: &str,
component: &Component,
task: &TaskDefinition,
) -> Job {
let mut env = self.env.clone();
env.extend(component.env.clone());
env.extend(task.env.clone());
Job {
name: format!("{}:{}", cmp_name.to_string(), task.name.clone()),
env,
..Job::default()
}
}
pub fn get_by_name(&self, name: &str) -> Option<Jobs> {
self.get_component(name)
.or_else(|| self.get_absolute_task(name))
.or_else(|| {
self.get_group(name).map(|v| {
v.iter()
.map(|jobs| jobs.to_vec())
.flatten()
.collect::<Vec<_>>()
.into()
})
})
}
pub fn from_str(s: &str) -> Self {
serde_yaml::from_str(s).unwrap()
}
}
#[derive(Serialize, Deserialize, Default)]
#[serde(default)]
pub struct Group {
name: String,
components: Vec<String>,
}
#[derive(Serialize, Deserialize)]
#[serde(default)]
pub struct Component {
pub env: HashMap<String, String>,
pub command: String,
pub path: Option<String>,
pub retry: bool,
pub keep_alive: bool,
pub retry_delay: u64,
pub before: Vec<String>,
pub tasks: Vec<TaskDefinition>,
}
impl Default for Component {
fn default() -> Self {
Self {
env: HashMap::new(),
command: String::new(),
path: None,
retry: false,
keep_alive: true,
retry_delay: 2,
before: vec![],
tasks: vec![],
}
}
}
#[derive(Serialize, Deserialize, Default)]
#[serde(default)]
pub struct TaskDefinition {
pub env: HashMap<String, String>,
pub name: String,
pub command: String,
pub path: Option<String>,
pub retry: bool,
pub keep_alive: bool,
pub retry_delay: u64,
pub before: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::Project;
#[test]
fn component_by_name() {
let project = Project::from_str(
r#"
components:
test-component: {}
"#,
);
let jobs = project.get_by_name("test-component").unwrap();
assert_eq!(jobs.len(), 1);
}
#[test]
fn project_task() {
let project = Project::from_str(
r#"
tasks:
- name: task1
"#,
);
let jobs = project.get_by_name("task1").unwrap();
assert_eq!(jobs.len(), 1);
}
#[test]
fn component_task() {
let project = Project::from_str(
r#"
components:
c1:
tasks:
- name: task1
"#,
);
let jobs = project.get_by_name("c1:task1").unwrap();
assert_eq!(jobs.len(), 1);
}
#[test]
fn component_dependent_absolute_component_task() {
let project = Project::from_str(
r#"
components:
main-cmp:
before:
- main-cmp:sub-task
tasks:
- name: sub-task
"#,
);
let jobs = project.get_by_name("main-cmp").unwrap();
assert_eq!(jobs.len(), 2);
assert_eq!(jobs.first().unwrap().name, "main-cmp:sub-task");
}
#[test]
fn component_dependent_relative() {
let project = Project::from_str(
r#"
components:
main-cmp:
before:
- sub-task
tasks:
- name: sub-task
"#,
);
let jobs = project.get_by_name("main-cmp").unwrap();
assert_eq!(jobs.len(), 2);
assert_eq!(jobs.first().unwrap().name, "main-cmp:sub-task");
}
#[test]
fn complicated_dependencies() {
let project = Project::from_str(
r#"
components:
ui:
before:
- build-ui
tasks:
- name: build-ui
server:
before:
- setup
tasks:
- name: setup
- name: build
before:
- server:setup
tasks:
- name: build
before:
- ui:build-ui
- server:build
"#,
);
let jobs = project.get_by_name("build").unwrap();
assert_eq!(jobs.get(0).unwrap().name, "ui:build-ui");
assert_eq!(jobs.get(1).unwrap().name, "server:setup");
assert_eq!(jobs.get(2).unwrap().name, "server:build");
assert_eq!(jobs.get(3).unwrap().name, "build");
assert_eq!(jobs.len(), 4);
}
#[test]
fn component_env() {
let project = Project::from_str(
r#"
env:
foo: one
sub: two
components:
main-cmp:
env:
sub: three
"#,
);
let jobs = project.get_by_name("main-cmp").unwrap();
let job = jobs.first().unwrap();
assert_eq!(job.env.get("foo"), Some(&String::from("one")));
assert_eq!(job.env.get("sub"), Some(&String::from("three")));
}
#[test]
fn task_env() {
let project = Project::from_str(
r#"
env:
root: ten
foo: one
sub: two
components:
main-cmp:
env:
sub: three
tasks:
- name: t
env:
foo: four
"#,
);
let jobs = project.get_by_name("main-cmp:t").unwrap();
let job = jobs.first().unwrap();
assert_eq!(job.env.get("foo"), Some(&String::from("four")));
assert_eq!(job.env.get("sub"), Some(&String::from("three")));
assert_eq!(job.env.get("root"), Some(&String::from("ten")));
}
#[test]
fn group() {
let project = Project::from_str(
r#"
groups:
- name: all
components:
- ui
- server
components:
ui:
before:
- build-ui
tasks:
- name: build-ui
server:
before:
- setup
tasks:
- name: setup
- name: build
before:
- server:setup
tasks:
- name: build
before:
- ui:build-ui
- server:build
"#,
);
let jobs = project.get_by_name("all").unwrap();
println!("{:?}", jobs);
assert_eq!(jobs.get(0).unwrap().name, "ui:build-ui");
assert_eq!(jobs.get(1).unwrap().name, "ui");
assert_eq!(jobs.get(2).unwrap().name, "server:setup");
assert_eq!(jobs.get(3).unwrap().name, "server");
assert_eq!(jobs.len(), 4);
}
}