use crate::error::{Error, ErrorKind, Result};
use crate::modules::copy::copy_file;
use crate::modules::copy::{Input, Params as CopyParams};
use crate::modules::{parse_params, Module, ModuleResult};
use crate::vars::Vars;
#[cfg(feature = "docs")]
use rash_derive::DocJsonSchema;
use std::fs::metadata;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
#[cfg(feature = "docs")]
use schemars::schema::RootSchema;
#[cfg(feature = "docs")]
use schemars::JsonSchema;
use serde::Deserialize;
use serde_yaml::Value;
use tera::Tera;
#[derive(Debug, PartialEq, Deserialize)]
#[cfg_attr(feature = "docs", derive(JsonSchema, DocJsonSchema))]
#[serde(deny_unknown_fields)]
pub struct Params {
src: String,
dest: String,
mode: Option<String>,
}
fn render_content(params: Params, vars: Vars) -> Result<CopyParams> {
let mut tera = Tera::default();
tera.add_template_file(Path::new(¶ms.src), None)
.map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
let mode = match params.mode.as_deref() {
Some("preserve") => {
let src_metadata = metadata(¶ms.src)?;
let src_permissions = src_metadata.permissions();
Some(format!("{:o}", src_permissions.mode() & 0o7777))
}
_ => params.mode,
};
Ok(CopyParams {
input: Input::Content(
tera.render(¶ms.src, &vars)
.map_err(|e| Error::new(ErrorKind::InvalidData, e))?,
),
dest: params.dest.clone(),
mode,
})
}
#[derive(Debug)]
pub struct Template;
impl Module for Template {
fn get_name(&self) -> &str {
"template"
}
fn exec(
&self,
optional_params: Value,
vars: Vars,
check_mode: bool,
) -> Result<(ModuleResult, Vars)> {
Ok((
copy_file(
render_content(parse_params(optional_params)?, vars.clone())?,
check_mode,
)?,
vars,
))
}
#[cfg(feature = "docs")]
fn get_json_schema(&self) -> Option<RootSchema> {
Some(Params::get_json_schema())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vars;
use std::fs::{set_permissions, File};
use std::io::Write;
use tempfile::tempdir;
#[test]
fn test_parse_params() {
let yaml: Value = serde_yaml::from_str(
r#"
src: "/tmp/foo.j2"
dest: "/tmp/buu.txt"
mode: "0600"
"#,
)
.unwrap();
let params: Params = parse_params(yaml).unwrap();
assert_eq!(
params,
Params {
src: "/tmp/foo.j2".to_string(),
dest: "/tmp/buu.txt".to_string(),
mode: Some("0600".to_string()),
}
);
}
#[test]
fn test_parse_params_mode_int() {
let yaml: Value = serde_yaml::from_str(
r#"
src: "/tmp/foo.j2"
dest: "/tmp/buu.txt"
mode: 0600
"#,
)
.unwrap();
let params: Params = parse_params(yaml).unwrap();
assert_eq!(
params,
Params {
src: "/tmp/foo.j2".to_string(),
dest: "/tmp/buu.txt".to_string(),
mode: Some("0600".to_string()),
}
);
}
#[test]
fn test_parse_params_no_mode() {
let yaml: Value = serde_yaml::from_str(
r#"
src: "/tmp/boo.j2"
dest: "/tmp/buu.txt"
"#,
)
.unwrap();
let params: Params = parse_params(yaml).unwrap();
assert_eq!(
params,
Params {
src: "/tmp/boo.j2".to_string(),
dest: "/tmp/buu.txt".to_string(),
mode: None,
}
);
}
#[test]
fn test_parse_params_random_field() {
let yaml: Value = serde_yaml::from_str(
r#"
src: "/tmp/boo.j2"
yea: foo
dest: "/tmp/buu.txt"
"#,
)
.unwrap();
let error = parse_params::<Params>(yaml).unwrap_err();
assert_eq!(error.kind(), ErrorKind::InvalidData);
}
#[test]
fn test_render_content() {
let dir = tempdir().unwrap();
let file_path = dir.path().join("template.j2");
let mut file = File::create(file_path.clone()).unwrap();
#[allow(clippy::write_literal)]
writeln!(file, "{}", "{{ boo }}").unwrap();
let vars = vars::from_iter(vec![("boo", "test")].into_iter());
let copy_params = render_content(
Params {
src: file_path.to_str().unwrap().to_owned(),
dest: "/tmp/buu.txt".to_string(),
mode: Some("0644".to_string()),
},
vars,
)
.unwrap();
assert_eq!(copy_params.get_content().unwrap(), "test\n");
let metadata = file.metadata().unwrap();
let permissions = metadata.permissions();
assert_eq!(
format!("{:o}", permissions.mode() & 0o7777),
format!("{:o}", 0o644)
);
}
#[test]
fn test_render_content_with_preserve() {
let dir = tempdir().unwrap();
let file_path = dir.path().join("template.j2");
let mut file = File::create(file_path.clone()).unwrap();
#[allow(clippy::write_literal)]
writeln!(file, "{}", "{{ boo }}").unwrap();
let mut permissions = file.metadata().unwrap().permissions();
permissions.set_mode(0o604);
set_permissions(&file_path, permissions).unwrap();
let vars = vars::from_iter(vec![("boo", "test")].into_iter());
let copy_params = render_content(
Params {
src: file_path.to_str().unwrap().to_owned(),
dest: "/tmp/buu.txt".to_string(),
mode: Some("preserve".to_string()),
},
vars,
)
.unwrap();
assert_eq!(copy_params.get_content().unwrap(), "test\n");
let metadata = file.metadata().unwrap();
let permissions = metadata.permissions();
assert_eq!(
format!("{:o}", permissions.mode() & 0o7777),
format!("{:o}", 0o604)
);
}
}