feat: add basic templates

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
2024-05-20 20:45:26 +02:00
parent fe48259d06
commit f04b0a2e54
14 changed files with 357 additions and 4 deletions

View File

@@ -24,10 +24,14 @@ sqlx = { version = "0.7.3", features = [
uuid = { version = "1.7.0", features = ["v4"] }
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
serde_yaml = "0.9.34"
tokio-stream = "0.1.15"
walkdir = "2.5.0"
minijinja = "2.0.1"
[[test]]
name = "integration"
path = "tests/tests.rs"
[dev-dependencies]
similar-asserts = "1.5.0"
tracing-test = "0.2.4"

View File

@@ -1,9 +1,12 @@
use std::{
collections::HashMap,
ops::Deref,
path::{Path, PathBuf},
};
use anyhow::Context;
use tokio::io::AsyncWriteExt;
use tokio_stream::{wrappers::ReadDirStream, StreamExt};
pub async fn process() -> anyhow::Result<()> {
process_opts(ProcessOpts::default()).await
@@ -11,12 +14,17 @@ pub async fn process() -> anyhow::Result<()> {
pub struct ProcessOpts {
pub path: PathBuf,
pub output: PathBuf,
}
impl Default for ProcessOpts {
fn default() -> Self {
Self {
path: std::env::current_dir().expect("to be able to get current dir"),
output: std::env::current_dir()
.expect("to be able to get current dir")
.join("cuddle-clusters")
.join("k8s"),
}
}
}
@@ -36,7 +44,13 @@ pub async fn process_opts(opts: ProcessOpts) -> anyhow::Result<()> {
let clusters = read_cuddle_section(&cuddle_path).await?;
tracing::debug!("found clusters: {:?}", clusters);
load_template_files(&template).await?;
let template_files = load_template_files(&template).await?;
tracing::debug!("found files: {:?}", template_files);
tokio::fs::remove_dir_all(&opts.output).await?;
tokio::fs::create_dir_all(&opts.output).await?;
process_templates(&clusters, &template_files, &opts.output).await?;
Ok(())
}
@@ -44,8 +58,18 @@ pub async fn process_opts(opts: ProcessOpts) -> anyhow::Result<()> {
#[derive(serde::Deserialize, Default, Debug, Clone)]
struct CuddleClusters(HashMap<String, serde_yaml::Value>);
impl Deref for CuddleClusters {
type Target = HashMap<String, serde_yaml::Value>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
async fn read_cuddle_section(path: &Path) -> anyhow::Result<CuddleClusters> {
let cuddle_file = tokio::fs::read(path).await?;
let cuddle_file = tokio::fs::read(path)
.await
.context(format!("failed to read: {}", path.display()))?;
let value: serde_yaml::Value = serde_yaml::from_slice(&cuddle_file)?;
@@ -63,6 +87,110 @@ async fn read_cuddle_section(path: &Path) -> anyhow::Result<CuddleClusters> {
Ok(cuddle_clusters)
}
async fn load_template_files(path: &Path) -> anyhow::Result<()> {
#[derive(Debug, Clone)]
struct TemplateFiles {
templates: Vec<PathBuf>,
raw: Vec<PathBuf>,
}
async fn load_template_files(path: &Path) -> anyhow::Result<TemplateFiles> {
Ok(TemplateFiles {
templates: read_dir(path)
.await?
.into_iter()
.filter(|i| !i.ends_with(".jinja2"))
.collect(),
raw: read_dir(&path.join("raw")).await.unwrap_or_default(),
})
}
async fn read_dir(path: &Path) -> anyhow::Result<Vec<PathBuf>> {
let template_dir = tokio::fs::read_dir(path).await?;
let mut template_dir_stream = ReadDirStream::new(template_dir);
let mut paths = Vec::new();
while let Some(entry) = template_dir_stream.next().await {
let entry = entry?;
if entry.metadata().await?.is_file() {
paths.push(entry.path());
}
}
Ok(paths)
}
async fn process_templates(
clusters: &CuddleClusters,
template_files: &TemplateFiles,
dest: &Path,
) -> anyhow::Result<()> {
for (cluster_name, _value) in clusters.iter() {
process_cluster(cluster_name, template_files, &dest.join(cluster_name)).await?;
}
Ok(())
}
async fn process_cluster(
cluster_name: &str,
template_files: &TemplateFiles,
dest: &Path,
) -> anyhow::Result<()> {
for template_file in &template_files.templates {
process_template_file(cluster_name, template_file, dest).await?;
}
for raw_file in &template_files.raw {
process_raw_file(cluster_name, raw_file, dest).await?;
}
Ok(())
}
async fn process_template_file(
cluster_name: &str,
template_file: &PathBuf,
dest: &Path,
) -> anyhow::Result<()> {
// TODO: use mini jinja
let file = tokio::fs::read_to_string(template_file)
.await
.context(format!("failed to find file: {}", template_file.display()))?;
if !dest.exists() {
tokio::fs::create_dir_all(dest).await?;
}
let file_name = template_file
.file_stem()
.ok_or(anyhow::anyhow!("file didn't have a jinja2 format"))?;
let mut dest_file = tokio::fs::File::create(dest.join(file_name)).await?;
dest_file.write_all(file.as_bytes()).await?;
Ok(())
}
async fn process_raw_file(
_cluster_name: &str,
raw_file: &PathBuf,
dest: &Path,
) -> anyhow::Result<()> {
let file = tokio::fs::read_to_string(raw_file)
.await
.context(format!("failed to find file: {}", raw_file.display()))?;
if !dest.exists() {
tokio::fs::create_dir_all(dest).await?;
}
let file_name = raw_file
.file_name()
.ok_or(anyhow::anyhow!("file didn't have a file name"))?;
let mut dest_file = tokio::fs::File::create(dest.join(file_name)).await?;
dest_file.write_all(file.as_bytes()).await?;
Ok(())
}

View File

@@ -0,0 +1,6 @@
{
hello = "world",
some = {
thing = "some"
}
}

View File

@@ -1,4 +1,7 @@
use std::{cmp::Ordering, path::Path};
use cuddle_clusters::process::ProcessOpts;
use walkdir::DirEntry;
pub(crate) async fn run_test(name: &str) -> anyhow::Result<()> {
let _ = tracing_subscriber::fmt::try_init();
@@ -7,10 +10,104 @@ pub(crate) async fn run_test(name: &str) -> anyhow::Result<()> {
let current_dir = std::env::current_dir()?;
let test_folder = current_dir.join("tests").join(name);
let actual = test_folder.join("actual");
tokio::fs::create_dir_all(&actual).await?;
let expected = test_folder.join("expected");
tokio::fs::create_dir_all(&expected).await?;
cuddle_clusters::process_opts(ProcessOpts {
path: current_dir.join("tests").join(name),
path: test_folder.clone(),
output: actual.clone(),
})
.await?;
if std::env::var("TEST_OVERRIDE") == Ok("true".to_string()) {
cuddle_clusters::process_opts(ProcessOpts {
path: test_folder,
output: expected.clone(),
})
.await?;
}
compare(&expected, &actual).await?;
Ok(())
}
async fn compare(expected: &Path, actual: &Path) -> anyhow::Result<()> {
let mut exp = walk_dir(expected)?;
let mut act = walk_dir(actual)?;
for (exp, act) in (&mut exp).zip(&mut act) {
let exp = exp?;
let act = act?;
if exp.depth() != act.depth() {
return Err(anyhow::anyhow!(
"path(different depth): expected {} is different from actual: {}",
exp.path().display(),
act.path().display()
));
}
if exp.file_type() != act.file_type() {
return Err(anyhow::anyhow!(
"path(different filetype): expected {} is different from actual: {}",
exp.path().display(),
act.path().display()
));
}
if exp.file_name() != act.file_name() {
return Err(anyhow::anyhow!(
"path(different filename): expected {} is different from actual: {}",
exp.path().display(),
act.path().display()
));
}
if exp.metadata()?.is_file() {
let exp_file = tokio::fs::read_to_string(exp.path()).await?;
let act_file = tokio::fs::read_to_string(act.path()).await?;
similar_asserts::assert_eq!(exp_file, act_file);
}
}
if exp.next().is_some() || act.next().is_some() {
return Err(anyhow::anyhow!(
"path(uneven amount of items): expected: {}, actual: {}",
exp.next()
.map(|o| match o {
Ok(o) => o.path().display().to_string(),
Err(_) => "expected: not-found".to_string(),
})
.unwrap_or("expected: not-found".into()),
act.next()
.map(|o| match o {
Ok(o) => o.path().display().to_string(),
Err(_) => "actual: not-found".to_string(),
})
.unwrap_or("actual: not-found".into()),
));
}
Ok(())
}
fn walk_dir(path: &Path) -> anyhow::Result<walkdir::IntoIter> {
let mut walkdir = walkdir::WalkDir::new(path)
.sort_by(compare_by_file_name)
.into_iter();
if let Some(Err(e)) = walkdir.next() {
Err(e.into())
} else {
Ok(walkdir)
}
}
fn compare_by_file_name(a: &DirEntry, b: &DirEntry) -> Ordering {
a.file_name().cmp(b.file_name())
}

View File

@@ -0,0 +1,2 @@
cuddle/clusters:
dev:

View File

@@ -0,0 +1,2 @@
some
file

View File

@@ -0,0 +1,2 @@
some: file
another: entry

View File

@@ -0,0 +1,2 @@
some
file

View File

@@ -0,0 +1,2 @@
some: file
another: entry

View File

@@ -1,3 +1,12 @@
pub mod common;
mod can_run_for_env;
use crate::common::run_test;
#[tokio::test]
async fn raw_files() -> anyhow::Result<()> {
run_test("raw_files").await?;
Ok(())
}