use clap::{Parser, ValueEnum};
use serde::Deserialize;
use serde_json::json;
use serde_yaml;
use std::{
env::current_dir,
fs,
io,
path::PathBuf,
};
use log::debug;
#[derive(Parser)]
#[command()]
struct Args {
path: Vec<PathBuf>,
#[arg(short, long, value_enum, default_value = "text")]
format: Option<Format>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Format {
Text,
Json,
}
#[derive(Default, Deserialize, PartialEq, Debug)]
#[serde(default, rename_all = "camelCase")]
struct Kustomization {
resources: Vec<String>,
config_map_generator: Vec<ConfigMapGenerator>,
}
#[derive(Clone, Default, Deserialize, PartialEq, Debug)]
#[serde(default)]
struct ConfigMapGenerator {
name: String,
files: Vec<String>,
}
fn normalize(path: PathBuf) -> PathBuf {
let mut canonical = path.canonicalize().unwrap();
if canonical.is_dir() {
canonical.push("kustomization.yml");
}
if canonical.is_file() {
return canonical;
} else {
canonical.set_extension("yaml");
}
if canonical.is_file() {
return canonical;
}
panic!("Unable to normalize path {}", path.display());
}
fn read_file(path: &PathBuf) -> io::Result<String> {
return fs::read_to_string(path);
}
fn deserialize(path: &PathBuf) -> Vec<Kustomization> {
let content = read_file(path).unwrap();
return serde_yaml::Deserializer::from_str(&content)
.map(|doc| Kustomization::deserialize(doc).unwrap())
.collect();
}
fn unsupported(resource: &str) -> bool {
let remote = [
"git://",
"http://",
"https://",
"github.com",
];
for r in remote {
if resource.starts_with(r) {
return true;
}
}
return false;
}
fn run(path: PathBuf, result: &mut Vec<String>) {
debug!("{:#?}", path.display());
let current_path = normalize(path.clone());
debug!("{:#?}", current_path.display());
result.push(format!("{}", current_path.display()));
let doc = deserialize(¤t_path);
debug!("{:#?}", doc);
doc
.iter()
.map(|doc| doc.config_map_generator.clone())
.flatten()
.map(|c| c.files)
.flatten()
.for_each(|f| {
let file = current_path
.parent()
.unwrap()
.join(f);
result.push(format!("{}", file.display()));
});
let resources: Vec<String> = doc
.iter()
.map(|doc| doc.resources.clone())
.flatten()
.collect();
for r in resources {
if unsupported(&r) {
continue;
};
let next_path = current_path
.parent()
.unwrap()
.join(r);
run(next_path, result);
};
}
fn main() {
env_logger::init();
let args = Args::parse();
let root = current_dir().unwrap();
let mut result = Vec::new();
for p in args.path.iter() {
let mut path = root.clone();
path.push(p);
run(path, &mut result);
}
result.sort();
result.dedup();
match args.format {
Some(Format::Json) => {
let json = json!(result);
println!("{}", json.to_string());
},
_ => {
for r in result.iter() {
println!("{r}");
}
}
}
}