use base64::encode;
use yaml_rust::yaml::Hash;
use yaml_rust::{EmitError, Yaml, YamlEmitter, YamlLoader};
use crate::{
parameter::{ParamMap, Parameter, ParameterValues},
processor::process_yaml,
secret::{Secret, Secrets},
};
#[derive(Debug)]
pub struct Template {
objects: Vec<Yaml>,
param_map: ParamMap,
secrets: Option<Secrets>,
}
impl Template {
pub fn new(
template_contents: String,
parameter_values: ParameterValues,
secrets: Option<Secrets>,
) -> Result<Self, String> {
let docs = YamlLoader::load_from_str(&template_contents)
.map_err(|err| err.to_string())?;
if docs.len() != 1 {
return Err("Only one YAML document can be present in the template.".to_owned());
}
let doc = &docs[0];
let mut template_objects = vec![];
let objects = match doc["objects"].as_vec() {
Some(objects) => objects,
None => return Err("Key \"objects\" must be present and must be an array.".to_owned()),
};
for object in objects {
template_objects.push(object.clone());
}
let mut param_map = ParamMap::new();
let parameter_specs = match doc["parameters"].as_vec() {
Some(parameter_specs) => parameter_specs,
None => {
return Err("Key \"parameters\" must be present and must be an array.".to_owned())
}
};
for parameter_spec in parameter_specs {
let parameter = Parameter::new(parameter_spec, ¶meter_values)?;
param_map.insert(parameter.name.clone(), parameter);
}
Ok(Template {
objects: template_objects,
param_map: param_map,
secrets: secrets,
})
}
pub fn process(mut self) -> Result<String, String> {
let mut secrets_encoded = 0;
for object in self.objects.iter_mut() {
process_yaml(object, &self.param_map);
if let Some(ref secrets) = self.secrets {
if maybe_base64_encode_secret(secrets, object)? {
secrets_encoded += 1;
}
}
}
if let Some(ref secrets) = self.secrets {
if secrets_encoded != secrets.len() {
return Err("Not all secrets specified were found.".to_string());
}
}
dump(self.objects)
}
}
fn maybe_base64_encode_secret(secrets: &Secrets, object: &mut Yaml) -> Result<bool, String> {
let hash = match object {
&mut Yaml::Hash(ref mut hash) => hash,
_ => return Ok(false),
};
if let Some(kind) = hash.get(&Yaml::String("kind".to_string())) {
match kind {
&Yaml::String(ref kind_string) => {
if kind_string != "Secret" {
return Ok(false);
}
}
_ => {
return Err(
"Encountered a resource with a non-string value for the \"kind\" field."
.to_string(),
)
}
}
} else {
return Err("Encountered a resource without a \"kind\" field.".to_string());
}
let metadata = match hash.get(&Yaml::String("metadata".to_string())) {
Some(&Yaml::Hash(ref metadata)) => metadata.clone(),
Some(_) => {
return Err("Encountered a resource with a non-hash \"metadata\" field.".to_string())
}
None => return Err("Encountered a resource without a \"metadata\" field.".to_string()),
};
let name = match metadata.get(&ystring("name")) {
Some(&Yaml::String(ref name)) => name.to_string(),
Some(_) => {
return Err(
"Encountered a resource with a non-string \"metadata.name\" field.".to_string(),
)
}
None => return Err("Encountered a resource without a \"metadata.name\" field.".to_string()),
};
let namespace = match metadata.get(&ystring("namespace")) {
Some(&Yaml::String(ref namespace)) => namespace.to_string(),
Some(_) => {
return Err(
"Encountered a resource with a non-string \"metadata.namespace\" field."
.to_string(),
)
}
None => "default".to_string(),
};
let secret = Secret {
name: name,
namespace: namespace,
};
if secrets.contains(&secret) {
if let Some(data) = hash.get_mut(&ystring("data")) {
match data {
&mut Yaml::Hash(ref mut data_hash) => {
base64_encode_secret_data(data_hash)?;
return Ok(true);
}
_ => return Err("Encountered secret with non-hash \"data\" field.".to_string()),
}
}
}
return Ok(false);
}
fn base64_encode_secret_data(data: &mut Hash) -> Result<(), String> {
for (_, value) in data.iter_mut() {
let encoded = match value {
&mut Yaml::String(ref value_string) => encode(value_string.as_bytes()),
_ => return Err("Encountered non-string secret data value.".to_string()),
};
*value = ystring(&encoded);
}
Ok(())
}
fn dump(objects: Vec<Yaml>) -> Result<String, String> {
let mut manifests = String::new();
let last = objects.len() - 1;
for (i, object) in objects.iter().enumerate() {
{
let mut emitter = YamlEmitter::new(&mut manifests);
emitter.dump(&object).map_err(|error| match error {
EmitError::FmtError(error) => format!("{}", error),
EmitError::BadHashmapKey => "Bad hashmap key in YAML structure.".to_owned(),
})?;
}
if i != last {
manifests.push_str("\n");
}
}
Ok(manifests)
}
fn ystring(s: &str) -> Yaml {
Yaml::String(s.to_string())
}