use std::path::{Path, PathBuf};
use std::collections::HashMap;
use crate::json_pointer;
use url::Url;
use yaml_rust::Yaml;
use yaml_rust::yaml::Hash;
use linked_hash_map::LinkedHashMap;
pub struct Karbonfile {
api_version: String,
kind: String,
resources: LinkedHashMap<Yaml, Yaml>,
generators: LinkedHashMap<Yaml, Yaml>,
transformations: Vec<Transformation>,
}
impl Karbonfile {
pub fn from_yaml(doc: Yaml) -> Karbonfile {
let r = optional(&doc, "resources", Yaml::as_hash);
let t = optional(&doc, "transformations", Yaml::as_vec)
.into_iter()
.map(|x| Transformation::from_yaml(x))
.collect();
Karbonfile {
api_version: mandatory(&doc, "apiVersion"),
kind: mandatory(&doc, "kind"),
resources: r,
generators: LinkedHashMap::new(),
transformations: t,
}
}
}
fn mandatory(doc: &Yaml, field: &str) -> String {
doc[field]
.as_str()
.expect("The field '{field}' is missing or its value is not valid")
.to_string()
}
pub struct Variant {
// Original resource file path.
pub path: Option<PathBuf>,
// Parsed content of the original resource file.
pub original: Option<Yaml>,
// Resulting variant after all transformations
pub transformed: Option<Yaml>,
// Chain of transformations to apply with the path of
// the Karbonfile they originate from.
pub transformations: Option<Vec<(PathBuf, Vec<Transformation>)>>,
}
impl Variant {
pub fn new() -> Variant {
Variant {
path: None,
original: None,
transformed: None,
transformations: None,
}
}
pub fn set_path(&mut self, p: PathBuf) {
self.path = Some(p);
}
pub fn set_original(&mut self, r: Yaml) {
self.original = Some(r);
}
pub fn push_transformation(&mut self, p: PathBuf, t: Vec<Transformation>) {
match self.transformations {
None => self.transformations = Some(vec!((p, t))),
Some(mut ts) => ts.push((p, t)),
}
}
pub fn apply_transformations(&mut self) {
if let Some(mut result) = self.original.to_owned() {
self.transformations
.iter()
.flatten()
// Ignore the Karbonfile path.
.map(|(_, ts)| ts)
// Flatten all the transformations of all the
// Karbonfiles into a single Vec.
.flatten()
// Drop Transformation which don't apply to
// this resource.
.filter(|t| matches_filter(&result, t.filter))
// Apply each type of transformation.
.for_each(|t| {
t_add(&result, t.add);
t_replace(&result, t.replace);
t_prefix(&result, t.prefix);
t_suffix(&result, t.suffix);
});
self.transformed = Some(result);
}
}
}
type Filter = LinkedHashMap<String, Yaml>;
fn matches_filter(doc: &Yaml, filter: Option<Filter>) -> bool {
if let Some(f) = filter {
// Check each field from the filter and return
// false as soon as one doesn't match
for (field, value) in f.iter() {
if json_pointer::get(doc, field) != Some(value) {
return false;
}
}
}
// If all fields match or if no fields were provided
// keep this transformation in the list to apply.
true
}
fn t_add(doc: &Yaml, add: Option<TransAdd>) {
}
fn t_replace(doc: &Yaml, add: Option<TransReplace>) {
}
fn t_prefix(doc: &Yaml, add: Option<TransPrefix>) {
}
fn t_suffix(doc: &Yaml, add: Option<TransSuffix>) {
}
type TransAdd = LinkedHashMap<String, Yaml>;
type TransReplace = LinkedHashMap<String, Yaml>;
type TransRemove = LinkedHashMap<String, ()>;
type TransPrefix = LinkedHashMap<String, String>;
type TransSuffix = LinkedHashMap<String, String>;
pub struct Transformation {
filter: Option<Filter>,
add: Option<TransAdd>,
replace: Option<TransReplace>,
remove: Option<TransRemove>,
prefix: Option<TransPrefix>,
suffix: Option<TransSuffix>,
}
impl Transformation {
fn from_yaml(doc: Yaml) -> Transformation {
Transformation {
filter: optional(&doc, "filter", Yaml::as_hash),
add: optional(&doc, "add", Yaml::as_hash),
replace: optional(&doc, "replace", Yaml::as_hash),
remove: optional(&doc, "remove", Yaml::as_hash),
prefix: optional(&doc, "prefix", Yaml::as_hash),
suffix: optional(&doc, "suffix", Yaml::as_hash),
}
}
}
fn optional<T, F>(doc: &Yaml, field: &str, func: F) -> Option<T>
where F: FnOnce(&Yaml) -> Option<&T>
{
doc[field]
.func()
.map(|x| x.to_owned())
}
const label_fields: [&'static str; 30] = [
"/metadata/labels",
"/spec/selector",
"/spec/template/metadata/labels",
"/spec/selector/matchLabels",
"/spec/template/metadata/labels",
"/spec/template/spec/affinity/podAffinity/preferredDuringSchedulingIgnoredDuringExecution/podAffinityTerm/labelSelector/matchLabels",
"/spec/template/spec/affinity/podAffinity/requiredDuringSchedulingIgnoredDuringExecution/labelSelector/matchLabels",
"/spec/template/spec/affinity/podAntiAffinity/preferredDuringSchedulingIgnoredDuringExecution/podAffinityTerm/labelSelector/matchLabels",
"/spec/template/spec/affinity/podAntiAffinity/requiredDuringSchedulingIgnoredDuringExecution/labelSelector/matchLabels",
"/spec/template/spec/topologySpreadConstraints/labelSelector/matchLabels",
"/spec/selector/matchLabels",
"/spec/template/metadata/labels",
"/spec/selector/matchLabels",
"/spec/template/metadata/labels",
"/spec/selector/matchLabels",
"/spec/template/metadata/labels",
"/spec/template/spec/affinity/podAffinity/preferredDuringSchedulingIgnoredDuringExecution/podAffinityTerm/labelSelector/matchLabels",
"/spec/template/spec/affinity/podAffinity/requiredDuringSchedulingIgnoredDuringExecution/labelSelector/matchLabels",
"/spec/template/spec/affinity/podAntiAffinity/preferredDuringSchedulingIgnoredDuringExecution/podAffinityTerm/labelSelector/matchLabels",
"/spec/template/spec/affinity/podAntiAffinity/requiredDuringSchedulingIgnoredDuringExecution/labelSelector/matchLabels",
"/spec/template/spec/topologySpreadConstraints/labelSelector/matchLabels",
//"/spec/volumeClaimTemplates[]/metadata/labels",
"/spec/selector/matchLabels",
"/spec/template/metadata/labels",
"/spec/jobTemplate/spec/selector/matchLabels",
"/spec/jobTemplate/metadata/labels",
"/spec/jobTemplate/spec/template/metadata/labels",
"/spec/selector/matchLabels",
"/spec/podSelector/matchLabels",
"/spec/ingress/from/podSelector/matchLabels",
"/spec/egress/to/podSelector/matchLabels",
];