|
|
|
@ -1,30 +1,101 @@
|
|
|
|
|
use crate::job::{Job, Jobs};
|
|
|
|
|
use rand::Rng;
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
|
#[serde(untagged)]
|
|
|
|
|
pub enum Command {
|
|
|
|
|
Single(String),
|
|
|
|
|
Multiple(Vec<String>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for Command {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::Single(String::new())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<String> for Command {
|
|
|
|
|
fn from(source: String) -> Self {
|
|
|
|
|
Self::Single(source)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&str> for Command {
|
|
|
|
|
fn from(source: &str) -> Self {
|
|
|
|
|
Self::Single(source.to_string())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for Command {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Single(v) => write!(f, "{}", &v),
|
|
|
|
|
Self::Multiple(v) => write!(f, "{}", &v.join(" && ")),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
|
#[serde(untagged)]
|
|
|
|
|
pub enum Dependencies {
|
|
|
|
|
Single(String),
|
|
|
|
|
Multiple(Vec<String>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Dependencies {
|
|
|
|
|
pub fn to_vec(&self) -> Vec<String> {
|
|
|
|
|
match self {
|
|
|
|
|
Dependencies::Single(v) => vec![v.clone()],
|
|
|
|
|
Dependencies::Multiple(v) => v.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for Dependencies {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::Multiple(vec![])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Default)]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
/// Represents an entire configuration. This is deserialized from the complete conductor.yml file.
|
|
|
|
|
pub struct Project {
|
|
|
|
|
/// The components that are defined within the project. This is stored as a map with the name of
|
|
|
|
|
/// of the component and its definition.
|
|
|
|
|
pub components: HashMap<String, Component>,
|
|
|
|
|
pub groups: Vec<Group>,
|
|
|
|
|
/// The list of groups defined in the project. These groups are stored as a map with the name of
|
|
|
|
|
/// the group and its definition.
|
|
|
|
|
pub groups: HashMap<String, Group>,
|
|
|
|
|
/// A map of values that are populated into the environment for all components and tasks.
|
|
|
|
|
/// This value is inclusive with any environment variables defined in the individual task or
|
|
|
|
|
/// component.
|
|
|
|
|
///
|
|
|
|
|
/// If the same value is defined here and within the task/component definition. The value
|
|
|
|
|
/// defined in the task/component will take priority.
|
|
|
|
|
pub env: HashMap<String, String>,
|
|
|
|
|
pub tasks: Vec<TaskDefinition>,
|
|
|
|
|
/// The tasks defined inside project. This is stored as a map with the task name and task
|
|
|
|
|
/// definition
|
|
|
|
|
pub tasks: HashMap<String, TaskDefinition>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Project {
|
|
|
|
|
/// Retreive a list of jobs from the name of a task. This function expects the fully qualified
|
|
|
|
|
/// task name. For subcomponents that means component::task.
|
|
|
|
|
/// The jobs structure returned contains a seuqential execution list with all dependencies
|
|
|
|
|
fn get_absolute_task(&self, name: &str) -> Option<Jobs> {
|
|
|
|
|
self.tasks
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|t| t.name == name)
|
|
|
|
|
.get(name)
|
|
|
|
|
.map(|task| {
|
|
|
|
|
let job = self.build_project_task_job(task);
|
|
|
|
|
let job = self.build_project_task_job(name, task);
|
|
|
|
|
let mut jobs = task
|
|
|
|
|
.before
|
|
|
|
|
.to_vec()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|name| self.get_absolute_task(name))
|
|
|
|
|
.flatten()
|
|
|
|
|
.map(|jobs| jobs.to_vec())
|
|
|
|
|
.flatten()
|
|
|
|
|
.flat_map(|name| self.get_absolute_task(name))
|
|
|
|
|
.flat_map(|jobs| jobs.to_vec())
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
jobs.push(job);
|
|
|
|
|
Jobs::new(jobs)
|
|
|
|
@ -34,16 +105,16 @@ impl Project {
|
|
|
|
|
component
|
|
|
|
|
.tasks
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|t| format!("{}:{}", c_name, t.name) == name)
|
|
|
|
|
.map(|task| {
|
|
|
|
|
let job = self.build_component_task_job(c_name, component, task);
|
|
|
|
|
.find(|(t_name, _task)| format!("{}:{}", c_name, t_name) == name)
|
|
|
|
|
.map(|(t_name, task)| {
|
|
|
|
|
let job =
|
|
|
|
|
self.build_component_task_job(c_name, t_name, component, task);
|
|
|
|
|
let mut jobs = task
|
|
|
|
|
.before
|
|
|
|
|
.to_vec()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|name| self.get_absolute_task(name))
|
|
|
|
|
.flatten()
|
|
|
|
|
.map(|jobs| jobs.to_vec())
|
|
|
|
|
.flatten()
|
|
|
|
|
.flat_map(|name| self.get_absolute_task(name))
|
|
|
|
|
.flat_map(|jobs| jobs.to_vec())
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
jobs.push(job);
|
|
|
|
|
Jobs::new(jobs)
|
|
|
|
@ -52,48 +123,44 @@ impl Project {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Retrieves a job based on a relative task definition. The task is found by the direct task name
|
|
|
|
|
/// and a component name separately.
|
|
|
|
|
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)
|
|
|
|
|
})
|
|
|
|
|
component.tasks.get(task_name).map(|task| {
|
|
|
|
|
let job = self.build_component_task_job(component_name, task_name, component, task);
|
|
|
|
|
let mut jobs = task
|
|
|
|
|
.before
|
|
|
|
|
.to_vec()
|
|
|
|
|
.iter()
|
|
|
|
|
.flat_map(|name| self.get_by_name(name))
|
|
|
|
|
.flat_map(|jobs| jobs.to_vec())
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
jobs.push(job);
|
|
|
|
|
Jobs::new(jobs)
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Retrieves a job based on a component definition
|
|
|
|
|
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
|
|
|
|
|
.to_vec()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|name| self.get_absolute_task(name))
|
|
|
|
|
.flatten()
|
|
|
|
|
.map(|jobs| jobs.to_vec())
|
|
|
|
|
.flatten()
|
|
|
|
|
.flat_map(|name| self.get_absolute_task(name))
|
|
|
|
|
.flat_map(|jobs| jobs.to_vec())
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
let mut relative_tasks = c
|
|
|
|
|
.before
|
|
|
|
|
.to_vec()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|task_name| self.get_relative_task(task_name, &component_name))
|
|
|
|
|
.flatten()
|
|
|
|
|
.map(|jobs| jobs.to_vec())
|
|
|
|
|
.flatten()
|
|
|
|
|
.flat_map(|task_name| self.get_relative_task(task_name, component_name))
|
|
|
|
|
.flat_map(|jobs| jobs.to_vec())
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
absolute_tasks.append(&mut relative_tasks);
|
|
|
|
@ -102,39 +169,49 @@ impl Project {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Retrieves a vector of jobs based on a group definition.
|
|
|
|
|
/// The return value is similar to `get_jobplan`, its is effectively a two-dimensional
|
|
|
|
|
/// array representing parallel and sequential jobs.
|
|
|
|
|
fn get_group(&self, name: &str) -> Option<Vec<Jobs>> {
|
|
|
|
|
self.groups.iter().find(|g| g.name == name).map(|group| {
|
|
|
|
|
self.groups.get(name).map(|group| {
|
|
|
|
|
group
|
|
|
|
|
.components
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|name| self.get_by_name(&name))
|
|
|
|
|
.flatten()
|
|
|
|
|
.flat_map(|name| self.get_by_name(name))
|
|
|
|
|
.collect()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Converts a component definition to a job definition
|
|
|
|
|
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(),
|
|
|
|
|
command: c.command.clone(),
|
|
|
|
|
color: c.color,
|
|
|
|
|
path: c.path.clone().unwrap_or_else(|| String::from(".")),
|
|
|
|
|
env,
|
|
|
|
|
..Job::default()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn build_project_task_job(&self, task: &TaskDefinition) -> Job {
|
|
|
|
|
/// Converts a root level task definition to a job definition
|
|
|
|
|
fn build_project_task_job(&self, name: &str, task: &TaskDefinition) -> Job {
|
|
|
|
|
let mut env = self.env.clone();
|
|
|
|
|
env.extend(task.env.clone());
|
|
|
|
|
Job {
|
|
|
|
|
name: task.name.clone(),
|
|
|
|
|
..Job::default()
|
|
|
|
|
env,
|
|
|
|
|
name: name.to_string(),
|
|
|
|
|
..Job::from(task)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Converts a component level task definition to a job definition
|
|
|
|
|
fn build_component_task_job(
|
|
|
|
|
&self,
|
|
|
|
|
cmp_name: &str,
|
|
|
|
|
component_name: &str,
|
|
|
|
|
task_name: &str,
|
|
|
|
|
component: &Component,
|
|
|
|
|
task: &TaskDefinition,
|
|
|
|
|
) -> Job {
|
|
|
|
@ -142,77 +219,120 @@ impl Project {
|
|
|
|
|
env.extend(component.env.clone());
|
|
|
|
|
env.extend(task.env.clone());
|
|
|
|
|
Job {
|
|
|
|
|
name: format!("{}:{}", cmp_name.to_string(), task.name.clone()),
|
|
|
|
|
env,
|
|
|
|
|
..Job::default()
|
|
|
|
|
name: format!("{}:{}", component_name, task_name),
|
|
|
|
|
..Job::from(task)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns a list of jobs for an object by name. This name could be a task or component and
|
|
|
|
|
/// the jobs returned will be the matched item with all of its dependencies
|
|
|
|
|
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()
|
|
|
|
|
/// Returns a vector of jobs for an object by name. This name could be a task, component, or
|
|
|
|
|
/// or group. The vector contains jobs that can be run in parallel. The individual jobs are run
|
|
|
|
|
/// in sequence.
|
|
|
|
|
pub fn get_runplan(&self, name: &str) -> Option<Vec<Jobs>> {
|
|
|
|
|
self.get_by_name(name)
|
|
|
|
|
.map(|i| vec![i])
|
|
|
|
|
.or_else(|| self.get_group(name))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Load a project from a yaml string
|
|
|
|
|
pub fn load_str(s: &str) -> anyhow::Result<Self> {
|
|
|
|
|
Ok(serde_yaml::from_str(s)?)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Default)]
|
|
|
|
|
/// Groups are used to define a series of components that can be ran in parallel.
|
|
|
|
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub struct Group {
|
|
|
|
|
name: String,
|
|
|
|
|
components: Vec<String>,
|
|
|
|
|
/// A list of component names to run when the group is launched
|
|
|
|
|
pub components: Vec<String>,
|
|
|
|
|
/// A description for the group that is displayed in the task list
|
|
|
|
|
pub description: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
|
/// A component represents a long running task.
|
|
|
|
|
/// In general, these are things that need to be kept alive during the
|
|
|
|
|
/// the operation.
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone)]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub struct Component {
|
|
|
|
|
/// A description for the component that is displayed in the task list
|
|
|
|
|
pub description: Option<String>,
|
|
|
|
|
/// A map of environment settings that are set before launching the component
|
|
|
|
|
pub env: HashMap<String, String>,
|
|
|
|
|
pub command: String,
|
|
|
|
|
/// The command(s) to execute when the component is launched
|
|
|
|
|
pub command: Command,
|
|
|
|
|
/// The working path the commands are executed in. This can be relative to the conductor.yaml
|
|
|
|
|
pub path: Option<String>,
|
|
|
|
|
pub retry: bool,
|
|
|
|
|
/// If true the component will be relaunched after it exits
|
|
|
|
|
pub keep_alive: bool,
|
|
|
|
|
/// The number of seconds to wait before relaunching the component
|
|
|
|
|
pub retry_delay: u64,
|
|
|
|
|
pub before: Vec<String>,
|
|
|
|
|
pub tasks: Vec<TaskDefinition>,
|
|
|
|
|
/// The tasks that should be ran before the component's commands
|
|
|
|
|
pub before: Dependencies,
|
|
|
|
|
/// A map of component level tasks. Tasks can be placed under the component for orgnaizational purposes.
|
|
|
|
|
/// They can be ran and referenced from anywhere using an absolute namespace: component::task
|
|
|
|
|
pub tasks: HashMap<String, TaskDefinition>,
|
|
|
|
|
/// The color to use for the component name in the terminal output from the command.
|
|
|
|
|
pub color: (u8, u8, u8),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for Component {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
|
|
|
|
|
|
Self {
|
|
|
|
|
description: None,
|
|
|
|
|
env: HashMap::new(),
|
|
|
|
|
command: String::new(),
|
|
|
|
|
command: Command::default(),
|
|
|
|
|
path: None,
|
|
|
|
|
retry: false,
|
|
|
|
|
keep_alive: true,
|
|
|
|
|
retry_delay: 2,
|
|
|
|
|
before: vec![],
|
|
|
|
|
tasks: vec![],
|
|
|
|
|
before: Dependencies::default(),
|
|
|
|
|
tasks: HashMap::new(),
|
|
|
|
|
color: (
|
|
|
|
|
rng.gen_range(100..255),
|
|
|
|
|
rng.gen_range(100..255),
|
|
|
|
|
rng.gen_range(100..255),
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Default)]
|
|
|
|
|
/// Tasks are short lived jobs that can be used to perform
|
|
|
|
|
/// utility tasks or to setup a component.
|
|
|
|
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub struct TaskDefinition {
|
|
|
|
|
pub description: String,
|
|
|
|
|
/// A map of environment variables that are provided before running the task
|
|
|
|
|
pub env: HashMap<String, String>,
|
|
|
|
|
pub name: String,
|
|
|
|
|
pub command: String,
|
|
|
|
|
/// The command(s) to execute when the task is launched
|
|
|
|
|
pub command: Command,
|
|
|
|
|
/// The path to execute the task commands from. This can be relative from the conductor.yaml
|
|
|
|
|
pub path: Option<String>,
|
|
|
|
|
pub retry: bool,
|
|
|
|
|
pub keep_alive: bool,
|
|
|
|
|
pub retry_delay: u64,
|
|
|
|
|
pub before: Vec<String>,
|
|
|
|
|
/// Other tasks that should be ran before running the task's command
|
|
|
|
|
pub before: Dependencies,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&TaskDefinition> for Job {
|
|
|
|
|
fn from(source: &TaskDefinition) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
env: source.env.clone(),
|
|
|
|
|
command: source.command.clone(),
|
|
|
|
|
retry: false,
|
|
|
|
|
keep_alive: false,
|
|
|
|
|
retry_delay: 0,
|
|
|
|
|
..Default::default()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
@ -221,12 +341,13 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn component_by_name() {
|
|
|
|
|
let project = Project::from_str(
|
|
|
|
|
let project = Project::load_str(
|
|
|
|
|
r#"
|
|
|
|
|
components:
|
|
|
|
|
test-component: {}
|
|
|
|
|
"#,
|
|
|
|
|
);
|
|
|
|
|
components:
|
|
|
|
|
test-component: {}
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let jobs = project.get_by_name("test-component").unwrap();
|
|
|
|
|
assert_eq!(jobs.len(), 1);
|
|
|
|
@ -234,12 +355,14 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn project_task() {
|
|
|
|
|
let project = Project::from_str(
|
|
|
|
|
let project = Project::load_str(
|
|
|
|
|
r#"
|
|
|
|
|
tasks:
|
|
|
|
|
- name: task1
|
|
|
|
|
"#,
|
|
|
|
|
);
|
|
|
|
|
tasks:
|
|
|
|
|
task1:
|
|
|
|
|
command: pwd
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let jobs = project.get_by_name("task1").unwrap();
|
|
|
|
|
assert_eq!(jobs.len(), 1);
|
|
|
|
@ -247,14 +370,16 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn component_task() {
|
|
|
|
|
let project = Project::from_str(
|
|
|
|
|
let project = Project::load_str(
|
|
|
|
|
r#"
|
|
|
|
|
components:
|
|
|
|
|
c1:
|
|
|
|
|
tasks:
|
|
|
|
|
- name: task1
|
|
|
|
|
"#,
|
|
|
|
|
);
|
|
|
|
|
components:
|
|
|
|
|
c1:
|
|
|
|
|
tasks:
|
|
|
|
|
task1:
|
|
|
|
|
command: pwd
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let jobs = project.get_by_name("c1:task1").unwrap();
|
|
|
|
|
assert_eq!(jobs.len(), 1);
|
|
|
|
@ -262,16 +387,17 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn component_dependent_absolute_component_task() {
|
|
|
|
|
let project = Project::from_str(
|
|
|
|
|
let project = Project::load_str(
|
|
|
|
|
r#"
|
|
|
|
|
components:
|
|
|
|
|
main-cmp:
|
|
|
|
|
before:
|
|
|
|
|
- main-cmp:sub-task
|
|
|
|
|
tasks:
|
|
|
|
|
- name: sub-task
|
|
|
|
|
"#,
|
|
|
|
|
);
|
|
|
|
|
components:
|
|
|
|
|
main-cmp:
|
|
|
|
|
before: main-cmp:sub-task
|
|
|
|
|
tasks:
|
|
|
|
|
sub-task:
|
|
|
|
|
command: pwd
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let jobs = project.get_by_name("main-cmp").unwrap();
|
|
|
|
|
assert_eq!(jobs.len(), 2);
|
|
|
|
@ -280,16 +406,18 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn component_dependent_relative() {
|
|
|
|
|
let project = Project::from_str(
|
|
|
|
|
let project = Project::load_str(
|
|
|
|
|
r#"
|
|
|
|
|
components:
|
|
|
|
|
main-cmp:
|
|
|
|
|
before:
|
|
|
|
|
- sub-task
|
|
|
|
|
tasks:
|
|
|
|
|
- name: sub-task
|
|
|
|
|
"#,
|
|
|
|
|
);
|
|
|
|
|
components:
|
|
|
|
|
main-cmp:
|
|
|
|
|
before:
|
|
|
|
|
- sub-task
|
|
|
|
|
tasks:
|
|
|
|
|
sub-task:
|
|
|
|
|
command: pwd
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let jobs = project.get_by_name("main-cmp").unwrap();
|
|
|
|
|
assert_eq!(jobs.len(), 2);
|
|
|
|
@ -298,29 +426,33 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn complicated_dependencies() {
|
|
|
|
|
let project = Project::from_str(
|
|
|
|
|
let project = Project::load_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
|
|
|
|
|
"#,
|
|
|
|
|
);
|
|
|
|
|
components:
|
|
|
|
|
ui:
|
|
|
|
|
before:
|
|
|
|
|
- build-ui
|
|
|
|
|
tasks:
|
|
|
|
|
build-ui:
|
|
|
|
|
command: pwd
|
|
|
|
|
server:
|
|
|
|
|
before:
|
|
|
|
|
- setup
|
|
|
|
|
tasks:
|
|
|
|
|
setup:
|
|
|
|
|
command: pwd
|
|
|
|
|
build:
|
|
|
|
|
command: pwd
|
|
|
|
|
before:
|
|
|
|
|
- server:setup
|
|
|
|
|
tasks:
|
|
|
|
|
build:
|
|
|
|
|
before:
|
|
|
|
|
- ui:build-ui
|
|
|
|
|
- server:build
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let jobs = project.get_by_name("build").unwrap();
|
|
|
|
|
assert_eq!(jobs.get(0).unwrap().name, "ui:build-ui");
|
|
|
|
@ -332,17 +464,18 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn component_env() {
|
|
|
|
|
let project = Project::from_str(
|
|
|
|
|
let project = Project::load_str(
|
|
|
|
|
r#"
|
|
|
|
|
env:
|
|
|
|
|
foo: one
|
|
|
|
|
sub: two
|
|
|
|
|
components:
|
|
|
|
|
main-cmp:
|
|
|
|
|
env:
|
|
|
|
|
sub: three
|
|
|
|
|
"#,
|
|
|
|
|
);
|
|
|
|
|
env:
|
|
|
|
|
foo: one
|
|
|
|
|
sub: two
|
|
|
|
|
components:
|
|
|
|
|
main-cmp:
|
|
|
|
|
env:
|
|
|
|
|
sub: three
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let jobs = project.get_by_name("main-cmp").unwrap();
|
|
|
|
|
let job = jobs.first().unwrap();
|
|
|
|
@ -352,24 +485,25 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn task_env() {
|
|
|
|
|
let project = Project::from_str(
|
|
|
|
|
let project = Project::load_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();
|
|
|
|
|
env:
|
|
|
|
|
root: ten
|
|
|
|
|
foo: one
|
|
|
|
|
sub: two
|
|
|
|
|
components:
|
|
|
|
|
main-cmp:
|
|
|
|
|
env:
|
|
|
|
|
sub: three
|
|
|
|
|
tasks:
|
|
|
|
|
subtask:
|
|
|
|
|
env:
|
|
|
|
|
foo: four
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let jobs = project.get_by_name("main-cmp:subtask").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")));
|
|
|
|
@ -378,40 +512,76 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn group() {
|
|
|
|
|
let project = Project::from_str(
|
|
|
|
|
let project = Project::load_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);
|
|
|
|
|
groups:
|
|
|
|
|
all:
|
|
|
|
|
components:
|
|
|
|
|
- ui
|
|
|
|
|
- server
|
|
|
|
|
components:
|
|
|
|
|
ui:
|
|
|
|
|
before:
|
|
|
|
|
- build-ui
|
|
|
|
|
tasks:
|
|
|
|
|
build-ui:
|
|
|
|
|
command: pwd
|
|
|
|
|
server:
|
|
|
|
|
before:
|
|
|
|
|
- setup
|
|
|
|
|
tasks:
|
|
|
|
|
setup:
|
|
|
|
|
command: pwd
|
|
|
|
|
build:
|
|
|
|
|
command: pwd
|
|
|
|
|
before:
|
|
|
|
|
- server:setup
|
|
|
|
|
tasks:
|
|
|
|
|
build:
|
|
|
|
|
before:
|
|
|
|
|
- ui:build-ui
|
|
|
|
|
- server:build
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let plan = project.get_runplan("all").unwrap();
|
|
|
|
|
assert_eq!(plan[0][0].name, "ui:build-ui");
|
|
|
|
|
assert_eq!(plan[0][1].name, "ui");
|
|
|
|
|
assert_eq!(plan[1][0].name, "server:setup");
|
|
|
|
|
assert_eq!(plan[1][1].name, "server");
|
|
|
|
|
assert_eq!(plan.len(), 2);
|
|
|
|
|
assert_eq!(plan[0].len(), 2);
|
|
|
|
|
assert_eq!(plan[1].len(), 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
pub fn shorthand_single_command() {
|
|
|
|
|
let project = Project::load_str(
|
|
|
|
|
r#"
|
|
|
|
|
components:
|
|
|
|
|
test:
|
|
|
|
|
command: pwd
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let jobs = project.get_by_name("test").unwrap();
|
|
|
|
|
assert_eq!(jobs[0].command.to_string(), String::from("pwd"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
pub fn multiple_commands() {
|
|
|
|
|
let project = Project::load_str(
|
|
|
|
|
r#"
|
|
|
|
|
components:
|
|
|
|
|
test:
|
|
|
|
|
command:
|
|
|
|
|
- sleep 1
|
|
|
|
|
- pwd
|
|
|
|
|
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let jobs = project.get_by_name("test").unwrap();
|
|
|
|
|
assert_eq!(jobs[0].command.to_string(), String::from("sleep 1 && pwd"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|