mod new;
mod valid;
use crate::context::GlobalParams;
use crate::error::{Error, ErrorKind, Result};
use crate::jinja::{
is_render_string, merge_option, render, render_force_string, render_map, render_string,
};
use crate::modules::{Module, ModuleResult};
use crate::task::new::TaskNew;
use rash_derive::FieldNames;
use std::process::exit;
use std::result::Result as StdResult;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use minijinja::{Value, context};
use nix::sys::wait::{WaitStatus, waitpid};
use nix::unistd::{ForkResult, Uid, User, fork, setgid, setuid};
use serde_error::Error as SerdeError;
use serde_norway::Value as YamlValue;
#[derive(Debug, Clone, FieldNames)]
pub struct Task<'a> {
r#become: bool,
become_user: String,
check_mode: bool,
module: &'static dyn Module,
params: YamlValue,
changed_when: Option<String>,
ignore_errors: Option<bool>,
name: Option<String>,
r#loop: Option<YamlValue>,
register: Option<String>,
vars: Option<YamlValue>,
when: Option<String>,
rescue: Option<YamlValue>,
always: Option<YamlValue>,
global_params: &'a GlobalParams<'a>,
}
pub type Tasks<'a> = Vec<Task<'a>>;
impl<'a> Task<'a> {
pub fn new(yaml: &YamlValue, global_params: &'a GlobalParams) -> Result<Self> {
trace!("new task: {yaml:?}");
TaskNew::from(yaml)
.validate_attrs()?
.get_task(global_params)
}
#[inline(always)]
fn is_attr(attr: &str) -> bool {
Self::get_field_names().contains(attr)
}
#[inline(always)]
fn extend_vars(&self, additional_vars: Value) -> Result<Value> {
match self.vars.clone() {
Some(v) => {
trace!("extend vars: {:?}", &v);
let rendered_value = match render(v.clone(), &additional_vars) {
Ok(v) => Value::from_serialize(v),
Err(e) if e.kind() == ErrorKind::OmitParam => context! {},
Err(e) => return Err(e),
};
Ok(context! { ..rendered_value, ..additional_vars})
}
None => Ok(additional_vars),
}
}
fn render_params(&self, vars: Value) -> Result<YamlValue> {
let extended_vars = self.extend_vars(vars)?;
let original_params = self.params.clone();
match original_params {
YamlValue::Mapping(x) => render_map(
x.clone(),
&extended_vars,
self.module.force_string_on_params(),
),
YamlValue::String(s) => Ok(YamlValue::String(render_string(&s, &extended_vars)?)),
YamlValue::Sequence(_) => {
if self.module.get_name() == "block" {
Ok(original_params)
} else {
Err(Error::new(
ErrorKind::InvalidData,
format!("{original_params:?} must be a mapping or a string"),
))
}
}
_ => Err(Error::new(
ErrorKind::InvalidData,
format!("{original_params:?} must be a mapping or a string"),
)),
}
}
fn is_exec(&self, vars: &Value) -> Result<bool> {
trace!("when: {:?}", &self.when);
match &self.when {
Some(s) => {
let extended_vars = self.extend_vars(vars.clone())?;
is_render_string(s, &extended_vars)
}
None => Ok(true),
}
}
fn get_iterator(value: &YamlValue, vars: Value) -> Result<Vec<YamlValue>> {
match value.as_sequence() {
Some(v) => Ok(v
.iter()
.map(|item| render_force_string(item.clone(), &vars))
.collect::<Result<Vec<YamlValue>>>()?),
None => Err(Error::new(ErrorKind::NotFound, "loop is not iterable")),
}
}
fn render_iterator(&self, vars: Value) -> Result<Vec<YamlValue>> {
let loop_some = self.r#loop.clone().unwrap();
let extended_vars = self.extend_vars(context! {item => "",..vars})?;
match loop_some.as_str() {
Some(s) => {
let value: YamlValue = serde_norway::from_str(&render_string(s, &extended_vars)?)?;
match value.as_str() {
Some(_) => Ok(vec![value]),
None => Task::get_iterator(&value, extended_vars),
}
}
None => Task::get_iterator(&loop_some, extended_vars),
}
}
fn is_changed(&self, result: &ModuleResult, vars: &Value) -> Result<bool> {
trace!("changed_when: {:?}", &self.changed_when);
match &self.changed_when {
Some(s) => is_render_string(s, vars),
None => Ok(result.get_changed()),
}
}
fn exec_module_rendered_with_user(
&self,
rendered_params: &YamlValue,
vars: &Value,
user: User,
) -> Result<Option<Value>> {
match setgid(user.gid) {
Ok(_) => match setuid(user.uid) {
Ok(_) => self.exec_module_rendered(rendered_params, vars),
Err(_) => Err(Error::new(
ErrorKind::Other,
format!("gid cannot be changed to {}", user.gid),
)),
},
Err(_) => Err(Error::new(
ErrorKind::Other,
format!("uid cannot be changed to {}", user.uid),
)),
}
}
fn exec_module_rendered(
&self,
rendered_params: &YamlValue,
vars: &Value,
) -> Result<Option<Value>> {
let module_name = self.module.get_name();
let extended_vars = self.extend_vars(vars.clone())?;
match self.module.exec(
self.global_params,
rendered_params.clone(),
&extended_vars,
self.check_mode,
) {
Ok((result, result_vars)) => {
if module_name != "include" && module_name != "block" {
let output = result.get_output();
let target = match self.is_changed(&result, &extended_vars)? {
true => "changed",
false => "ok",
};
let target_empty =
&format!("{}{}", target, if output.is_none() { "_empty" } else { "" });
info!(target: target_empty,
"{}",
output.unwrap_or_else(
|| "".to_owned()
)
);
}
let register_vars = self.register.clone().map(|register| {
trace!("register {:?} in {:?}", &result, register);
[(register, Value::from_serialize(&result))]
.into_iter()
.collect::<Value>()
});
let new_vars_value = [result_vars, register_vars]
.into_iter()
.fold(context! {}, merge_option);
let new_vars = if new_vars_value == context! {} {
None
} else {
Some(new_vars_value)
};
Ok(new_vars)
}
Err(e) => match self.ignore_errors {
Some(is_true) if is_true => {
info!(target: "ignoring", "{e}");
Ok(None)
}
_ => Err(e),
},
}
}
fn exec_module(&self, vars: Value) -> Result<Option<Value>> {
if self.is_exec(&vars)? {
let rendered_params = self.render_params(vars.clone())?;
match self.r#become {
true => {
let user_not_found_error = || {
Error::new(
ErrorKind::Other,
format!("User {:?} not found.", self.become_user),
)
};
let user = match User::from_name(&self.become_user)
.map_err(|_| user_not_found_error())?
{
Some(user) => Ok(user),
None => match self.become_user.parse::<u32>().map(Uid::from_raw) {
Ok(uid) => match User::from_uid(uid)? {
Some(user) => Ok(user),
None => Err(user_not_found_error()),
},
Err(_) => Err(user_not_found_error()),
},
}?;
if user.uid != Uid::current() {
if self.module.get_name() == "command"
&& rendered_params["transfer_pid"].as_bool().unwrap_or(false)
{
return self.exec_module_rendered_with_user(
&rendered_params,
&vars,
user,
);
}
#[allow(clippy::type_complexity)]
let (tx, rx): (
IpcSender<StdResult<String, SerdeError>>,
IpcReceiver<StdResult<String, SerdeError>>,
) = ipc::channel().map_err(|e| Error::new(ErrorKind::Other, e))?;
match unsafe { fork() } {
Ok(ForkResult::Child) => {
trace!("change uid to: {}", user.uid);
trace!("change gid to: {}", user.gid);
let result = self.exec_module_rendered_with_user(
&rendered_params,
&vars,
user,
);
trace!("send result: {result:?}");
tx.send(
result
.map(|v| serde_json::to_string(&v))?
.map_err(|e| SerdeError::new(&e)),
)
.unwrap_or_else(|e| {
error!("child failed to send result: {e}");
exit(1)
});
exit(0);
}
Ok(ForkResult::Parent { child, .. }) => {
match waitpid(child, None) {
Ok(WaitStatus::Exited(_, 0)) => Ok(()),
Ok(WaitStatus::Exited(_, exit_code)) => Err(Error::new(
ErrorKind::SubprocessFail,
format!("child failed with exit_code {exit_code}"),
)),
Err(e) => Err(Error::new(ErrorKind::Other, e)),
_ => Err(Error::new(
ErrorKind::SubprocessFail,
format!("child {child} unknown status"),
)),
}?;
trace!("receive result");
rx.recv()
.map_err(|e| Error::new(ErrorKind::Other, format!("{e:?}")))
.and_then(|result| {
result.map_err(|e| {
Error::new(ErrorKind::Other, format!("{e:?}"))
})
})
.and_then(|s| {
serde_json::from_str::<Option<Value>>(&s)
.map_err(|e| Error::new(ErrorKind::Other, e))
})
}
Err(e) => Err(Error::new(ErrorKind::Other, e)),
}
} else {
self.exec_module_rendered(&rendered_params, &vars)
}
}
false => self.exec_module_rendered(&rendered_params, &vars),
}
} else {
debug!("skipping");
Ok(None)
}
}
pub fn exec(&self, vars: Value) -> Result<Option<Value>> {
debug!("Module: {}", self.module.get_name());
debug!("Params: {:?}", self.params);
if self.rescue.is_some() || self.always.is_some() {
return self.exec_with_rescue_always(vars);
}
self.exec_main_task(vars)
}
pub fn get_name(&self) -> Option<String> {
self.name.clone()
}
pub fn get_rendered_name(&self, vars: Value) -> Result<String> {
render_string(
&self
.name
.clone()
.ok_or_else(|| Error::new(ErrorKind::NotFound, "no name found"))?,
&vars,
)
}
pub fn get_module(&self) -> &dyn Module {
self.module
}
fn exec_with_rescue_always(&self, vars: Value) -> Result<Option<Value>> {
let initial_vars = vars;
let (main_result, main_vars) = match self.exec_main_task(initial_vars.clone()) {
Ok(success_vars) => {
trace!("Main task execution succeeded");
(Ok(()), success_vars)
}
Err(task_error) => {
warn!("Main task execution failed: {task_error}");
(Err(task_error), None)
}
};
let post_main_vars = merge_option(initial_vars, main_vars.clone());
let (rescue_result, rescue_vars) = match (&main_result, &self.rescue) {
(Err(_), Some(rescue_tasks)) => {
info!("Executing rescue tasks due to main task failure");
match self.execute_task_sequence(rescue_tasks, post_main_vars.clone()) {
Ok(rescue_vars) => {
info!("Rescue tasks executed successfully");
(Ok(()), rescue_vars)
}
Err(rescue_error) => {
error!("Rescue tasks failed: {rescue_error}");
(Err(rescue_error), None)
}
}
}
(Err(_), None) => {
trace!("Main task failed but no rescue tasks defined");
(Ok(()), main_vars.clone()) }
(Ok(_), _) => {
trace!("Main task succeeded, skipping rescue tasks");
(Ok(()), main_vars.clone()) }
};
let post_rescue_vars = merge_option(post_main_vars, rescue_vars.clone());
let always_vars = match &self.always {
Some(always_tasks) => {
trace!("Executing always tasks");
match self.execute_task_sequence(always_tasks, post_rescue_vars) {
Ok(always_vars) => {
trace!("Always tasks executed successfully");
always_vars
}
Err(always_error) => {
error!("Always tasks failed: {always_error}");
return Err(Error::new(
ErrorKind::Other,
format!("Always section failed: {always_error}"),
));
}
}
}
None => {
trace!("No always tasks to execute");
None
}
};
let all_vars_value = [main_vars, rescue_vars, always_vars]
.into_iter()
.fold(context! {}, merge_option);
let all_vars = if all_vars_value == context! {} {
None
} else {
Some(all_vars_value)
};
match (&main_result, &rescue_result) {
(Ok(_), Ok(_)) => {
Ok(all_vars)
}
(Ok(_), Err(_)) => {
warn!("Unexpected state: main task succeeded but rescue reported failure");
Ok(all_vars)
}
(Err(_main_error), Ok(_)) => {
debug!("Task execution recovered through rescue tasks");
Ok(all_vars)
}
(Err(main_error), Err(_)) => {
if self.rescue.is_some() {
Err(Error::new(
ErrorKind::Other,
format!(
"Task execution failed and rescue tasks could not recover: {main_error}"
),
))
} else {
Err(Error::new(
ErrorKind::Other,
format!("Task execution failed with no rescue defined: {main_error}"),
))
}
}
}
}
fn execute_task_sequence(&self, tasks_yaml: &YamlValue, vars: Value) -> Result<Option<Value>> {
match tasks_yaml {
YamlValue::Sequence(tasks) => {
if tasks.is_empty() {
warn!("Empty task sequence provided");
return Ok(None);
}
let mut current_vars = vars;
let mut current_new_vars = context! {};
for (index, task_yaml) in tasks.iter().enumerate() {
match Task::new(task_yaml, self.global_params) {
Ok(task) => {
info!(target: "task",
"[{}:{}] - ",
current_vars.get_attr("rash")?.get_attr("path")?,
task.get_rendered_name(current_vars.clone())
.unwrap_or_else(|_| task.get_module().get_name().to_owned()),
);
match task.exec(current_vars.clone()) {
Ok(new_vars) => {
current_vars = context! {..current_vars, ..new_vars.clone()};
current_new_vars =
context! {..current_new_vars, ..new_vars.clone()};
trace!("Task {index} in sequence completed successfully");
}
Err(task_error) => {
error!("Task {index} in sequence failed: {task_error}");
return Err(Error::new(
ErrorKind::Other,
format!(
"Task sequence failed at index {index}: {task_error}"
),
));
}
}
}
Err(parse_error) => {
error!("Failed to parse task {index} in sequence: {parse_error}");
return Err(Error::new(
ErrorKind::InvalidData,
format!("Invalid task at index {index}: {parse_error}"),
));
}
}
}
Ok(Some(current_new_vars))
}
_ => Err(Error::new(
ErrorKind::InvalidData,
format!("Task sequence must be a YAML array, got: {tasks_yaml:?}"),
)),
}
}
#[deprecated(note = "Use execute_task_sequence instead for better error handling")]
#[allow(dead_code)]
fn execute_rescue_tasks(&self, rescue_tasks: &YamlValue, vars: Value) -> Result<Option<Value>> {
self.execute_task_sequence(rescue_tasks, vars)
}
#[deprecated(note = "Use execute_task_sequence instead for better error handling")]
#[allow(dead_code)]
fn execute_always_tasks(&self, always_tasks: &YamlValue, vars: Value) -> Result<Option<Value>> {
self.execute_task_sequence(always_tasks, vars)
}
#[deprecated(note = "Use execute_task_sequence instead for better error handling")]
#[allow(dead_code)]
fn execute_task_list(&self, tasks_yaml: &YamlValue, vars: Value) -> Result<Option<Value>> {
self.execute_task_sequence(tasks_yaml, vars)
}
fn exec_main_task(&self, vars: Value) -> Result<Option<Value>> {
if self.r#loop.is_some() {
let mut new_vars = context! {};
for item in self.render_iterator(vars.clone())?.into_iter() {
let ctx = context! {..vars.clone(), ..new_vars.clone()};
let new_ctx = context! {item => &item, ..ctx};
trace!("pre execute loop: {:?}", &new_ctx);
let loop_vars = self.exec_module(new_ctx)?;
if let Some(v) = loop_vars {
new_vars = context! {..new_vars, ..v};
}
trace!("post execute loop: {:?}", &new_vars);
}
if new_vars == context! {} {
Ok(None)
} else {
Ok(Some(new_vars))
}
} else {
self.exec_module(vars)
}
}
}
#[cfg(test)]
use crate::context::GLOBAL_PARAMS;
#[cfg(test)]
impl From<YamlValue> for Task<'_> {
fn from(value: YamlValue) -> Self {
TaskNew::from(&value)
.validate_attrs()
.unwrap()
.get_task(&GLOBAL_PARAMS)
.unwrap()
}
}
pub fn parse_file<'a>(tasks_file: &str, global_params: &'a GlobalParams) -> Result<Tasks<'a>> {
let tasks: Vec<YamlValue> = serde_norway::from_str(tasks_file)?;
tasks
.into_iter()
.map(|task| Task::new(&task, global_params))
.collect::<Result<Tasks>>()
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use minijinja::context;
#[test]
fn test_from_yaml() {
let s: String = r#"
name: 'Test task'
command: 'example'
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert_eq!(task.name.unwrap(), "Test task");
assert_eq!(&task.module.get_name(), &"command");
}
#[test]
fn test_from_yaml_no_module() {
let s = r#"
name: 'Test task'
no_module: 'example'
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task_err = Task::new(&yaml, &GlobalParams::default()).unwrap_err();
assert_eq!(task_err.kind(), ErrorKind::InvalidData);
}
#[test]
fn test_from_yaml_invalid_attr() {
let s = r#"
name: 'Test task'
command: 'example'
invalid_attr: 'foo'
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task_err = Task::new(&yaml, &GlobalParams::default()).unwrap_err();
assert_eq!(task_err.kind(), ErrorKind::InvalidData);
}
#[test]
fn test_is_exec() {
let s: String = r#"
when: "boo == 'test'"
command: 'example'
"#
.to_owned();
let vars = context! { boo => "test"};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(task.is_exec(&vars).unwrap());
}
#[test]
fn test_is_exec_parsed_bool() {
let s: String = r#"
when: "boo | bool"
command: 'example'
"#
.to_owned();
let vars = Value::from_serialize(vec![("boo", "false")]);
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(!task.is_exec(&vars).unwrap());
}
#[test]
fn test_is_exec_false() {
let s: String = r#"
when: "boo != 'test'"
command: 'example'
"#
.to_owned();
let vars = context! { boo => "test"};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(!task.is_exec(&vars).unwrap());
}
#[test]
fn test_is_exec_bool_false() {
let s: String = r#"
when: false
command: 'example'
"#
.to_owned();
let vars = context! {};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(!task.is_exec(&vars).unwrap());
}
#[test]
fn test_is_exec_array() {
let s: String = r#"
when:
- true
- "boo == 'test'"
command: 'example'
"#
.to_owned();
let vars = context! { boo => "test"};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(task.is_exec(&vars).unwrap());
}
#[test]
fn test_is_exec_array_one_false() {
let s: String = r#"
when:
- false
- "boo == 'test'"
command: 'example'
"#
.to_owned();
let vars = context! { boo => "test"};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(!task.is_exec(&vars).unwrap());
let s: String = r#"
command: 'example'
when:
- true
- false
- true
"#
.to_owned();
let vars = context! {};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(!task.is_exec(&vars).unwrap());
let s: String = r#"
command: 'example'
when:
- true
- true
- true
"#
.to_owned();
let vars = context! {};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(task.is_exec(&vars).unwrap());
let s: String = r#"
command: 'example'
when:
- true or true or true
- false
- true
"#
.to_owned();
let vars = context! {};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(!task.is_exec(&vars).unwrap());
}
#[test]
fn test_is_exec_array_with_or_operator() {
let s: String = r#"
command: 'example'
when:
- true
- boo == 'test' or false
"#
.to_owned();
let vars = context! { boo => "test"};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(task.is_exec(&vars).unwrap());
}
#[test]
fn test_render_iterator() {
let s: String = r#"
command: 'example'
loop:
- 1
- 2
- 3
"#
.to_owned();
let vars = context! {};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert_eq!(
task.render_iterator(vars).unwrap(),
vec![YamlValue::from(1), YamlValue::from(2), YamlValue::from(3)]
);
}
#[test]
fn test_is_changed() {
let s: String = r#"
changed_when: "boo == 'test'"
command: 'example'
"#
.to_owned();
let vars = context! { boo => "test" };
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(
task.is_changed(&ModuleResult::new(false, None, None), &vars)
.unwrap(),
);
}
#[test]
fn test_is_changed_bool_true() {
let s: String = r#"
changed_when: true
command: 'example'
"#
.to_owned();
let vars = context! { boo => "test"};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(
task.is_changed(&ModuleResult::new(false, None, None), &vars)
.unwrap(),
);
}
#[test]
fn test_is_changed_bool_false() {
let s: String = r#"
changed_when: false
command: 'example'
"#
.to_owned();
let vars = context! { boo => "test"};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(
!task
.is_changed(&ModuleResult::new(true, None, None), &vars)
.unwrap(),
);
}
#[test]
fn test_is_changed_string_false() {
let s: String = r#"
changed_when: "false"
command: 'example'
"#
.to_owned();
let vars = context! { boo => "test"};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(
!task
.is_changed(&ModuleResult::new(false, None, None), &vars)
.unwrap(),
);
}
#[test]
fn test_is_changed_false() {
let s: String = r#"
changed_when: "boo != 'test'"
command: 'example'
"#
.to_owned();
let vars = context! { boo => "test"};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(
!task
.is_changed(&ModuleResult::new(false, None, None), &vars)
.unwrap(),
);
}
#[test]
fn test_is_changed_array() {
let s: String = r#"
changed_when:
- "boo == 'test'"
- true
command: 'example'
"#
.to_owned();
let vars = context! { boo => "test"};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(
task.is_changed(&ModuleResult::new(false, None, None), &vars)
.unwrap(),
);
}
#[test]
fn test_is_changed_array_false() {
let s: String = r#"
changed_when:
- "boo == 'test'"
- false
command: 'example'
"#
.to_owned();
let vars = context! { boo => "test"};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert!(
!task
.is_changed(&ModuleResult::new(true, None, None), &vars)
.unwrap(),
);
}
#[test]
fn test_when_in_loop() {
let s: String = r#"
command: echo 'example'
loop:
- 1
- 2
- 3
when: item == 1
"#
.to_owned();
let vars = context! {};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
let result = task.exec(vars).unwrap();
assert_eq!(result, None);
}
#[test]
fn test_render_iterator_var() {
let s: String = r#"
command: 'example'
loop: "{{ range(3) }}"
"#
.to_owned();
let vars = context! { boo => "test"};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert_eq!(
task.render_iterator(vars).unwrap(),
vec![YamlValue::from(0), YamlValue::from(1), YamlValue::from(2)]
);
}
#[test]
fn test_render_iterator_list_with_vars() {
let s: String = r#"
command: 'example'
loop:
- "{{ boo }}"
- 2
"#
.to_owned();
let vars = context! { boo => "test"};
let yaml: YamlValue = serde_norway::from_str(&s).unwrap();
let task = Task::from(yaml);
assert_eq!(
task.render_iterator(vars).unwrap(),
vec![YamlValue::from("test"), YamlValue::from(2)]
);
}
#[test]
fn test_task_execute() {
let s0 = r#"
name: task 1
command: echo foo
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s0).unwrap();
let task = Task::from(yaml);
let vars = context! {};
let result = task.exec(vars.clone()).unwrap();
assert_eq!(result, None);
}
#[test]
fn test_task_execute_register() {
let s0 = r#"
name: task 1
command: echo foo
register: yea
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s0).unwrap();
let task = Task::from(yaml);
let vars = context! {};
let result = task.exec(vars.clone()).unwrap();
assert!(
result
.unwrap()
.get_attr("yea")
.map(|x| !x.is_undefined())
.unwrap()
);
}
#[test]
fn test_read_tasks() {
let file = r#"
#!/bin/rash
- name: task 1
command:
foo: boo
- name: task 2
command: boo
"#;
let global_params = GlobalParams::default();
let tasks = parse_file(file, &global_params).unwrap();
assert_eq!(tasks.len(), 2);
let s0 = r#"
name: task 1
command:
foo: boo
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s0).unwrap();
let task_0 = Task::from(yaml);
assert_eq!(tasks[0].name, task_0.name);
assert_eq!(tasks[0].params, task_0.params);
assert_eq!(tasks[0].module.get_name(), task_0.module.get_name());
let s1 = r#"
name: task 2
command: boo
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s1).unwrap();
let task_1 = Task::from(yaml);
assert_eq!(tasks[1].name, task_1.name);
assert_eq!(tasks[1].params, task_1.params);
assert_eq!(tasks[1].module.get_name(), task_1.module.get_name());
}
#[test]
fn test_render_params() {
let s0 = r#"
name: task 1
command:
cmd: ls {{ directory }}
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s0).unwrap();
let task = Task::from(yaml);
let vars = Value::from_serialize(
[("directory", "boo"), ("xuu", "zoo")]
.iter()
.cloned()
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.collect::<HashMap<String, String>>(),
);
let rendered_params = task.render_params(vars).unwrap();
assert_eq!(rendered_params["cmd"].as_str().unwrap(), "ls boo");
}
#[test]
fn test_render_params_with_vars() {
let s0 = r#"
name: task 1
command:
cmd: ls {{ foo }}
vars:
foo: boo
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s0).unwrap();
let task = Task::from(yaml);
let vars = context! {};
let rendered_params = task.render_params(vars).unwrap();
assert_eq!(rendered_params["cmd"].as_str().unwrap(), "ls boo");
}
#[test]
fn test_render_params_with_render_vars() {
let s0 = r#"
name: task 1
command:
cmd: ls {{ foo }}
vars:
foo: '{{ directory }}'
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s0).unwrap();
let task = Task::from(yaml);
let vars = context! {
directory => "boo",
xuu => "zoo",
};
let rendered_params = task.render_params(vars).unwrap();
assert_eq!(rendered_params["cmd"].as_str().unwrap(), "ls boo");
}
#[test]
fn test_render_params_with_concat_render_vars() {
let s0 = r#"
name: task 1
command:
cmd: ls {{ foo }}
vars:
boo: '{{ directory }}'
foo: '{{ boo }}'
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s0).unwrap();
let task = Task::from(yaml);
let vars = context! {
directory => "boo",
xuu => "zoo",
};
let rendered_params = task.render_params(vars).unwrap();
assert_eq!(rendered_params["cmd"].as_str().unwrap(), "ls boo");
}
#[test]
fn test_render_params_with_vars_array_not_valid() {
let s0 = r#"
name: task 1
command:
cmd: ls {{ foo }}
vars:
- foo: boo
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s0).unwrap();
let task = Task::from(yaml);
let vars = context! {};
let rendered_params_err = task.render_params(vars).unwrap_err();
assert_eq!(rendered_params_err.kind(), ErrorKind::JinjaRenderError);
}
#[test]
fn test_render_params_with_vars_array_concat() {
let s0 = r#"
name: task 1
command:
cmd: echo {{ (boo + buu) | join(' ') }}
vars:
boo:
- 1
- 23
buu:
- 13
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s0).unwrap();
let task = Task::from(yaml);
let vars = context! {};
let rendered_params = task.render_params(vars).unwrap();
assert_eq!(rendered_params["cmd"].as_str().unwrap(), "echo 1 23 13");
}
#[test]
fn test_render_params_with_vars_array_concat_in_vars() {
let s0 = r#"
name: task 1
command:
cmd: echo {{ all | join(' ') }}
vars:
all: '{{ boo + buu }}'
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s0).unwrap();
let task = Task::from(yaml);
let vars = context! {boo => &[1, 23], buu => &[13]};
let rendered_params = task.render_params(vars).unwrap();
assert_eq!(rendered_params["cmd"].as_str().unwrap(), "echo 1 23 13");
}
#[test]
fn test_render_params_with_vars_array_concat_in_vars_recursive() {
let s0 = r#"
name: task 1
command:
cmd: echo {{ all | join(' ') }}
vars:
boo:
- 1
- 23
buu:
- 13
all: '{{ boo + buu }}'
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s0).unwrap();
let task = Task::from(yaml);
let vars = context! {};
let rendered_params = task.render_params(vars).unwrap();
assert_eq!(rendered_params["cmd"].as_str().unwrap(), "echo 1 23 13");
}
#[test]
fn test_render_params_no_hash_map() {
let s0 = r#"
name: task 1
command: ls {{ directory }}
"#
.to_owned();
let yaml: YamlValue = serde_norway::from_str(&s0).unwrap();
let task = Task::from(yaml);
let vars = context! {
directory => "boo",
xuu => "zoo",
};
let rendered_params = task.render_params(vars).unwrap();
assert_eq!(rendered_params.as_str().unwrap(), "ls boo");
}
}