use std::sync::{Arc,RwLock};
use std::path::PathBuf;
use crate::tasks::request::TaskRequest;
use crate::tasks::response::TaskResponse;
use crate::inventory::hosts::Host;
use crate::playbooks::traversal::RunState;
use crate::playbooks::context::PlaybookContext;
use crate::tasks::cmd_library::{screen_path,screen_general_input_strict};
use crate::handle::response::Response;
use crate::playbooks::templar::{Templar,TemplateMode};
#[derive(Eq,Hash,PartialEq,Clone,Copy,Debug)]
pub enum BlendTarget {
NotTemplateModule,
TemplateModule,
}
#[derive(Eq,Hash,PartialEq,Clone,Copy,Debug)]
pub enum Safety {
Safe,
Unsafe
}
pub struct Template {
run_state: Arc<RunState>,
host: Arc<RwLock<Host>>,
response: Arc<Response>,
detached_templar: Templar
}
impl Template {
pub fn new(run_state: Arc<RunState>, host: Arc<RwLock<Host>>, response:Arc<Response>) -> Self {
Self {
run_state,
host,
response,
detached_templar: Templar::new()
}
}
pub fn get_context(&self) -> Arc<RwLock<PlaybookContext>> {
return Arc::clone(&self.run_state.context);
}
fn unwrap_string_result(&self, request: &Arc<TaskRequest>, str_result: &Result<String,String>) -> Result<String, Arc<TaskResponse>> {
return match str_result {
Ok(x) => Ok(x.clone()),
Err(y) => {
return Err(self.response.is_failed(request, &y.clone()));
}
};
}
fn template_unsafe_internal(&self, request: &Arc<TaskRequest>, tm: TemplateMode, _field: &String, template: &String, blend_target: BlendTarget) -> Result<String,Arc<TaskResponse>> {
let result = self.run_state.context.read().unwrap().render_template(template, &self.host, blend_target, tm);
if result.is_ok() {
let result_ok = result.as_ref().unwrap();
if result_ok.eq("") {
return Err(self.response.is_failed(request, &format!("evaluated to empty string")));
}
}
let result2 = self.unwrap_string_result(request, &result)?;
return Ok(result2);
}
pub fn string_for_template_module_use_only(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &String) -> Result<String,Arc<TaskResponse>> {
return self.template_unsafe_internal(request, tm, field, template, BlendTarget::TemplateModule);
}
pub fn string_unsafe_for_shell(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &String) -> Result<String,Arc<TaskResponse>> {
return self.template_unsafe_internal(request, tm, field, template, BlendTarget::NotTemplateModule);
}
fn string_option_unsafe(&self, request: &Arc<TaskRequest>, tm: TemplateMode,field: &String, template: &Option<String>) -> Result<Option<String>,Arc<TaskResponse>> {
if template.is_none() { return Ok(None); }
let result = self.string(request, tm, field, &template.as_ref().unwrap());
return match result {
Ok(x) => Ok(Some(x)),
Err(y) => { Err(self.response.is_failed(request, &format!("field ({}) template error: {:?}", field, y))) }
};
}
pub fn string_option_unsafe_for_shell(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &Option<String>) -> Result<Option<String>,Arc<TaskResponse>> {
return match template.is_none() {
true => Ok(None),
false => Ok(Some(self.template_unsafe_internal(request, tm, field, &template.as_ref().unwrap(), BlendTarget::NotTemplateModule)?))
}
}
pub fn string(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &String) -> Result<String,Arc<TaskResponse>> {
let result = self.string_unsafe_for_shell(request, tm, field, template);
return match result {
Ok(x) => match screen_general_input_strict(&x) {
Ok(y) => Ok(y),
Err(z) => { return Err(self.response.is_failed(request, &format!("field {}, {}", field, z))) }
},
Err(y) => Err(y)
};
}
pub fn string_no_spaces(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &String) -> Result<String,Arc<TaskResponse>> {
let value = self.string(request, tm, field, template)?;
if self.has_spaces(&value) {
return Err(self.response.is_failed(request, &format!("field ({}): spaces are not allowed", field)))
}
return Ok(value.clone());
}
pub fn string_option_no_spaces(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &Option<String>) -> Result<Option<String>,Arc<TaskResponse>> {
let prelim = self.string_option(request, tm, field, template)?;
if prelim.is_some() {
let value = prelim.as_ref().unwrap();
if self.has_spaces(&value) {
return Err(self.response.is_failed(request, &format!("field ({}): spaces are not allowed", field)))
}
}
return Ok(prelim.clone());
}
pub fn string_option_default(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &Option<String>, default: &String) -> Result<String,Arc<TaskResponse>> {
let prelim = self.string_option(request, tm, field, template)?;
match prelim {
Some(x) => Ok(x.clone()),
None => Ok(default.clone())
}
}
pub fn string_option_trim(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &Option<String>) -> Result<Option<String>,Arc<TaskResponse>> {
let prelim = self.string_option(request, tm, field, template)?;
if prelim.is_some() {
return Ok(Some(prelim.unwrap().trim().to_string()));
}
return Ok(None);
}
pub fn no_template_string_option_trim(&self, input: &Option<String>) -> Option<String> {
if input.is_some() {
let value = input.as_ref().unwrap();
return Some(value.trim().to_string());
}
return None;
}
pub fn path(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &String) -> Result<String,Arc<TaskResponse>> {
let result = self.run_state.context.read().unwrap().render_template(template, &self.host, BlendTarget::NotTemplateModule, tm);
let result2 = self.unwrap_string_result(request, &result)?;
return match screen_path(&result2) {
Ok(x) => Ok(x), Err(y) => { return Err(self.response.is_failed(request, &format!("{}, for field {}", y, field))) }
}
}
pub fn string_option(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &Option<String>) -> Result<Option<String>,Arc<TaskResponse>> {
let result = self.string_option_unsafe(request, tm, field, template);
return match result {
Ok(x1) => match x1 {
Some(x) => match screen_general_input_strict(&x) {
Ok(y) => Ok(Some(y)),
Err(z) => { return Err(self.response.is_failed(request, &format!("field {}, {}", field, z))) }
},
None => Ok(None)
},
Err(y) => Err(y)
};
}
#[allow(dead_code)]
pub fn integer(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &String)-> Result<u64,Arc<TaskResponse>> {
if tm == TemplateMode::Off {
return Ok(0);
}
let st = self.string(request, tm, field, template)?;
let num = st.parse::<u64>();
return match num {
Ok(num) => Ok(num),
Err(_err) => Err(self.response.is_failed(request, &format!("field ({}) value is not an integer: {}", field, st)))
}
}
pub fn integer_option(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &Option<String>, default: u64) -> Result<u64,Arc<TaskResponse>> {
if tm == TemplateMode::Off {
return Ok(0);
}
if template.is_none() {
return Ok(default);
}
let st = self.string(request, tm, field, &template.as_ref().unwrap())?;
let num = st.parse::<u64>();
return match num {
Ok(num) => Ok(num),
Err(_err) => Err(self.response.is_failed(request, &format!("field ({}) value is not an integer: {}", field, st)))
}
}
#[allow(dead_code)]
pub fn boolean(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &String) -> Result<bool,Arc<TaskResponse>> {
if tm == TemplateMode::Off {
return Ok(true);
}
let st = self.string(request, tm, field, template)?;
let x = st.parse::<bool>();
return match x {
Ok(x) => Ok(x), Err(_err) => Err(self.response.is_failed(request, &format!("field ({}) value is not a boolean: {}", field, st)))
}
}
#[allow(dead_code)]
pub fn boolean_option_default_true(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &Option<String>)-> Result<bool,Arc<TaskResponse>>{
return self.internal_boolean_option(request, tm, field, template, true);
}
pub fn boolean_option_default_false(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &Option<String>)-> Result<bool,Arc<TaskResponse>>{
return self.internal_boolean_option(request, tm, field, template, false);
}
fn internal_boolean_option(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &Option<String>, default: bool)-> Result<bool,Arc<TaskResponse>>{
if tm == TemplateMode::Off {
return Ok(false);
}
if template.is_none() {
return Ok(default);
}
let st = self.string(request, tm, field, &template.as_ref().unwrap())?;
let x = st.parse::<bool>();
return match x {
Ok(x) => Ok(x),
Err(_err) => Err(self.response.is_failed(request, &format!("field ({}) value is not a boolean: {}", field, st)))
}
}
pub fn boolean_option_default_none(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, template: &Option<String>)-> Result<Option<bool>,Arc<TaskResponse>>{
if tm == TemplateMode::Off {
return Ok(None);
}
if template.is_none() {
return Ok(None);
}
let st = self.string(request, tm, field, &template.as_ref().unwrap())?;
let x = st.parse::<bool>();
return match x {
Ok(x) => Ok(Some(x)),
Err(_err) => Err(self.response.is_failed(request, &format!("field ({}) value is not a boolean: {}", field, st)))
}
}
pub fn test_condition(&self, request: &Arc<TaskRequest>, tm: TemplateMode, expr: &String) -> Result<bool, Arc<TaskResponse>> {
if tm == TemplateMode::Off {
return Ok(false);
}
let result = self.get_context().read().unwrap().test_condition(expr, &self.host, tm);
return match result {
Ok(x) => Ok(x), Err(y) => Err(self.response.is_failed(request, &y))
}
}
pub fn test_condition_with_extra_data(&self, request: &Arc<TaskRequest>, tm: TemplateMode, expr: &String, _host: &Arc<RwLock<Host>>, vars_input: serde_yaml::Mapping) -> Result<bool,Arc<TaskResponse>> {
if tm == TemplateMode::Off {
return Ok(false);
}
let result = self.get_context().read().unwrap().test_condition_with_extra_data(expr, &self.host, vars_input, tm);
return match result {
Ok(x) => Ok(x), Err(y) => Err(self.response.is_failed(request, &y))
}
}
pub fn find_template_path(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, str_path: &String) -> Result<PathBuf, Arc<TaskResponse>> {
return self.find_sub_path(&String::from("templates"), request, tm, field, str_path);
}
pub fn find_file_path(&self, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, str_path: &String) -> Result<PathBuf, Arc<TaskResponse>> {
return self.find_sub_path(&String::from("files"), request, tm, field, str_path);
}
fn find_sub_path(&self, prefix: &String, request: &Arc<TaskRequest>, tm: TemplateMode, field: &String, str_path: &String) -> Result<PathBuf, Arc<TaskResponse>> {
if tm == TemplateMode::Off {
return Ok(PathBuf::new());
}
let prelim = match screen_path(str_path) {
Ok(x) => x,
Err(y) => { return Err(self.response.is_failed(request, &format!("{}, for field: {}", y, field))) }
};
let mut path = PathBuf::new();
path.push(prelim);
if path.is_absolute() {
if path.is_file() {
return Ok(path);
} else {
return Err(self.response.is_failed(request, &format!("field ({}): no such file: {}", field, str_path)));
}
} else {
let mut path2 = PathBuf::new();
path2.push(prefix);
path2.push(str_path);
if path2.is_file() {
return Ok(path2);
} else {
return Err(self.response.is_failed(request, &format!("field ({}): no such file: {}", field, str_path)));
}
}
}
fn has_spaces(&self, input: &String) -> bool {
let found = input.find(' ');
return found.is_some();
}
pub fn add_sudo_details(&self, request: &TaskRequest, cmd: &str) -> Result<String, String> {
if ! request.is_sudoing() {
return Ok(cmd.to_owned());
}
let details = request.sudo_details.as_ref().unwrap();
let user = details.user.as_ref().unwrap().clone();
let sudo_template = details.template.clone();
let mut data = serde_yaml::Mapping::new();
data.insert(serde_yaml::Value::String(String::from("jet_sudo_user")), serde_yaml::Value::String(user.clone()));
data.insert(serde_yaml::Value::String(String::from("jet_command")), serde_yaml::Value::String(cmd.to_string()));
let result = self.detached_templar.render(&sudo_template, data, TemplateMode::Strict)?;
return Ok(result)
}
}