diff --git a/Cargo.lock b/Cargo.lock index 6960c60..4110862 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,10 +2,388 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anyhow" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c794e162a5eff65c72ef524dfe393eb923c354e350bb78b9c7383df13f3bc142" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "base" version = "0.1.0" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "cuddle_cli" version = "0.1.0" +dependencies = [ + "anyhow", + "git2", + "serde", + "serde_yaml", + "walkdir", +] + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "git2" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" + +[[package]] +name = "libgit2-sys" +version = "0.14.0+1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a00859c70c8a4f7218e6d1cc32875c4b55f6799445b842b0d8ed5e4c3d959b" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libssh2-sys" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b7c9017c64a49806c6e8df8ef99b92446d09c92457f85f91835b01a8064ae0" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "syn" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "931179334a56395bcf64ba5e0ff56781381c1a5832178280c7d7f91d1679aeb0" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/cuddle_cli/Cargo.toml b/cuddle_cli/Cargo.toml index 564cd03..e9a4d8d 100644 --- a/cuddle_cli/Cargo.toml +++ b/cuddle_cli/Cargo.toml @@ -6,3 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.60" +serde = { version = "1.0.143", features = ["derive"] } +serde_yaml = "0.9.4" +walkdir = "2.3.2" +git2 = { version = "0.15.0", features = ["ssh"] } diff --git a/cuddle_cli/src/main.rs b/cuddle_cli/src/main.rs index e7a11a9..d069101 100644 --- a/cuddle_cli/src/main.rs +++ b/cuddle_cli/src/main.rs @@ -1,3 +1,205 @@ -fn main() { - println!("Hello, world!"); +use std::{ + collections::HashMap, + env::{self, current_dir}, + ffi::OsStr, + path::{Path, PathBuf}, + sync::{Arc, Mutex}, +}; + +use git2::{build::RepoBuilder, FetchOptions, RemoteCallbacks}; +use serde::Deserialize; + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(untagged)] +enum CuddleBase { + Bool(bool), + String(String), +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +struct CuddleShellScript {} +#[derive(Debug, Clone, PartialEq, Deserialize)] +struct CuddleDaggerScript {} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(tag = "type")] +enum CuddleScript { + #[serde(alias = "shell")] + Shell(CuddleShellScript), + #[serde(alias = "dagger")] + Dagger(CuddleDaggerScript), +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +struct CuddlePlan { + pub base: CuddleBase, + pub scripts: Option>, +} + +#[derive(Debug)] +struct CuddleContext { + pub plan: CuddlePlan, + pub path: PathBuf, +} + +fn main() -> anyhow::Result<()> { + let mut curr_dir = current_dir()?; + curr_dir.push(".cuddle/"); + if let Err(res) = std::fs::remove_dir_all(curr_dir) { + println!("{}", res); + } + + // Load main cuddle file + let cuddle_yaml = find_root_cuddle()?; + // TODO: Set trace + println!("{}", cuddle_yaml); + let cuddle_plan = serde_yaml::from_str::(cuddle_yaml.as_str())?; + + // TODO: Set debug + println!("{:?}", cuddle_plan); + + let context: Arc>> = Arc::new(Mutex::new(Vec::new())); + context.lock().unwrap().push(CuddleContext { + plan: cuddle_plan.clone(), + path: current_dir()?, + }); + + // pull parent plan and execute recursive descent + match cuddle_plan.base { + CuddleBase::Bool(true) => { + return Err(anyhow::anyhow!( + "plan cannot be enabled without specifying a plan" + )) + } + CuddleBase::Bool(false) => { + println!("plan is root skipping") + } + CuddleBase::String(parent_plan) => { + let destination_path = create_cuddle_local()?; + let mut cuddle_dest = destination_path.clone(); + cuddle_dest.push("base"); + + pull_parent_cuddle_into_local(parent_plan, cuddle_dest.clone())?; + recurse_parent(cuddle_dest, context.clone())?; + } + } + + if let Ok(ctx) = context.lock() { + println!("{:?}", ctx) + } else { + return Err(anyhow::anyhow!("could not acquire lock")); + } + + Ok(()) +} + +fn create_cuddle_local() -> anyhow::Result { + let mut curr_dir = current_dir()?; + curr_dir.push(".cuddle/"); + + if curr_dir.exists() { + println!(".cuddle already exists skipping"); + return Ok(curr_dir); + } + + std::fs::create_dir(curr_dir.clone())?; + + Ok(curr_dir) +} + +fn create_cuddle(path: PathBuf) -> anyhow::Result { + let mut curr_dir = path.clone(); + curr_dir.push(".cuddle/"); + + if curr_dir.exists() { + println!(".cuddle already exists skipping"); + return Ok(curr_dir); + } + + std::fs::create_dir(curr_dir.clone())?; + + Ok(curr_dir) +} + +fn pull_parent_cuddle_into_local( + parent_cuddle: String, + destination: PathBuf, +) -> anyhow::Result<()> { + let mut rc = RemoteCallbacks::new(); + rc.credentials(|_url, username_from_url, _allowed_types| { + git2::Cred::ssh_key( + username_from_url.unwrap(), + None, + Path::new(&format!("{}/.ssh/id_ed25519", env::var("HOME").unwrap())), + None, + ) + }); + + let mut fo = FetchOptions::new(); + fo.remote_callbacks(rc); + + RepoBuilder::new() + .fetch_options(fo) + .clone(&parent_cuddle, &destination)?; + + println!("pulled: {}", parent_cuddle); + + Ok(()) +} + +fn recurse_parent(path: PathBuf, context: Arc>>) -> anyhow::Result<()> { + let cuddle_contents = find_cuddle(path.clone())?; + let cuddle_plan = serde_yaml::from_str::(&cuddle_contents)?; + + let ctx = context.clone(); + if let Ok(mut ctxs) = ctx.lock() { + ctxs.push(CuddleContext { + plan: cuddle_plan.clone(), + path: path.clone(), + }); + } else { + return Err(anyhow::anyhow!("Could not acquire lock, aborting")); + } + + match cuddle_plan.base { + CuddleBase::Bool(true) => { + return Err(anyhow::anyhow!( + "plan cannot be enabled without specifying a plan" + )) + } + CuddleBase::Bool(false) => { + println!("plan is root, finishing up"); + return Ok(()); + } + CuddleBase::String(parent_plan) => { + let destination_path = create_cuddle(path.clone())?; + let mut cuddle_dest = destination_path.clone(); + cuddle_dest.push("base"); + + pull_parent_cuddle_into_local(parent_plan, cuddle_dest.clone())?; + return recurse_parent(cuddle_dest, context.clone()); + } + } +} + +fn find_root_cuddle() -> anyhow::Result { + // TODO: Make recursive towards root + let current_dir = env::current_dir()?; + find_cuddle(current_dir) +} + +fn find_cuddle(path: PathBuf) -> anyhow::Result { + for entry in std::fs::read_dir(path)? { + let entry = entry?; + let path = entry.path(); + + let metadata = std::fs::metadata(&path)?; + if metadata.is_file() && path.file_name().unwrap() == OsStr::new("cuddle.yaml") { + return Ok(std::fs::read_to_string(path)?); + } + } + + Err(anyhow::anyhow!( + "Could not find 'cuddle.yaml' in the current directory" + )) } diff --git a/examples/base/.cuddle/base b/examples/base/.cuddle/base new file mode 160000 index 0000000..4a0d007 --- /dev/null +++ b/examples/base/.cuddle/base @@ -0,0 +1 @@ +Subproject commit 4a0d007dbbd532d6c1b8d73c845cda85f7330b33 diff --git a/examples/base/cuddle.yaml b/examples/base/cuddle.yaml index eda4a66..4e2437b 100644 --- a/examples/base/cuddle.yaml +++ b/examples/base/cuddle.yaml @@ -1,3 +1,7 @@ # yaml-language-server: $schema=../../schemas/base.json -base: "git@git.front.kjuulh.io:kjuulh/cuddle-base.git" +base: "git@git.front.kjuulh.io:kjuulh/cuddle-rust-plan.git" + +scripts: + build: + type: shell diff --git a/schemas/base.json b/schemas/base.json index 3e34a93..27d84e9 100644 --- a/schemas/base.json +++ b/schemas/base.json @@ -15,6 +15,26 @@ "title": "Whether it is enabled or not" } ] + }, + "scripts": { + "type": "object", + "title": "Scripts the cuddle cli can execute", + "description": "Scripts the cuddle cli can execute 'cuddle x my-awesome-script'", + "patternProperties": { + "^.*$": { + "required": [ + "type" + ], + "properties": { + "type": { + "enum": [ + "shell", + "dagger" + ] + } + } + } + } } }, "required": [