TFHE7SKK7K6UKJM3ATI2OU3QWX4YVAHHIFAAVLNIMA6EUZGZJWFAC RQHPHZ6GVNUXCI4ZMV6UBGRGJZJHIAQVGCBUAUMMDNKRJJW76TJAC FGD37V6XCUCI6IJGJP4SU4EFNEE7F3GQVBYJNK4XT2AYY34OCUTQC GJVR74FXYXUF6APQ2RJJ27BE5ZG6JCN6OREHHPCV3TP6ZMNNVCAAC 4MK5VE34SUDTEN2C2VVFBEONHGFY3P3EDTEK2WPU7RH5IWDAT3DQC DTAFS7PQQ6X2REJYR6EDKEM7TOKGIAX2YHIRRU354AOAFR2B3VIQC VWUEKTF47O72TML5LWVFTOUI6P52AB5KW7FHMSSUGMYJWYAVPLFAC A2SYW5RCLJO6WXUGQ7DLQA76DENJE2I5TLTUG6MXEI27ADPG2FVAC SCCMQCSX6BA6DQKARGW25PVWQPU3MZNZS4RNZ53FXYFCKMI24RGQC 6WWZUDNYHVYEQJHHJCUBMOZME2LG4SATAXSADBECKPBUGR3QLYTAC FWDHYYDCRS27NN2LVAP3VBCAFJAQ32X6WF4KRRD5ZBXAKZPX6TFQC HBKDTCY3SEIVJQACNASSDPRXNABOKIJ55H2EAMBWJ6Z5HBCT5QAAC use std::path::{Path, PathBuf};use std::collections::HashMap;use serde_yaml::{self, Value, Mapping};use url::Url;mod transformation;use transformation::*;#[serde(try_from = "RawKarbonfile")]pub struct Karbonfile {pub resources: Vec<Resource>,pub generators: Vec<Generator>,pub transformations: Vec<Transformation>,}#[serde(rename_all = "camelCase")]#[serde(deny_unknown_fields)]#[serde(default)]#[serde(default)]#[serde(default)]}impl TryFrom<RawKarbonfile> for Karbonfile {type Error = &'static str;fn try_from(t: RawKarbonfile) -> Result<Self, &'static str> {Ok(Self {api_version: t.api_version,kind: t.kind,generators: t.generators,transformations: t.transformations,})}resources: t.resources.into_keys().collect(),pub transformations: Vec<Transformation>,pub generators: Vec<Generator>,pub resources: HashMap<Resource, ()>,struct RawKarbonfile {pub api_version: ApiVersion,pub kind: Kind,#[derive(Deserialize, Serialize, Clone, Debug)]}pub enum Generator {ConfigMap,}pub struct Variant {// Original resource file Url.pub url: Option<Url>,// Parsed content of the original resource file.pub original: Option<Value>,}original: None,}}pub fn apply_transformations(&mut self) {if let Some(mut result) = self.original.to_owned() {}}}}self.result = Some(result);});self.transformations.iter().flatten()// Ignore the Karbonfile path..map(|(_, ts)| ts)// Flatten all the transformations of all the// Karbonfiles into a single Vec..flatten().for_each(|t| {if matches_filter(&result, t.filter.as_ref()) {t_add(&mut result, t.add.as_ref());t_replace(&mut result, t.replace.as_ref());t_prefix(&mut result, t.prefix.as_ref());t_suffix(&mut result, t.suffix.as_ref());result: None,transformations: None,impl Variant {pub fn new() -> Variant {Variant {url: None,// Resulting variant after all transformationspub result: Option<Value>,// Chain of transformations to apply with the Url of// the Karbonfile they originate from.pub transformations: Option<Vec<(Url, Vec<Transformation>)>>,#[derive(Deserialize, Serialize, Clone, Debug)]#[derive(Deserialize, Serialize, Clone, Debug)]pub type ApiVersion = String;pub type Kind = String;pub type Resource = String;pub api_version: ApiVersion,pub kind: Kind,#[derive(Deserialize, Serialize, Clone, Debug)]#[serde(rename_all = "camelCase")]#[serde(deny_unknown_fields)]use serde::{Deserialize, Serialize};
use std::collections::HashMap;use serde_yaml::Value;use crate::json_pointer;#[serde(deny_unknown_fields)]pub struct Transformation {pub filter: Option<Filter>,pub add: Option<TransAdd>,pub replace: Option<TransReplace>,pub remove: Option<TransRemove>,pub prefix: Option<TransPrefix>,pub suffix: Option<TransSuffix>,}pub type Filter = HashMap<String, Value>;pub type TransAdd = HashMap<String, Value>;pub type TransReplace = HashMap<String, Value>;pub type TransRemove = HashMap<String, ()>;pub type TransPrefix = HashMap<String, String>;pub type TransSuffix = HashMap<String, String>;p.push_str(current);*r = serde_yaml::from_str(&p).unwrap();}}}}pub fn t_suffix(result: &mut Value, t: Option<&TransSuffix>) {if let Some(map) = t {for (ptr, suffix) in map {if let Some(r) = json_pointer::get_mut(result, ptr) {let mut current = r.as_str().expect(&format!("Failed to suffix. Field {ptr} doesn't contain a string")).to_string();current.push_str(suffix);*r = serde_yaml::from_str(¤t).unwrap();}}}}pub fn matches_filter(doc: &Value, filter: Option<&Filter>) -> bool {if let Some(f) = filter {// Check each field from the filter and return// false as soon as one doesn't matchfor (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}pub fn t_add(result: &mut Value, t: Option<&TransAdd>) {if let Some(map) = t {for (ptr, value) in map {json_pointer::insert(result, ptr, value);}}}pub fn t_replace(result: &mut Value, t: Option<&TransReplace>) {if let Some(map) = t {for (ptr, new_value) in map {if let Some(r) = json_pointer::get_mut(result, ptr) {*r = new_value.to_owned();}}}}pub fn t_prefix(result: &mut Value, t: Option<&TransPrefix>) {if let Some(map) = t {for (ptr, prefix) in map {let mut p = prefix.to_owned();if let Some(r) = json_pointer::get_mut(result, ptr) {let current = r.as_str().expect(&format!("Failed to prefix. Field {ptr} doesn't contain a string"));#[derive(Deserialize, Serialize, Clone, Debug)]use serde::{Deserialize, Serialize};
}}.split('/').skip(1).map(|x| x.replace("~1", "/").replace("~0", "~")).try_fold(doc, |target, token| {match target {Value::Mapping(map) => {let t: Value = serde_yaml::from_str(&token).unwrap();}Value::Sequence(seq) => {let t: usize = token.parse().unwrap();}}})}//pub fn update<'a>(doc: &'a mut Yaml, pointer: &'a str) -> Option<&'a mut Yaml> {// // Taken from serde JSON// if pointer.is_empty() {// return Some(doc);// }// if !pointer.starts_with('/') {// return None;// }// pointer// .split('/')// .skip(1)// .map(|x| x.replace("~1", "/").replace("~0", "~"))// .try_fold(doc, |target, token| {// match target {// Yaml::Hash(map) => {// let t: Yaml = Yaml::from_str(&token).unwrap();// map.get_mut(&t)// }// Yaml::Sequence(seq) => {// let t: usize = token.parse().unwrap();// seq.get_mut(t)// }// _ => None,// }// })//}//////pub fn upsert<'a>(doc: &'a mut Yaml, pointer: &'a str) -> Option<&'a mut Yaml> {// // Taken from serde JSON// if pointer.is_empty() {// return Some(doc);// }// if !pointer.starts_with('/') {// return None;// }// pointer// .split('/')// .skip(1)// .map(|x| x.replace("~1", "/").replace("~0", "~"))// .try_fold(doc, |target, token| {// match target {// Yaml::Hash(map) => {// let t: Yaml = Yaml::from_str(&token).unwrap();//// if !map.contains_key(&t) {// let next = Yaml::Hash();//// map.insert(t.to_owned(), Yaml::Hash(next));// }//// map.get_mut(&t)// }// Yaml::Sequence(seq) => {// let t: usize = token.parse().unwrap();// seq.get_mut(t)// }// _ => None,// }// })//}let mut target_acc = ptr.split('/').skip(1).map(|x| x.replace("~1", "/").replace("~0", "~")).try_fold(doc, |target, token| {match target {Value::Mapping(map) => {let t: Value = serde_yaml::from_str(&token).unwrap();map.get_mut(&t)}Value::Sequence(seq) => {let t: usize = token.parse().unwrap();seq.get_mut(t)}_ => {None}}});let mut addition_acc: Value;ptr.windows(2).for_each(|token| {if let Ok(n) = token.parse::<usize>() {} else if let Ok(s) = token.parse::<str>() {if s == "-" {} else {}}});}pub fn insert<'a>(doc: &'a mut Value, ptr: &'a str, value: &'a Value) {if ptr.is_empty() {return;}if !ptr.starts_with('/') {return;}seq.get_mut(t)}_ => {Nonemap.get_mut(&t)ptrif !ptr.starts_with('/') {panic!("{} is not a valid pointer", &ptr);}if ptr.is_empty() {panic!("Point is empty");ptr.split('/').skip(1).map(|x| x.replace("~1", "/").replace("~0", "~")).try_fold(doc, |target, token| {match target {Value::Mapping(map) => {let t: Value = serde_yaml::from_str(&token).unwrap();map.get(&t)}Value::Sequence(seq) => {let t: usize = token.parse().unwrap();seq.get(t)}_ => {None}}})}pub fn get_mut<'a>(doc: &'a mut Value, ptr: &'a str) -> Option<&'a mut Value> {pub fn get<'a>(doc: &'a Value, ptr: &'a str) -> Option<&'a Value> {if ptr.is_empty() {panic!("Point is empty");if !ptr.starts_with('/') {panic!("{} is not a valid pointer", &ptr);}use serde::Deserialize;use serde_yaml::{self, Value, Mapping, Sequence};use std::collections::HashMap;
use url::{Url, ParseError};use serde_yaml::Value;mod karbonfile;use karbonfile::*;mod json_pointer;
use thiserror::Error;
if p.is_file() {let url = Url::from_file_path(p).expect("Unable to convert {p} to a URL");
#[derive(Error, Debug)]enum Error {#[error(transparent)]StdIo(#[from] std::io::Error),#[error("{0} is not a valid karbonfile path or directory")]InvalidBuildPath(PathBuf),#[error(transparent)]SerdeError(#[from] serde_yaml::Error),}
return vec!(url);} else if p.is_dir() {let entries = fs::read_dir(p.to_owned()).expect("Unable to list the content of {p}");let mut result = Vec::new();
#[derive(Clone, Debug, Deserialize)]#[serde(untagged)]enum Document {Karbonfile {apiVersion: String,resources: HashMap<String, Option<ResourceOpts>>,#[serde(default)]transformations: Vec<Transformation>,},Other(Value),}
serde_yaml::from_reader(file).expect("Failed to parse the file.")}_ => {panic!("Failed to par {url}, remote files are not implemented yet.");}
#[derive(Clone, Debug, Deserialize)]#[serde(untagged, rename_all = "lowercase")]enum Transformation {PatchSet {#[serde(default)]filters: Vec<Filter>,patches: HashMap<JsonPointer, JsonPatch>,
path.set_extension("yaml");if path.is_file() {return to_url(&path);}panic!("Failed to find any karbonfile.[yml|yaml] files in {url}");}if path.is_file() {return url.to_owned();
for (ptr, val) in map.into_iter() {if *ptr.get_mut(doc) != val.clone() {return false;
fn merge_url(base: &Url, s: &str) -> Url {base.join(s).expect("Failed to merge {url} and {s}")}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq)]#[serde(try_from = "String")]struct JsonPointer(Vec<String>);
fn build_variants(original_url: Url,mut variant: Variant,result: &mut VariantVec) {
for field in self.fields() {result = result.get_mut(field).expect("Field {field} wasn't found");}
if resources.is_empty() {leaf_is_karbonfile_without_resources(result, &variant, &url);} else {// not a leaf here since it's a karbonfile// and there is resources to go through.// So start a new branch an keep walking.for r in resources {let next = merge_url(&url, &r);build_variants(next, variant.clone(), result);}}}Err(e) => {leaf_is_not_karbonfile(result, &variant, &url);
impl From<String> for JsonPointer {fn from(item: String) -> Self {if !item.starts_with("/") {panic!("{item} is not a valid JSON pointer. It should start with a '/'");
fn get_resources(karbonfile: &Karbonfile) -> Vec<Resource> {karbonfile.resources.iter().map(|k| k.clone()).collect()
fn read_file(path: &PathBuf) -> String {return fs::read_to_string(path).expect(&format!("Failed to open file {}", path.display()));
fn push_transformations(variant: &mut Variant, url: &Url, karbonfile: &Karbonfile) {let new = (url.to_owned(),karbonfile.transformations.to_owned());match &variant.transformations {None => {variant.transformations = Some(vec!(new));}Some(t) => {let mut new_t = t.to_owned();new_t.push(new);variant.transformations = Some(new_t);}}
return result;
v.original = Some(parse(&url));v.url = Some(url.to_owned());v.apply_transformations();result.push(v);
fn normalize(root: &PathBuf, input: &str) -> PathBuf {let mut result = root.clone();if result.is_file() {result.pop();};result.push(input);return result.canonicalize().expect(&format!("Failed to normalize {}", result.display()));
fn leaf_is_karbonfile_without_resources(result: &mut VariantVec, variant: &Variant, url: &Url) {// leaf since it's a karbonfile but there// is no resources in it.// Set the path and push the branch to resultlet mut v = variant.to_owned();
match document {Document::Karbonfile { resources: resources, .. } => {let mut branch = variant.branch;branch.push(file);
v.url = Some(url.to_owned());result.push(v);
let mut new_variant = Variant {branch: branch,..variant};for r in resources.keys() {run(normalize(&path, r), result, new_variant.clone());};},Document::Other(_) => {let mut new_variant = Variant {original: Some(file),..variant};result.push(new_variant);},};
clap = { version = "3.0", features = ["derive"] }url = { version = "2", features = ["serde"] }minreq = { version = "2.5.1", features = ["https", "punycode"] }serde = { version = "1.0", features = ["derive"] }serde_yaml = "0.8"
clap = { version = "4.0.18", features = ["derive"] }serde = { version = "1.0.147", features = ["derive"] }serde_with = "2.0.1"serde_yaml = "0.9.14"thiserror = "1.0.37"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"[[package]]name = "base64"version = "0.13.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
"matches","percent-encoding",
"os_str_bytes",][[package]]name = "codespan-reporting"version = "0.11.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"dependencies = ["termcolor","unicode-width",][[package]]name = "core-foundation-sys"version = "0.8.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"[[package]]name = "cxx"version = "1.0.80"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a"dependencies = ["cc","cxxbridge-flags","cxxbridge-macro","link-cplusplus",][[package]]name = "cxx-build"version = "1.0.80"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827"dependencies = ["cc","codespan-reporting","once_cell","proc-macro2","quote","scratch","syn",][[package]]name = "cxxbridge-flags"version = "1.0.80"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a"[[package]]name = "cxxbridge-macro"version = "1.0.80"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7"dependencies = ["proc-macro2","quote","syn",][[package]]name = "darling"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"dependencies = ["darling_core","darling_macro",][[package]]name = "darling_core"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"dependencies = ["fnv","ident_case","proc-macro2","quote","strsim","syn",][[package]]name = "darling_macro"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"dependencies = ["darling_core","quote","syn",
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"[[package]]name = "iana-time-zone"version = "0.1.53"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
[[package]]name = "iana-time-zone-haiku"version = "0.1.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"dependencies = ["cxx","cxx-build",][[package]]name = "ident_case"version = "1.0.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"dependencies = ["cc","libc","once_cell","spin","untrusted","web-sys","winapi",]
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"[[package]]name = "scratch"version = "1.0.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
name = "web-sys"version = "0.3.56"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"dependencies = ["js-sys","wasm-bindgen",][[package]]name = "webpki"version = "0.22.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"dependencies = ["ring","untrusted",][[package]]name = "webpki-roots"version = "0.22.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449"dependencies = ["webpki",][[package]]