Compare commits
119 Commits
44e8fe8918
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b2f92b55e5 | |||
| c701d7fa6b | |||
| 80d8e5c0a3 | |||
| 6b72240871 | |||
| 15298f1f96 | |||
| b86fec2a58 | |||
| 687792706b | |||
| 1c29770ce2 | |||
| c1e1215f3c | |||
| e94513c24f | |||
|
6aa90d22ea
|
|||
| 8fe00b22c5 | |||
| 78262a138d | |||
| bcebe4bce4 | |||
| 1f8a5d52c4 | |||
| 9207ef8f81 | |||
| 7df629290a | |||
|
54829a7fe4
|
|||
|
0ebe88a470
|
|||
| 67af5e7aa6 | |||
|
bf7a7db868
|
|||
|
1d1ac49d0b
|
|||
| 8520bcb5b0 | |||
|
7b136b1331
|
|||
| d0d591dd4f | |||
|
e09e28e8d0
|
|||
|
3a09c68378
|
|||
| 8f889663b2 | |||
| 6d3bdda04d | |||
| 1720f29149 | |||
| dc2a4977d6 | |||
|
52c7f77751
|
|||
|
ede55b975b
|
|||
|
85cc1d46db
|
|||
| cc7aaf14eb | |||
| 1bacfdfb29 | |||
| 9820d2c3ab | |||
| 5fae1fc403 | |||
| b9f7ff0a6f | |||
| 440245c332 | |||
| a89af69c15 | |||
| b93f96053e | |||
| 347d6171a5 | |||
| e7d90ffcc5 | |||
| 9c607e8ab1 | |||
| cf6a4b9fcd | |||
| a05699d24e | |||
| ae9fdf7e7e | |||
| 8c3e546b27 | |||
| 248f294000 | |||
| 65f4271957 | |||
| eb980308c6 | |||
| 51e5e1a4ce | |||
| 00e45053f7 | |||
| dace148e52 | |||
| 04774a69bf | |||
| c489660211 | |||
| 2e6e6fd328 | |||
| 84df1fe4df | |||
| 30c58b9eb2 | |||
| c2502dbf00 | |||
| bec95c9519 | |||
| 02b1a627f0 | |||
| 4b84f27d67 | |||
| a3bb366d07 | |||
| c479cadf4d | |||
| 74bd83aaec | |||
| a707d31277 | |||
| 8cfdebdaee | |||
| f721e45f8f | |||
| db4b41c032 | |||
| 4945ecca40 | |||
| a6560a10cb | |||
|
e2feef1c27
|
|||
| 1b9c9188d4 | |||
| a9ca8cdc18 | |||
| 0431dd03bf | |||
| e5eabf6901 | |||
| a336025a0c | |||
| e471452ed3 | |||
| 05ea0d8ed8 | |||
| c42ad69eef | |||
| 614e2bf442 | |||
| 23d9ca8d11 | |||
| 05d8209f15 | |||
| 8de0a15922 | |||
| e20fbc56b4 | |||
| aa1f6c3a16 | |||
| e944203e80 | |||
| 3d4e6b6fcd | |||
| 35d54ab30f | |||
| 941a8f600f | |||
| 32d5fb6f97 | |||
| f97fe5c832 | |||
| 8580813bbc | |||
| 65a995c9ee | |||
| 31c77c7581 | |||
| dff0e85f7d | |||
| ff74c55829 | |||
| e6cd1c16b6 | |||
| 6b7f915c12 | |||
| c8e4f4b66d | |||
| 0aa7195415 | |||
| 1da2ded83e | |||
| 60b234c0d7 | |||
| cace45ee5b | |||
|
bc2999fb92
|
|||
|
26ef1cb0cd
|
|||
|
2cd9509fcb
|
|||
| 53b7513ceb | |||
| 71965cb07c | |||
| dd94ee2e8e | |||
| 3bf7e837e5 | |||
| 39505938a5 | |||
| 724a364984 | |||
| 840694967d | |||
| b32643ff7a | |||
| 41d337d003 | |||
|
fd01de7ede
|
1895
Cargo.lock
generated
1895
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["cuddle_cli", "examples/base", "ci"]
|
members = ["cuddle", "examples/base", "ci"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Cuddle CLI is a Rust command-line interface application designed to manage
|
Cuddle CLI is a Rust command-line interface application designed to manage
|
||||||
configuration variables and scripts across projects. It simplifies sharing of
|
configuration variables and scripts across projects. It simplifies sharing of
|
||||||
code and workflows, making development and collaboration smoother and more
|
code and workflows, making development and collaboration smoother and more
|
||||||
efficient. This project is published on crates.io as `cuddle_cli`.
|
efficient. This project is published on crates.io as `cuddle`.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ Make sure you have Rust and Cargo installed. You can install Rust and Cargo from
|
|||||||
To install Cuddle CLI, run:
|
To install Cuddle CLI, run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo install cuddle_cli
|
cargo install cuddle
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dagger-sdk = "0.2.22"
|
dagger-sdk = "0.9.8"
|
||||||
eyre = "0.6.8"
|
eyre = "0.6.12"
|
||||||
tokio = { version = "1.29.1", features = ["full"] }
|
tokio = { version = "1.36.0", features = ["full"] }
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ async fn main() -> eyre::Result<()> {
|
|||||||
HostDirectoryOptsBuilder::default()
|
HostDirectoryOptsBuilder::default()
|
||||||
.include(vec![
|
.include(vec![
|
||||||
"ci/",
|
"ci/",
|
||||||
"cuddle_cli/",
|
"cuddle/",
|
||||||
"examples",
|
"examples",
|
||||||
"Cargo.lock",
|
"Cargo.lock",
|
||||||
"Cargo.toml",
|
"Cargo.toml",
|
||||||
@@ -79,14 +79,14 @@ async fn dind_image(
|
|||||||
"libz-dev",
|
"libz-dev",
|
||||||
])
|
])
|
||||||
.with_workdir("/app/cuddle/")
|
.with_workdir("/app/cuddle/")
|
||||||
.with_directory(".", src.id().await?)
|
.with_directory(".", src)
|
||||||
.with_exec(vec![
|
.with_exec(vec![
|
||||||
"cargo",
|
"cargo",
|
||||||
"install",
|
"install",
|
||||||
"--target",
|
"--target",
|
||||||
&format!("{architecture}-unknown-linux-musl"),
|
&format!("{architecture}-unknown-linux-musl"),
|
||||||
"--path",
|
"--path",
|
||||||
"cuddle_cli",
|
"cuddle",
|
||||||
"--profile=release",
|
"--profile=release",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ async fn dind_image(
|
|||||||
.from("docker:dind")
|
.from("docker:dind")
|
||||||
.with_directory(
|
.with_directory(
|
||||||
"/usr/local/cargo/bin/",
|
"/usr/local/cargo/bin/",
|
||||||
rust_bin.directory("/usr/local/cargo/bin/").id().await?,
|
rust_bin.directory("/usr/local/cargo/bin/"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let path_env = final_image.env_variable("PATH").await?;
|
let path_env = final_image.env_variable("PATH").await?;
|
||||||
|
|||||||
51
cuddle/Cargo.toml
Normal file
51
cuddle/Cargo.toml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
[package]
|
||||||
|
name = "cuddle"
|
||||||
|
description = "cuddle is a shuttle inspired script and configuration management tool. It enables sharing of workflows on developers workstations and ci"
|
||||||
|
repository = "https://git.front.kjuulh.io/kjuulh/cuddle"
|
||||||
|
readme = "../README.md"
|
||||||
|
license-file = "../LICENSE"
|
||||||
|
publish = true
|
||||||
|
version = "0.2.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "cuddle"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { version = "1.0.79", features = ["backtrace"] }
|
||||||
|
serde = { version = "1.0.196", features = ["derive"] }
|
||||||
|
serde_yaml = "0.9.31"
|
||||||
|
walkdir = "2.4.0"
|
||||||
|
git2 = { version = "0.18.2", default-features = false, features = [
|
||||||
|
"vendored-libgit2",
|
||||||
|
"vendored-openssl",
|
||||||
|
"ssh",
|
||||||
|
] }
|
||||||
|
clap = { version = "4.4.18", features = ["env", "string"] }
|
||||||
|
envconfig = "0.10.0"
|
||||||
|
dirs = "5.0.1"
|
||||||
|
tracing = "0.1.40"
|
||||||
|
tracing-subscriber = { version = "0.3.18", features = ["json", "env-filter"] }
|
||||||
|
log = { version = "0.4.20", features = ["std", "kv_unstable"] }
|
||||||
|
tera = "1.19.1"
|
||||||
|
openssl = { version = "0.10.63", features = ["vendored"] }
|
||||||
|
libz-sys = { version = "1.1.15", default-features = false, features = [
|
||||||
|
"libc",
|
||||||
|
"static",
|
||||||
|
] }
|
||||||
|
inquire = { version = "0.6.2", features = ["console"] }
|
||||||
|
tempfile = { version = "3.10.0" }
|
||||||
|
serde_json = "1.0.113"
|
||||||
|
rlua = "0.19.8"
|
||||||
|
rlua-searcher = "0.1.0"
|
||||||
|
dotenvy = { version = "0.15.7" }
|
||||||
|
blake3 = "1.5.0"
|
||||||
|
tokio = { version = "1.36.0", features = ["full"] }
|
||||||
|
futures-util = "0.3.30"
|
||||||
|
fs_extra = "1.3.0"
|
||||||
|
|
||||||
|
[dependencies.reqwest]
|
||||||
|
version = "0.11"
|
||||||
|
default-features = false
|
||||||
|
features = ["rustls-tls", "json"]
|
||||||
@@ -78,7 +78,7 @@ impl CuddleAction {
|
|||||||
|
|
||||||
log::trace!("preparing to run action");
|
log::trace!("preparing to run action");
|
||||||
|
|
||||||
return match ShellAction::new(
|
match ShellAction::new(
|
||||||
self.name.clone(),
|
self.name.clone(),
|
||||||
format!(
|
format!(
|
||||||
"{}/scripts/{}.sh",
|
"{}/scripts/{}.sh",
|
||||||
@@ -98,7 +98,7 @@ impl CuddleAction {
|
|||||||
log::error!("{}", e);
|
log::error!("{}", e);
|
||||||
Err(e)
|
Err(e)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
CuddleScript::Dagger(_d) => Err(anyhow::anyhow!("not implemented yet!")),
|
CuddleScript::Dagger(_d) => Err(anyhow::anyhow!("not implemented yet!")),
|
||||||
CuddleScript::Lua(l) => {
|
CuddleScript::Lua(l) => {
|
||||||
@@ -183,6 +183,184 @@ impl CuddleAction {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
CuddleScript::Rust(script) => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod rust_action {
|
||||||
|
use std::{path::PathBuf, time::Duration};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use futures_util::StreamExt;
|
||||||
|
use reqwest::Method;
|
||||||
|
use tokio::{fs::File, io::AsyncWriteExt};
|
||||||
|
|
||||||
|
use crate::model::{CuddleRustScript, CuddleRustUpstream, CuddleVariable};
|
||||||
|
|
||||||
|
pub struct RustActionConfig {
|
||||||
|
pub config_dir: PathBuf,
|
||||||
|
pub cache_dir: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RustActionConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
let config = dirs::config_dir().expect("to be able to find a valid .config dir");
|
||||||
|
let cache = dirs::cache_dir().expect("to be able to find a valid .cache dir");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
config_dir: config,
|
||||||
|
cache_dir: cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RustAction {
|
||||||
|
pub config: RustActionConfig,
|
||||||
|
pub plan: String,
|
||||||
|
pub binary_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustAction {
|
||||||
|
pub fn new(plan: String, binary_name: String) -> Self {
|
||||||
|
Self {
|
||||||
|
plan,
|
||||||
|
binary_name,
|
||||||
|
config: RustActionConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(
|
||||||
|
&self,
|
||||||
|
script: CuddleRustScript,
|
||||||
|
variables: impl IntoIterator<Item = CuddleVariable>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let commit_sha = self
|
||||||
|
.get_commit_sha()
|
||||||
|
.await
|
||||||
|
.context("failed to find a valid commit sha on the inferred path: .cuddle/plan")?;
|
||||||
|
|
||||||
|
let binary_hash = self.calculate_hash(commit_sha)?;
|
||||||
|
|
||||||
|
// Get cached binary
|
||||||
|
// let binary = match self.get_binary(&binary_hash).await? {
|
||||||
|
// Some(binary) => binary,
|
||||||
|
// None => self.fetch_binary(&script, &binary_hash).await?,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// Execute binary
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_binary(
|
||||||
|
&self,
|
||||||
|
binary_hash: impl Into<String>,
|
||||||
|
) -> anyhow::Result<Option<RustBinary>> {
|
||||||
|
let binary_path = self.get_cached_binary_path(binary_hash);
|
||||||
|
if !binary_path.exists() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(RustBinary {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cached_binary_path(&self, binary_hash: impl Into<String>) -> PathBuf {
|
||||||
|
let cached_binary_name = self.get_cached_binary_name(binary_hash);
|
||||||
|
let binary_path = self
|
||||||
|
.config
|
||||||
|
.cache_dir
|
||||||
|
.join("binaries")
|
||||||
|
.join(cached_binary_name);
|
||||||
|
binary_path
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_cached_binary_name(&self, binary_hash: impl Into<String>) -> String {
|
||||||
|
format!("{}-{}", binary_hash.into(), self.binary_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_commit_sha(&self) -> anyhow::Result<String> {
|
||||||
|
let repo = git2::Repository::open(".cuddle/plan")?;
|
||||||
|
let head = repo.head()?;
|
||||||
|
let commit = head.peel_to_commit()?;
|
||||||
|
|
||||||
|
let commit_sha = commit.id();
|
||||||
|
|
||||||
|
Ok(commit_sha.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
// async fn fetch_binary(
|
||||||
|
// &self,
|
||||||
|
// script: &CuddleRustScript,
|
||||||
|
// binary_hash: impl Into<String>,
|
||||||
|
// ) -> anyhow::Result<RustBinary> {
|
||||||
|
//let upstream = &script.upstream;
|
||||||
|
|
||||||
|
//TODO: we should interpret some template variables in the upstream string. Ignore for now though
|
||||||
|
|
||||||
|
// match UpstreamRustBinary::from(upstream) {
|
||||||
|
// UpstreamRustBinary::HttpBased { url } => {
|
||||||
|
// let client = reqwest::ClientBuilder::new()
|
||||||
|
// .user_agent(concat!(
|
||||||
|
// env!("CARGO_PKG_NAME"),
|
||||||
|
// "/",
|
||||||
|
// env!("CARGO_PKG_VERSION")
|
||||||
|
// ))
|
||||||
|
// .connect_timeout(Duration::from_secs(5))
|
||||||
|
// .build()?;
|
||||||
|
|
||||||
|
// let resp = client.request(Method::GET, url).send().await?;
|
||||||
|
|
||||||
|
// let mut stream = resp.bytes_stream();
|
||||||
|
|
||||||
|
// let mut file = File::create(self.get_cached_binary_name(binary_hash)).await?;
|
||||||
|
|
||||||
|
// while let Some(item) = stream.next().await {
|
||||||
|
// let chunk = item?;
|
||||||
|
|
||||||
|
// file.write_all(&chunk).await?;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Make sure the entire file is written before we execute it
|
||||||
|
// file.flush().await?;
|
||||||
|
|
||||||
|
// todo!()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn calculate_hash(&self, commit_sha: impl Into<Vec<u8>>) -> anyhow::Result<String> {
|
||||||
|
let mut contents: Vec<u8> = Vec::new();
|
||||||
|
contents.append(&mut self.plan.clone().into_bytes());
|
||||||
|
contents.append(&mut commit_sha.into());
|
||||||
|
|
||||||
|
let hash = blake3::hash(&contents);
|
||||||
|
|
||||||
|
let hex = hash.to_hex();
|
||||||
|
|
||||||
|
Ok(hex.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RustBinary {}
|
||||||
|
|
||||||
|
pub enum UpstreamRustBinary {
|
||||||
|
HttpBased { url: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CuddleRustUpstream> for UpstreamRustBinary {
|
||||||
|
fn from(value: CuddleRustUpstream) -> Self {
|
||||||
|
match value {
|
||||||
|
CuddleRustUpstream::Gitea { url } => Self::HttpBased { url },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&CuddleRustUpstream> for UpstreamRustBinary {
|
||||||
|
fn from(value: &CuddleRustUpstream) -> Self {
|
||||||
|
match value {
|
||||||
|
CuddleRustUpstream::Gitea { url } => Self::HttpBased { url: url.clone() },
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ impl ShellAction {
|
|||||||
|
|
||||||
let mut process = Command::new(&path)
|
let mut process = Command::new(&path)
|
||||||
.current_dir(current_dir)
|
.current_dir(current_dir)
|
||||||
.envs(variables.iter().map(|v| {
|
.envs(variables.iter().rev().map(|v| {
|
||||||
log::trace!("{:?}", v);
|
log::trace!("{:?}", v);
|
||||||
|
|
||||||
(v.name.to_uppercase(), v.value.clone())
|
(v.name.to_uppercase(), v.value.clone())
|
||||||
@@ -15,7 +15,10 @@ use crate::{
|
|||||||
util::git::GitCommit,
|
util::git::GitCommit,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::subcommands::render_template::RenderTemplateCommand;
|
use self::subcommands::{
|
||||||
|
render::RenderCommand, render_kustomize::RenderKustomizeCommand,
|
||||||
|
render_template::RenderTemplateCommand,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CuddleCli {
|
pub struct CuddleCli {
|
||||||
@@ -73,9 +76,8 @@ impl CuddleCli {
|
|||||||
if let Ok(context_iter) = self.context.clone().unwrap().lock() {
|
if let Ok(context_iter) = self.context.clone().unwrap().lock() {
|
||||||
for ctx in context_iter.iter() {
|
for ctx in context_iter.iter() {
|
||||||
if let Some(variables) = ctx.plan.vars.clone() {
|
if let Some(variables) = ctx.plan.vars.clone() {
|
||||||
for (name, var) in variables {
|
let mut variables: Vec<CuddleVariable> = variables.into();
|
||||||
self.variables.push(CuddleVariable::new(name, var))
|
self.variables.append(&mut variables);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let CuddleTreeType::Root = ctx.node_type {
|
if let CuddleTreeType::Root = ctx.node_type {
|
||||||
@@ -83,7 +85,7 @@ impl CuddleCli {
|
|||||||
temp_path.push(".cuddle/tmp");
|
temp_path.push(".cuddle/tmp");
|
||||||
|
|
||||||
self.variables.push(CuddleVariable::new(
|
self.variables.push(CuddleVariable::new(
|
||||||
"tmp".into(),
|
"tmp",
|
||||||
temp_path.clone().to_string_lossy().to_string(),
|
temp_path.clone().to_string_lossy().to_string(),
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -93,10 +95,9 @@ impl CuddleCli {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match GitCommit::new() {
|
match GitCommit::new() {
|
||||||
Ok(commit) => self.variables.push(CuddleVariable::new(
|
Ok(commit) => self
|
||||||
"commit_sha".into(),
|
.variables
|
||||||
commit.commit_sha.clone(),
|
.push(CuddleVariable::new("commit_sha", commit.commit_sha.clone())),
|
||||||
)),
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::debug!("{}", e);
|
log::debug!("{}", e);
|
||||||
}
|
}
|
||||||
@@ -126,6 +127,7 @@ impl CuddleCli {
|
|||||||
name,
|
name,
|
||||||
l.description.clone(),
|
l.description.clone(),
|
||||||
)),
|
)),
|
||||||
|
CuddleScript::Rust(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,7 +138,7 @@ impl CuddleCli {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn process_templates(self) -> anyhow::Result<Self> {
|
fn process_templates(self) -> anyhow::Result<Self> {
|
||||||
if let None = self.tmp_dir {
|
if self.tmp_dir.is_none() {
|
||||||
log::debug!("cannot process template as bare bones cli");
|
log::debug!("cannot process template as bare bones cli");
|
||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
@@ -147,11 +149,9 @@ impl CuddleCli {
|
|||||||
.clone()
|
.clone()
|
||||||
.ok_or(anyhow::anyhow!("tmp_dir does not exist aborting"))?;
|
.ok_or(anyhow::anyhow!("tmp_dir does not exist aborting"))?;
|
||||||
match self.config.get_fetch_policy()? {
|
match self.config.get_fetch_policy()? {
|
||||||
CuddleFetchPolicy::Always => {
|
CuddleFetchPolicy::Always if tmp_dir.exists() && tmp_dir.ends_with("tmp") => {
|
||||||
if tmp_dir.exists() && tmp_dir.ends_with("tmp") {
|
|
||||||
std::fs::remove_dir_all(tmp_dir.clone())?;
|
std::fs::remove_dir_all(tmp_dir.clone())?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
std::fs::create_dir_all(tmp_dir.clone())?;
|
std::fs::create_dir_all(tmp_dir.clone())?;
|
||||||
@@ -169,12 +169,30 @@ impl CuddleCli {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for file in std::fs::read_dir(template_path)?.into_iter() {
|
for file in std::fs::read_dir(&template_path)? {
|
||||||
let f = file?;
|
let f = file?;
|
||||||
let mut dest_file = tmp_dir.clone();
|
let mut dest_file = tmp_dir.clone();
|
||||||
dest_file.push(f.file_name());
|
dest_file.push(f.path().strip_prefix(&template_path)?.parent().unwrap());
|
||||||
|
|
||||||
std::fs::copy(f.path(), dest_file)?;
|
tracing::trace!(
|
||||||
|
"moving from: {} to {}",
|
||||||
|
f.path().display(),
|
||||||
|
dest_file.display()
|
||||||
|
);
|
||||||
|
|
||||||
|
if f.path().is_dir() {
|
||||||
|
std::fs::create_dir_all(&dest_file)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs_extra::copy_items(
|
||||||
|
&[f.path()],
|
||||||
|
&dest_file,
|
||||||
|
&fs_extra::dir::CopyOptions {
|
||||||
|
overwrite: true,
|
||||||
|
skip_exist: false,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,6 +212,8 @@ impl CuddleCli {
|
|||||||
|
|
||||||
root_cmd = subcommands::x::build_command(root_cmd, self.clone());
|
root_cmd = subcommands::x::build_command(root_cmd, self.clone());
|
||||||
root_cmd = subcommands::render_template::build_command(root_cmd);
|
root_cmd = subcommands::render_template::build_command(root_cmd);
|
||||||
|
root_cmd = subcommands::render_kustomize::build_command(root_cmd);
|
||||||
|
root_cmd = subcommands::render::build_command(root_cmd);
|
||||||
root_cmd = subcommands::init::build_command(root_cmd, self.clone());
|
root_cmd = subcommands::init::build_command(root_cmd, self.clone());
|
||||||
|
|
||||||
self.command = Some(root_cmd);
|
self.command = Some(root_cmd);
|
||||||
@@ -223,11 +243,20 @@ impl CuddleCli {
|
|||||||
|
|
||||||
let res = match matches.subcommand() {
|
let res = match matches.subcommand() {
|
||||||
Some(("x", exe_submatch)) => subcommands::x::execute_x(exe_submatch, self.clone()),
|
Some(("x", exe_submatch)) => subcommands::x::execute_x(exe_submatch, self.clone()),
|
||||||
|
Some(("render", sub_matches)) => {
|
||||||
|
RenderCommand::execute(sub_matches, self.clone())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Some(("render_template", sub_matches)) => {
|
Some(("render_template", sub_matches)) => {
|
||||||
RenderTemplateCommand::from_matches(sub_matches, self.clone())
|
RenderTemplateCommand::from_matches(sub_matches, self.clone())
|
||||||
.and_then(|cmd| cmd.execute())?;
|
.and_then(|cmd| cmd.execute())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Some(("render-kustomize", sub_matches)) => {
|
||||||
|
RenderKustomizeCommand::from_matches(sub_matches, self.clone())
|
||||||
|
.and_then(|cmd| cmd.execute())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Some(("init", sub_matches)) => {
|
Some(("init", sub_matches)) => {
|
||||||
subcommands::init::execute_init(sub_matches, self.clone())
|
subcommands::init::execute_init(sub_matches, self.clone())
|
||||||
}
|
}
|
||||||
169
cuddle/src/cli/subcommands/folder.rs
Normal file
169
cuddle/src/cli/subcommands/folder.rs
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||||
|
use serde_json::from_value;
|
||||||
|
use tera::Function;
|
||||||
|
|
||||||
|
use crate::{cli::CuddleCli, model::CuddleVariable};
|
||||||
|
|
||||||
|
pub fn build_command(root_cmd: Command) -> Command {
|
||||||
|
root_cmd.subcommand(
|
||||||
|
Command::new("folder")
|
||||||
|
.about("renders a template folder")
|
||||||
|
.args(&[
|
||||||
|
Arg::new("source")
|
||||||
|
.long("source")
|
||||||
|
.required(true)
|
||||||
|
.value_parser(clap::value_parser!(PathBuf)),
|
||||||
|
Arg::new("destination")
|
||||||
|
.long("destination")
|
||||||
|
.required(true)
|
||||||
|
.value_parser(clap::value_parser!(PathBuf)),
|
||||||
|
Arg::new("extra-var")
|
||||||
|
.long("extra-var")
|
||||||
|
.action(ArgAction::Append)
|
||||||
|
.required(false),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FolderCommand {
|
||||||
|
variables: Vec<CuddleVariable>,
|
||||||
|
source: PathBuf,
|
||||||
|
destination: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FolderCommand {
|
||||||
|
pub fn from_matches(matches: &ArgMatches, cli: CuddleCli) -> anyhow::Result<Self> {
|
||||||
|
let source = matches
|
||||||
|
.get_one::<PathBuf>("source")
|
||||||
|
.expect("source")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let destination = matches
|
||||||
|
.get_one::<PathBuf>("destination")
|
||||||
|
.expect("destination")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let mut extra_vars: Vec<CuddleVariable> =
|
||||||
|
if let Some(extra_vars) = matches.get_many::<String>("extra-var") {
|
||||||
|
let mut vars = Vec::with_capacity(extra_vars.len());
|
||||||
|
for var in extra_vars.into_iter() {
|
||||||
|
let parts: Vec<&str> = var.split('=').collect();
|
||||||
|
if parts.len() != 2 {
|
||||||
|
return Err(anyhow::anyhow!("extra-var: is not set correctly: {}", var));
|
||||||
|
}
|
||||||
|
|
||||||
|
vars.push(CuddleVariable::new(parts[0], parts[1]));
|
||||||
|
}
|
||||||
|
vars
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
extra_vars.append(&mut cli.variables.clone());
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
variables: extra_vars,
|
||||||
|
source,
|
||||||
|
destination,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(self) -> anyhow::Result<()> {
|
||||||
|
let _ = std::fs::remove_dir_all(&self.destination);
|
||||||
|
std::fs::create_dir_all(&self.destination).context("failed to create directory")?;
|
||||||
|
|
||||||
|
// Prepare context
|
||||||
|
let mut context = tera::Context::new();
|
||||||
|
for var in self.variables.iter().rev() {
|
||||||
|
context.insert(var.name.to_lowercase().replace([' ', '-'], "_"), &var.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut tera = tera::Tera::default();
|
||||||
|
|
||||||
|
tera.register_function("filter_by_prefix", filter_by_prefix(self.variables.clone()));
|
||||||
|
|
||||||
|
for entry in walkdir::WalkDir::new(&self.source) {
|
||||||
|
let entry = entry.context("entry was not found")?;
|
||||||
|
let entry_path = entry.path();
|
||||||
|
let rel_path = self
|
||||||
|
.destination
|
||||||
|
.join(entry_path.strip_prefix(&self.source)?);
|
||||||
|
|
||||||
|
if entry_path.is_file() {
|
||||||
|
// Load source template
|
||||||
|
let source = std::fs::read_to_string(entry_path)
|
||||||
|
.context("failed to read entry into memory")?;
|
||||||
|
|
||||||
|
let output = tera.render_str(&source, &context)?;
|
||||||
|
|
||||||
|
if let Some(parent) = rel_path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).context("failed to create parent dir")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put template in final destination
|
||||||
|
std::fs::write(&rel_path, output).context(format!(
|
||||||
|
"failed to write to destination: {}",
|
||||||
|
&rel_path.display()
|
||||||
|
))?;
|
||||||
|
|
||||||
|
log::info!("finished writing template to: {}", &rel_path.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_by_prefix(variables: Vec<CuddleVariable>) -> impl Function {
|
||||||
|
Box::new(
|
||||||
|
move |args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
|
||||||
|
for var in &variables {
|
||||||
|
tracing::info!("variable: {} - {}", var.name, var.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let prefix = match args.get("prefix") {
|
||||||
|
Some(value) => match from_value::<Vec<String>>(value.clone()) {
|
||||||
|
Ok(prefix) => prefix,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("prefix was not a string: {}", e);
|
||||||
|
return Err("prefix was not a string".into());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => return Err("prefix is required".into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let prefix = prefix.join("_");
|
||||||
|
|
||||||
|
let vars = variables
|
||||||
|
.iter()
|
||||||
|
.filter_map(|v| {
|
||||||
|
if v.name.starts_with(&prefix) {
|
||||||
|
Some(CuddleVariable::new(
|
||||||
|
v.name.trim_start_matches(&prefix).trim_start_matches('_'),
|
||||||
|
v.value.clone(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<CuddleVariable>>();
|
||||||
|
|
||||||
|
let mut structure: HashMap<String, String> = HashMap::new();
|
||||||
|
for var in vars {
|
||||||
|
if !structure.contains_key(&var.name) {
|
||||||
|
tracing::info!("found: {} - {}", &var.name, &var.value);
|
||||||
|
structure.insert(var.name, var.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(serde_json::to_value(structure).unwrap())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_by_name(args: &HashMap<String, tera::Value>) -> tera::Result<tera::Value> {
|
||||||
|
Ok(tera::Value::Null)
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ use std::fs::{create_dir_all, read, read_dir};
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
use clap::{ArgMatches, Command};
|
use clap::{ArgMatches, Command};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ pub fn execute_init(exe_submatch: &ArgMatches, _cli: CuddleCli) -> anyhow::Resul
|
|||||||
if dir.count() != 0 {
|
if dir.count() != 0 {
|
||||||
for entry in read_dir(&path)? {
|
for entry in read_dir(&path)? {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
if entry.file_name() == ".git" {
|
if entry.file_name() == ".git" || entry.file_name() == ".jj" {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
anyhow::bail!("Directory {} is not empty", &path);
|
anyhow::bail!("Directory {} is not empty", &path);
|
||||||
@@ -178,8 +179,16 @@ pub fn execute_init(exe_submatch: &ArgMatches, _cli: CuddleCli) -> anyhow::Resul
|
|||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("writing to: {}", new_path.display());
|
tracing::info!("writing to: {}", new_path.display());
|
||||||
let new_content =
|
|
||||||
replace_with_variables(&std::fs::read_to_string(entry_path)?, &template)?;
|
let old_content = match std::fs::read_to_string(entry_path) {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::debug!("found invalid file possibly with invalid utf8: {}", e);
|
||||||
|
std::fs::copy(entry_path, new_path).context("failed to write file")?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let new_content = replace_with_variables(&old_content, &template)?;
|
||||||
|
|
||||||
std::fs::write(new_path, new_content.as_bytes())?;
|
std::fs::write(new_path, new_content.as_bytes())?;
|
||||||
}
|
}
|
||||||
91
cuddle/src/cli/subcommands/kustomize.rs
Normal file
91
cuddle/src/cli/subcommands/kustomize.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use std::{io::Write, path::PathBuf};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use clap::{Arg, ArgMatches, Command};
|
||||||
|
|
||||||
|
use crate::cli::CuddleCli;
|
||||||
|
|
||||||
|
pub fn build_command(root_cmd: Command) -> Command {
|
||||||
|
root_cmd.subcommand(
|
||||||
|
Command::new("kustomize")
|
||||||
|
.about("renders a kustomize folder")
|
||||||
|
.args(&[
|
||||||
|
Arg::new("kustomize-folder")
|
||||||
|
.long("kustomize-folder")
|
||||||
|
.value_parser(clap::value_parser!(PathBuf))
|
||||||
|
.required(true),
|
||||||
|
Arg::new("destination")
|
||||||
|
.long("destination")
|
||||||
|
.required(true)
|
||||||
|
.value_parser(clap::value_parser!(PathBuf)),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct KustomizeCommand {
|
||||||
|
kustomize_folder: PathBuf,
|
||||||
|
destination: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KustomizeCommand {
|
||||||
|
pub fn from_matches(matches: &ArgMatches, _cli: CuddleCli) -> anyhow::Result<Self> {
|
||||||
|
let kustomize_folder = matches
|
||||||
|
.get_one::<PathBuf>("kustomize-folder")
|
||||||
|
.expect("kustomize-folder")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let destination = matches
|
||||||
|
.get_one::<PathBuf>("destination")
|
||||||
|
.expect("destination")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
kustomize_folder,
|
||||||
|
destination,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(self) -> anyhow::Result<()> {
|
||||||
|
let mut cmd = std::process::Command::new("kubectl");
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(&self.destination);
|
||||||
|
std::fs::create_dir_all(&self.destination)?;
|
||||||
|
|
||||||
|
let cmd = cmd
|
||||||
|
.arg("kustomize")
|
||||||
|
.arg("--enable-helm")
|
||||||
|
.arg(self.kustomize_folder);
|
||||||
|
let output = cmd.output().context("failed to run kubectl kustomize")?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"failed to run kustomize: {}",
|
||||||
|
output.status.code().expect("to find exit code")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cmd = std::process::Command::new("kubectl-slice");
|
||||||
|
let cmd = cmd
|
||||||
|
.arg("-o")
|
||||||
|
.arg(self.destination)
|
||||||
|
.stdin(std::process::Stdio::piped());
|
||||||
|
|
||||||
|
let mut child = cmd.spawn().context("failed to run kubectl-slice")?;
|
||||||
|
|
||||||
|
if let Some(mut stdin) = child.stdin.take() {
|
||||||
|
stdin.write_all(&output.stdout)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = child
|
||||||
|
.wait_with_output()
|
||||||
|
.context("failed to run kubectl-slice")?;
|
||||||
|
if !output.status.success() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"failed to run kustomize: {}",
|
||||||
|
output.status.code().expect("to find exit code")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
7
cuddle/src/cli/subcommands/mod.rs
Normal file
7
cuddle/src/cli/subcommands/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pub mod folder;
|
||||||
|
pub mod init;
|
||||||
|
pub mod kustomize;
|
||||||
|
pub mod render;
|
||||||
|
pub mod render_kustomize;
|
||||||
|
pub mod render_template;
|
||||||
|
pub mod x;
|
||||||
34
cuddle/src/cli/subcommands/render.rs
Normal file
34
cuddle/src/cli/subcommands/render.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
use anyhow::Context;
|
||||||
|
use clap::{ArgMatches, Command};
|
||||||
|
|
||||||
|
use crate::cli::CuddleCli;
|
||||||
|
|
||||||
|
use super::{folder::FolderCommand, kustomize::KustomizeCommand};
|
||||||
|
|
||||||
|
pub fn build_command(root_cmd: Command) -> Command {
|
||||||
|
let cmd = Command::new("render").about("accesses different render commands");
|
||||||
|
|
||||||
|
let cmd = super::kustomize::build_command(cmd);
|
||||||
|
let cmd = super::folder::build_command(cmd);
|
||||||
|
|
||||||
|
root_cmd.subcommand(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RenderCommand {}
|
||||||
|
impl RenderCommand {
|
||||||
|
pub fn execute(matches: &ArgMatches, cli: CuddleCli) -> anyhow::Result<()> {
|
||||||
|
match matches.subcommand() {
|
||||||
|
Some(("kustomize", sub_matches)) => {
|
||||||
|
KustomizeCommand::from_matches(sub_matches, cli)?.execute()?;
|
||||||
|
}
|
||||||
|
Some(("folder", sub_matches)) => {
|
||||||
|
FolderCommand::from_matches(sub_matches, cli)?
|
||||||
|
.execute()
|
||||||
|
.context("failed to render folder")?;
|
||||||
|
}
|
||||||
|
_ => anyhow::bail!("failed to find match for render"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
70
cuddle/src/cli/subcommands/render_kustomize.rs
Normal file
70
cuddle/src/cli/subcommands/render_kustomize.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use clap::{Arg, ArgMatches, Command};
|
||||||
|
|
||||||
|
use crate::cli::CuddleCli;
|
||||||
|
|
||||||
|
pub fn build_command(root_cmd: Command) -> Command {
|
||||||
|
root_cmd.subcommand(
|
||||||
|
Command::new("render-kustomize")
|
||||||
|
.about("renders a kustomize folder")
|
||||||
|
.args(&[
|
||||||
|
Arg::new("kustomize-folder")
|
||||||
|
.long("kustomize-folder")
|
||||||
|
.value_parser(clap::value_parser!(PathBuf))
|
||||||
|
.required(true),
|
||||||
|
Arg::new("destination")
|
||||||
|
.long("destination")
|
||||||
|
.required(true)
|
||||||
|
.value_parser(clap::value_parser!(PathBuf)),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RenderKustomizeCommand {
|
||||||
|
kustomize_folder: PathBuf,
|
||||||
|
destination: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderKustomizeCommand {
|
||||||
|
pub fn from_matches(matches: &ArgMatches, _cli: CuddleCli) -> anyhow::Result<Self> {
|
||||||
|
let kustomize_folder = matches
|
||||||
|
.get_one::<PathBuf>("kustomize-folder")
|
||||||
|
.expect("kustomize-folder")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let destination = matches
|
||||||
|
.get_one::<PathBuf>("destination")
|
||||||
|
.expect("destination")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
kustomize_folder,
|
||||||
|
destination,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(self) -> anyhow::Result<()> {
|
||||||
|
let mut cmd = std::process::Command::new("kubectl");
|
||||||
|
|
||||||
|
std::fs::create_dir_all(&self.destination)?;
|
||||||
|
|
||||||
|
let cmd = cmd
|
||||||
|
.arg("kustomize")
|
||||||
|
.arg(self.kustomize_folder)
|
||||||
|
.arg(format!("--output={}", self.destination.display()));
|
||||||
|
|
||||||
|
let mut process = cmd.spawn()?;
|
||||||
|
|
||||||
|
let exit = process.wait()?;
|
||||||
|
|
||||||
|
if !exit.success() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"failed to run kustomize: {}",
|
||||||
|
exit.code().expect("to find exit code")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
use std::{path::PathBuf, str::FromStr};
|
use std::{path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
use clap::{Arg, ArgMatches, Command};
|
use clap::{Arg, ArgMatches, Command};
|
||||||
|
|
||||||
use crate::{cli::CuddleCli, model::CuddleVariable};
|
use crate::{cli::CuddleCli, model::CuddleVariable};
|
||||||
|
|
||||||
|
const DESTINATION: &str = "destination is the output path of the template once done, but default .tmpl is stripped and the normal file extension is used. this can be overwritten if a file path is entered instead. I.e. (/some/file/name.txt)";
|
||||||
|
const TEMPLATE_FILE: &str = "template-file is the input file path of the .tmpl file (or inferred) that you would like to render";
|
||||||
|
|
||||||
pub fn build_command(root_cmd: Command) -> Command {
|
pub fn build_command(root_cmd: Command) -> Command {
|
||||||
root_cmd.subcommand(
|
root_cmd.subcommand(
|
||||||
Command::new("render_template")
|
Command::new("render_template")
|
||||||
@@ -14,19 +18,21 @@ pub fn build_command(root_cmd: Command) -> Command {
|
|||||||
.short('t')
|
.short('t')
|
||||||
.long("template-file")
|
.long("template-file")
|
||||||
.required(true)
|
.required(true)
|
||||||
.action(clap::ArgAction::Set).long_help("template-file is the input file path of the .tmpl file (or inferred) that you would like to render"),
|
.action(clap::ArgAction::Set)
|
||||||
|
.long_help(TEMPLATE_FILE),
|
||||||
Arg::new("destination")
|
Arg::new("destination")
|
||||||
.alias("dest")
|
.alias("dest")
|
||||||
.short('d')
|
.short('d')
|
||||||
.long("destination")
|
.long("destination")
|
||||||
.required(true)
|
.required(true)
|
||||||
.action(clap::ArgAction::Set)
|
.action(clap::ArgAction::Set)
|
||||||
.long_help("destination is the output path of the template once done, but default .tmpl is stripped and the normal file extension is used. this can be overwritten if a file path is entered instead. I.e. (/some/file/name.txt)"),
|
.long_help(DESTINATION),
|
||||||
Arg::new("extra-var")
|
Arg::new("extra-var")
|
||||||
.long("extra-var")
|
.long("extra-var")
|
||||||
.required(false)
|
.required(false)
|
||||||
.action(clap::ArgAction::Set),
|
.action(clap::ArgAction::Append),
|
||||||
]))
|
]),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RenderTemplateCommand {
|
pub struct RenderTemplateCommand {
|
||||||
@@ -46,18 +52,19 @@ impl RenderTemplateCommand {
|
|||||||
.get_one::<String>("destination")
|
.get_one::<String>("destination")
|
||||||
.ok_or(anyhow::anyhow!("destination was not found"))
|
.ok_or(anyhow::anyhow!("destination was not found"))
|
||||||
.and_then(get_path_buf_and_check_dir_exists)
|
.and_then(get_path_buf_and_check_dir_exists)
|
||||||
.and_then(RenderTemplateCommand::transform_extension)?;
|
.and_then(RenderTemplateCommand::transform_extension)
|
||||||
|
.context("failed to access dest directory")?;
|
||||||
|
|
||||||
let mut extra_vars: Vec<CuddleVariable> =
|
let mut extra_vars: Vec<CuddleVariable> =
|
||||||
if let Some(extra_vars) = matches.get_many::<String>("extra-var") {
|
if let Some(extra_vars) = matches.get_many::<String>("extra-var") {
|
||||||
let mut vars = Vec::with_capacity(extra_vars.len());
|
let mut vars = Vec::with_capacity(extra_vars.len());
|
||||||
for var in extra_vars.into_iter() {
|
for var in extra_vars.into_iter() {
|
||||||
let parts: Vec<&str> = var.split("=").collect();
|
let parts: Vec<&str> = var.split('=').collect();
|
||||||
if parts.len() != 2 {
|
if parts.len() != 2 {
|
||||||
return Err(anyhow::anyhow!("extra-var: is not set correctly: {}", var));
|
return Err(anyhow::anyhow!("extra-var: is not set correctly: {}", var));
|
||||||
}
|
}
|
||||||
|
|
||||||
vars.push(CuddleVariable::new(parts[0].into(), parts[1].into()));
|
vars.push(CuddleVariable::new(parts[0], parts[1]));
|
||||||
}
|
}
|
||||||
vars
|
vars
|
||||||
} else {
|
} else {
|
||||||
@@ -77,10 +84,7 @@ impl RenderTemplateCommand {
|
|||||||
// Prepare context
|
// Prepare context
|
||||||
let mut context = tera::Context::new();
|
let mut context = tera::Context::new();
|
||||||
for var in self.variables {
|
for var in self.variables {
|
||||||
context.insert(
|
context.insert(var.name.to_lowercase().replace([' ', '-'], "_"), &var.value)
|
||||||
var.name.to_lowercase().replace(" ", "_").replace("-", "_"),
|
|
||||||
&var.value,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load source template
|
// Load source template
|
||||||
@@ -88,8 +92,15 @@ impl RenderTemplateCommand {
|
|||||||
|
|
||||||
let output = tera::Tera::one_off(source.as_str(), &context, false)?;
|
let output = tera::Tera::one_off(source.as_str(), &context, false)?;
|
||||||
|
|
||||||
|
if let Some(parent) = self.destination.parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Put template in final destination
|
// Put template in final destination
|
||||||
std::fs::write(&self.destination, output)?;
|
std::fs::write(&self.destination, output).context(format!(
|
||||||
|
"failed to write to destination: {}",
|
||||||
|
&self.destination.display(),
|
||||||
|
))?;
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"finished writing template to: {}",
|
"finished writing template to: {}",
|
||||||
@@ -118,8 +129,8 @@ impl RenderTemplateCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_path_buf_and_check_exists(raw_path: &String) -> anyhow::Result<PathBuf> {
|
fn get_path_buf_and_check_exists(raw_path: impl Into<String>) -> anyhow::Result<PathBuf> {
|
||||||
match PathBuf::from_str(&raw_path) {
|
match PathBuf::from_str(&raw_path.into()) {
|
||||||
Ok(pb) => {
|
Ok(pb) => {
|
||||||
if pb.exists() {
|
if pb.exists() {
|
||||||
Ok(pb)
|
Ok(pb)
|
||||||
@@ -134,17 +145,9 @@ fn get_path_buf_and_check_exists(raw_path: &String) -> anyhow::Result<PathBuf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_path_buf_and_check_dir_exists(raw_path: &String) -> anyhow::Result<PathBuf> {
|
fn get_path_buf_and_check_dir_exists(raw_path: impl Into<String>) -> anyhow::Result<PathBuf> {
|
||||||
match PathBuf::from_str(&raw_path) {
|
match PathBuf::from_str(&raw_path.into()) {
|
||||||
Ok(pb) => {
|
Ok(pb) => Ok(pb),
|
||||||
if pb.is_dir() && pb.exists() {
|
|
||||||
Ok(pb)
|
|
||||||
} else if pb.is_file() {
|
|
||||||
Ok(pb)
|
|
||||||
} else {
|
|
||||||
Ok(pb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => Err(anyhow::anyhow!(e)),
|
Err(e) => Err(anyhow::anyhow!(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +61,7 @@ pub fn build_scripts(cli: CuddleCli) -> Vec<Command> {
|
|||||||
}
|
}
|
||||||
crate::model::CuddleScript::Dagger(_) => todo!(),
|
crate::model::CuddleScript::Dagger(_) => todo!(),
|
||||||
crate::model::CuddleScript::Lua(l) => {}
|
crate::model::CuddleScript::Lua(l) => {}
|
||||||
|
crate::model::CuddleScript::Rust(_) => todo!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmds.push(cmd)
|
cmds.push(cmd)
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
use std::{
|
use std::{
|
||||||
env::{self, current_dir},
|
env::{self, current_dir},
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
io::Write,
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use git2::{
|
use git2::{
|
||||||
build::{CheckoutBuilder, RepoBuilder},
|
build::{CheckoutBuilder, RepoBuilder},
|
||||||
FetchOptions, RemoteCallbacks,
|
FetchOptions, RemoteCallbacks,
|
||||||
@@ -46,7 +44,7 @@ pub fn extract_cuddle(
|
|||||||
|
|
||||||
// Load main cuddle file.
|
// Load main cuddle file.
|
||||||
let cuddle_yaml = find_root_cuddle()?;
|
let cuddle_yaml = find_root_cuddle()?;
|
||||||
if let None = cuddle_yaml {
|
if cuddle_yaml.is_none() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let cuddle_yaml = cuddle_yaml.unwrap();
|
let cuddle_yaml = cuddle_yaml.unwrap();
|
||||||
@@ -122,7 +120,7 @@ fn pull_parent_cuddle_into_local(
|
|||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let mut rc = RemoteCallbacks::new();
|
let mut rc = RemoteCallbacks::new();
|
||||||
rc.credentials(|_url, username_from_url, _allowed_types| {
|
rc.credentials(|_url, username_from_url, _allowed_types| {
|
||||||
if "true".to_string() == std::env::var("CUDDLE_SSH_AGENT").ok().unwrap_or("".into()) {
|
if *"true" == std::env::var("CUDDLE_SSH_AGENT").ok().unwrap_or("".into()) {
|
||||||
git2::Cred::ssh_key_from_agent(username_from_url.unwrap())
|
git2::Cred::ssh_key_from_agent(username_from_url.unwrap())
|
||||||
} else {
|
} else {
|
||||||
git2::Cred::ssh_key(
|
git2::Cred::ssh_key(
|
||||||
@@ -155,7 +153,7 @@ fn recurse_parent(
|
|||||||
context: Arc<Mutex<Vec<CuddleContext>>>,
|
context: Arc<Mutex<Vec<CuddleContext>>>,
|
||||||
) -> anyhow::Result<Option<()>> {
|
) -> anyhow::Result<Option<()>> {
|
||||||
let cuddle_contents = find_cuddle(path.clone())?;
|
let cuddle_contents = find_cuddle(path.clone())?;
|
||||||
if let None = cuddle_contents {
|
if cuddle_contents.is_none() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let cuddle_plan = serde_yaml::from_str::<CuddlePlan>(&cuddle_contents.unwrap())?;
|
let cuddle_plan = serde_yaml::from_str::<CuddlePlan>(&cuddle_contents.unwrap())?;
|
||||||
@@ -172,14 +170,12 @@ fn recurse_parent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match cuddle_plan.base {
|
match cuddle_plan.base {
|
||||||
CuddleBase::Bool(true) => {
|
CuddleBase::Bool(true) => Err(anyhow::anyhow!(
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"plan cannot be enabled without specifying a plan"
|
"plan cannot be enabled without specifying a plan"
|
||||||
))
|
)),
|
||||||
}
|
|
||||||
CuddleBase::Bool(false) => {
|
CuddleBase::Bool(false) => {
|
||||||
log::debug!("plan is root: finishing up");
|
log::debug!("plan is root: finishing up");
|
||||||
return Ok(Some(()));
|
Ok(Some(()))
|
||||||
}
|
}
|
||||||
CuddleBase::String(parent_plan) => {
|
CuddleBase::String(parent_plan) => {
|
||||||
let destination_path = create_cuddle(path.clone())?;
|
let destination_path = create_cuddle(path.clone())?;
|
||||||
@@ -189,7 +185,7 @@ fn recurse_parent(
|
|||||||
if !cuddle_dest.exists() {
|
if !cuddle_dest.exists() {
|
||||||
pull_parent_cuddle_into_local(parent_plan, cuddle_dest.clone())?;
|
pull_parent_cuddle_into_local(parent_plan, cuddle_dest.clone())?;
|
||||||
}
|
}
|
||||||
return recurse_parent(cuddle_dest, context.clone());
|
recurse_parent(cuddle_dest, context.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ mod util;
|
|||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
init_logging()?;
|
init_logging()?;
|
||||||
let _ = dotenv::dotenv();
|
let _ = dotenvy::dotenv();
|
||||||
|
|
||||||
let config = CuddleConfig::from_env()?;
|
let config = CuddleConfig::from_env()?;
|
||||||
|
|
||||||
238
cuddle/src/model.rs
Normal file
238
cuddle/src/model.rs
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum CuddleBase {
|
||||||
|
Bool(bool),
|
||||||
|
String(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
pub struct CuddleShellScriptArgEnv {
|
||||||
|
pub key: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
pub struct CuddleShellScriptArgFlag {
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub required: Option<bool>,
|
||||||
|
pub default_value: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum CuddleShellScriptArg {
|
||||||
|
#[serde(alias = "env")]
|
||||||
|
Env(CuddleShellScriptArgEnv),
|
||||||
|
#[serde(alias = "flag")]
|
||||||
|
Flag(CuddleShellScriptArgFlag),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
pub struct CuddleShellScript {
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub args: Option<HashMap<String, CuddleShellScriptArg>>,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
pub struct CuddleDaggerScript {
|
||||||
|
pub description: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
pub struct CuddleLuaScript {
|
||||||
|
pub description: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
pub enum CuddleRustUpstream {
|
||||||
|
#[serde(alias = "gitea")]
|
||||||
|
Gitea { url: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
pub struct CuddleRustScript {
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub upstream: CuddleRustUpstream,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum CuddleScript {
|
||||||
|
#[serde(alias = "shell")]
|
||||||
|
Shell(CuddleShellScript),
|
||||||
|
#[serde(alias = "dagger")]
|
||||||
|
Dagger(CuddleDaggerScript),
|
||||||
|
#[serde(alias = "lua")]
|
||||||
|
Lua(CuddleLuaScript),
|
||||||
|
#[serde(alias = "rust")]
|
||||||
|
Rust(CuddleRustScript),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
pub struct CuddlePlan {
|
||||||
|
pub base: CuddleBase,
|
||||||
|
pub vars: Option<CuddlePlanVariables>,
|
||||||
|
pub scripts: Option<HashMap<String, CuddleScript>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||||
|
pub struct CuddlePlanVariables(HashMap<String, CuddlePlanVar>);
|
||||||
|
|
||||||
|
impl From<CuddlePlanVariables> for Vec<CuddleVariable> {
|
||||||
|
fn from(value: CuddlePlanVariables) -> Self {
|
||||||
|
let variables: CuddleVariables = value.0.into();
|
||||||
|
|
||||||
|
let mut vars = variables.0;
|
||||||
|
vars.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap());
|
||||||
|
|
||||||
|
vars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum CuddlePlanVar {
|
||||||
|
Str(String),
|
||||||
|
Nested(HashMap<String, CuddlePlanVar>),
|
||||||
|
Array(Vec<CuddlePlanVar>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
struct CuddleVariables(Vec<CuddleVariable>);
|
||||||
|
|
||||||
|
impl From<HashMap<String, CuddlePlanVar>> for CuddleVariables {
|
||||||
|
fn from(value: HashMap<String, CuddlePlanVar>) -> Self {
|
||||||
|
let mut variables = Vec::new();
|
||||||
|
for (k, v) in value {
|
||||||
|
match v {
|
||||||
|
CuddlePlanVar::Str(value) => variables.push(CuddleVariable::new(k, value)),
|
||||||
|
CuddlePlanVar::Nested(nested) => {
|
||||||
|
let nested_variables: CuddleVariables = nested.into();
|
||||||
|
|
||||||
|
let mut combined_variables: Vec<_> = nested_variables
|
||||||
|
.0
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| CuddleVariable::new(format!("{}_{}", k, v.name), v.value))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
variables.append(&mut combined_variables);
|
||||||
|
}
|
||||||
|
CuddlePlanVar::Array(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CuddleVariables(variables)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, serde::Serialize)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct CuddleVariable {
|
||||||
|
pub name: String,
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CuddleVariable {
|
||||||
|
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
value: value.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HashMap<String, CuddlePlanVar>> for CuddlePlanVar {
|
||||||
|
fn from(value: HashMap<String, CuddlePlanVar>) -> Self {
|
||||||
|
CuddlePlanVar::Nested(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HashMap<&str, CuddlePlanVar>> for CuddlePlanVar {
|
||||||
|
fn from(value: HashMap<&str, CuddlePlanVar>) -> Self {
|
||||||
|
CuddlePlanVar::Nested(value.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for CuddlePlanVar {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
CuddlePlanVar::Str(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for CuddlePlanVar {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
CuddlePlanVar::Str(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::{CuddlePlanVariables, CuddleVariable};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn parse_cuddle_variables() {
|
||||||
|
let cuddle = r#"
|
||||||
|
someKey: someValue
|
||||||
|
someNestedKey:
|
||||||
|
someNestedNestedKey:
|
||||||
|
someKey: key
|
||||||
|
someKey: key
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let cuddle_var: CuddlePlanVariables = serde_yaml::from_str(cuddle).unwrap();
|
||||||
|
|
||||||
|
let mut expected = HashMap::new();
|
||||||
|
expected.insert("someKey", "someValue".into());
|
||||||
|
|
||||||
|
let mut nested_key = HashMap::new();
|
||||||
|
nested_key.insert("someKey", "key".into());
|
||||||
|
|
||||||
|
let mut nested_nested_key = HashMap::new();
|
||||||
|
nested_nested_key.insert("someKey", "key".into());
|
||||||
|
|
||||||
|
nested_key.insert("someNestedNestedKey", nested_nested_key.into());
|
||||||
|
|
||||||
|
expected.insert("someNestedKey", nested_key.into());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
CuddlePlanVariables(
|
||||||
|
expected
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (k.to_string(), v))
|
||||||
|
.collect()
|
||||||
|
),
|
||||||
|
cuddle_var
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn to_cuddle_variables() {
|
||||||
|
let cuddle = r#"
|
||||||
|
someKey: someValue
|
||||||
|
someNestedKey:
|
||||||
|
someNestedNestedKey:
|
||||||
|
someKey: key
|
||||||
|
someKey: key
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let cuddle_var: CuddlePlanVariables = serde_yaml::from_str(cuddle).unwrap();
|
||||||
|
|
||||||
|
let variables: Vec<CuddleVariable> = cuddle_var.into();
|
||||||
|
|
||||||
|
let mut expected: Vec<CuddleVariable> = vec![
|
||||||
|
CuddleVariable::new("someKey", "someValue"),
|
||||||
|
CuddleVariable::new("someNestedKey_someKey", "key"),
|
||||||
|
CuddleVariable::new("someNestedKey_someNestedNestedKey_someKey", "key"),
|
||||||
|
];
|
||||||
|
|
||||||
|
expected.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap());
|
||||||
|
|
||||||
|
assert_eq!(expected, variables);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "cuddle_cli"
|
|
||||||
description = "cuddle is a shuttle inspired script and configuration management tool. It enables sharing of workflows on developers workstations and ci"
|
|
||||||
repository = "https://git.front.kjuulh.io/kjuulh/cuddle"
|
|
||||||
readme = "../README.md"
|
|
||||||
license-file = "../LICENSE"
|
|
||||||
publishable = true
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "cuddle"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = { version = "1.0.72", features = ["backtrace"] }
|
|
||||||
serde = { version = "1.0.180", features = ["derive"] }
|
|
||||||
serde_yaml = "0.9.25"
|
|
||||||
walkdir = "2.3.3"
|
|
||||||
git2 = { version = "0.17.2", default-features = false, features = [
|
|
||||||
"vendored-libgit2",
|
|
||||||
"vendored-openssl",
|
|
||||||
"ssh",
|
|
||||||
] }
|
|
||||||
clap = { version = "4.3.19", features = ["env", "string"] }
|
|
||||||
envconfig = "0.10.0"
|
|
||||||
dirs = "5.0.1"
|
|
||||||
tracing = "0.1.37"
|
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["json", "env-filter"] }
|
|
||||||
log = { version = "0.4.19", features = ["std", "kv_unstable"] }
|
|
||||||
tera = "1.19.0"
|
|
||||||
openssl = { version = "0.10.55", features = ["vendored"] }
|
|
||||||
libz-sys = { version = "1.1.12", default-features = false, features = [
|
|
||||||
"libc",
|
|
||||||
"static",
|
|
||||||
] }
|
|
||||||
inquire = { version = "0.6.2", features = ["console"] }
|
|
||||||
tempfile = { version = "3.7.0" }
|
|
||||||
serde_json = "1.0.104"
|
|
||||||
rlua = "0.19.7"
|
|
||||||
rlua-searcher = { git = "https://git.front.kjuulh.io/kjuulh/rlua-searcher.git", rev = "2b29a9d0e86ec7f91b31dd844a58168969b7b74b" }
|
|
||||||
dotenv = { version = "0.15.0", features = ["clap"] }
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
pub mod init;
|
|
||||||
pub mod render_template;
|
|
||||||
pub mod x;
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum CuddleBase {
|
|
||||||
Bool(bool),
|
|
||||||
String(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
||||||
pub struct CuddleShellScriptArgEnv {
|
|
||||||
pub key: String,
|
|
||||||
pub description: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
||||||
pub struct CuddleShellScriptArgFlag {
|
|
||||||
pub name: String,
|
|
||||||
pub description: Option<String>,
|
|
||||||
pub required: Option<bool>,
|
|
||||||
pub default_value: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
||||||
#[serde(tag = "type")]
|
|
||||||
pub enum CuddleShellScriptArg {
|
|
||||||
#[serde(alias = "env")]
|
|
||||||
Env(CuddleShellScriptArgEnv),
|
|
||||||
#[serde(alias = "flag")]
|
|
||||||
Flag(CuddleShellScriptArgFlag),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
||||||
pub struct CuddleShellScript {
|
|
||||||
pub description: Option<String>,
|
|
||||||
pub args: Option<HashMap<String, CuddleShellScriptArg>>,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
||||||
pub struct CuddleDaggerScript {
|
|
||||||
pub description: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
||||||
pub struct CuddleLuaScript {
|
|
||||||
pub description: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
||||||
#[serde(tag = "type")]
|
|
||||||
pub enum CuddleScript {
|
|
||||||
#[serde(alias = "shell")]
|
|
||||||
Shell(CuddleShellScript),
|
|
||||||
#[serde(alias = "dagger")]
|
|
||||||
Dagger(CuddleDaggerScript),
|
|
||||||
#[serde(alias = "lua")]
|
|
||||||
Lua(CuddleLuaScript),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
||||||
pub struct CuddlePlan {
|
|
||||||
pub base: CuddleBase,
|
|
||||||
pub vars: Option<HashMap<String, String>>,
|
|
||||||
pub scripts: Option<HashMap<String, CuddleScript>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct CuddleVariable {
|
|
||||||
pub name: String,
|
|
||||||
pub value: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CuddleVariable {
|
|
||||||
pub fn new(name: String, value: String) -> Self {
|
|
||||||
Self { name, value }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
CUDDLE_FETCH_POLICY=never cuddle_cli render_template \
|
CUDDLE_FETCH_POLICY=never cuddle render_template \
|
||||||
--template-file "$TMP/input.txt.tmpl" \
|
--template-file "$TMP/input.txt.tmpl" \
|
||||||
--destination "$TMP/input.txt" \
|
--destination "$TMP/input.txt" \
|
||||||
--extra-var "extravar=someextravar"
|
--extra-var "extravar=someextravar"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"additionalProperties": false,
|
"additionalProperties": true,
|
||||||
"properties": {
|
"properties": {
|
||||||
"base": {
|
"base": {
|
||||||
"title": "Base url from which to base current cuddle plan on",
|
"title": "Base url from which to base current cuddle plan on",
|
||||||
@@ -27,6 +27,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
cargo install --path cuddle_cli/
|
cargo install --path cuddle/ --force
|
||||||
|
|
||||||
cuddle --version
|
cuddle --version
|
||||||
|
|||||||
@@ -16,11 +16,20 @@ WORKDIR /app/cuddle/
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN cargo install --target x86_64-unknown-linux-musl --path cuddle_cli
|
RUN cargo install --target x86_64-unknown-linux-musl --path cuddle
|
||||||
|
|
||||||
FROM docker:dind
|
FROM docker:dind
|
||||||
|
|
||||||
RUN apk add bash git
|
RUN apk add bash git kubectl
|
||||||
|
|
||||||
|
ENV SLICE_VERSION=v1.2.7
|
||||||
|
RUN wget -O kubectl-slice_linux_x86_64.tar.gz \
|
||||||
|
"https://github.com/patrickdappollonio/kubectl-slice/releases/download/$SLICE_VERSION/kubectl-slice_linux_x86_64.tar.gz" && \
|
||||||
|
tar -xf kubectl-slice_linux_x86_64.tar.gz && \
|
||||||
|
chmod +x ./kubectl-slice && \
|
||||||
|
mv ./kubectl-slice /usr/local/bin/kubectl-slice && \
|
||||||
|
rm kubectl-slice_linux_x86_64.tar.gz
|
||||||
|
|
||||||
RUN eval `ssh-agent`
|
RUN eval `ssh-agent`
|
||||||
|
|
||||||
COPY --from=1password/op:2 /usr/local/bin/op /usr/local/bin/op
|
COPY --from=1password/op:2 /usr/local/bin/op /usr/local/bin/op
|
||||||
|
|||||||
Reference in New Issue
Block a user