diff --git a/Cargo.lock b/Cargo.lock index ec31792..07ed801 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -331,6 +331,9 @@ dependencies = [ "dagger-sdk", "eyre", "futures", + "serde", + "serde_json", + "serde_yaml", "tokio", ] @@ -1415,6 +1418,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15e0ef66bf939a7c890a0bf6d5a733c70202225f9888a89ed5c62298b019129" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1764,6 +1780,12 @@ dependencies = [ "void", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index f195bca..e79375d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,3 +27,6 @@ color-eyre = "*" clap = {version = "4", features = ["derive"]} futures = "0.3.29" async-scoped = { version = "0.8.0", features = ["tokio", "use-tokio"] } +serde_json = { version = "1" } +serde_yaml = {version = "0.9"} +serde = {version = "1", features = ["derive"]} diff --git a/crates/cuddle-ci/Cargo.toml b/crates/cuddle-ci/Cargo.toml index 065257e..b3654be 100644 --- a/crates/cuddle-ci/Cargo.toml +++ b/crates/cuddle-ci/Cargo.toml @@ -19,6 +19,9 @@ clap.workspace = true async-trait.workspace = true futures.workspace = true tokio.workspace = true +serde_json.workspace = true +serde_yaml.workspace = true +serde.workspace = true [dev-dependencies] tokio.workspace = true diff --git a/crates/cuddle-ci/src/cuddle_file.rs b/crates/cuddle-ci/src/cuddle_file.rs new file mode 100644 index 0000000..6f05683 --- /dev/null +++ b/crates/cuddle-ci/src/cuddle_file.rs @@ -0,0 +1,129 @@ +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct CuddleFile { + pub vars: CuddleVars, + pub deployment: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct CuddleVars { + pub service: String, + pub registry: String, + + pub clusters: CuddleClusters, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct CuddleDeployment { + pub registry: String, + pub env: CuddleDeploymentEnv, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct CuddleDeploymentEnv(pub BTreeMap); + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct CuddleDeploymentCluster { + pub clusters: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct CuddleClusters(pub BTreeMap); + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct CuddleCluster { + pub namespace: String, +} + +impl CuddleFile { + pub async fn from_cuddle_file() -> eyre::Result { + let cuddle_file_content = tokio::fs::read_to_string("cuddle.yaml").await?; + + Self::parse_cuddle_file(&cuddle_file_content) + } + + pub fn parse_cuddle_file(content: &str) -> eyre::Result { + let cuddle_file: CuddleFile = serde_yaml::from_str(content)?; + + Ok(cuddle_file) + } +} + +#[cfg(test)] +mod test { + use std::collections::BTreeMap; + + use crate::cuddle_file::{ + CuddleCluster, CuddleClusters, CuddleDeploymentCluster, CuddleDeploymentEnv, CuddleVars, + }; + + use super::CuddleFile; + + #[test] + fn parse_file() { + let cuddle_file = r#" +base: "git@git.front.kjuulh.io:kjuulh/cuddle-base.git" + +vars: + service: "infrastructure-example" + registry: kasperhermansen + + clusters: + clank_prod: + replicas: "3" + namespace: clank_prod + +deployment: + registry: git@git.front.kjuulh.io:kjuulh/clank-clusters + env: + prod: + clusters: + - clank_prod + +scripts: + render: + type: shell + args: + cluster: + name: cluster + type: flag + image_tag: + name: image_tag + type: flag"#; + + let res = CuddleFile::parse_cuddle_file(cuddle_file).expect("to parse file"); + + let mut clusters = BTreeMap::new(); + clusters.insert( + "clank_prod".into(), + CuddleCluster { + namespace: "clank_prod".into(), + }, + ); + + let mut deployment = BTreeMap::new(); + deployment.insert( + "prod".into(), + CuddleDeploymentCluster { + clusters: vec!["clank_prod".into()], + }, + ); + + let expected = CuddleFile { + vars: CuddleVars { + service: "infrastructure-example".into(), + registry: "kasperhermansen".into(), + clusters: CuddleClusters(clusters), + }, + deployment: Some(crate::cuddle_file::CuddleDeployment { + registry: "git@git.front.kjuulh.io:kjuulh/clank-cluster".into(), + env: CuddleDeploymentEnv(deployment), + }), + }; + + assert_eq!(expected, res) + } +} diff --git a/crates/cuddle-ci/src/cuddle_releaser.rs b/crates/cuddle-ci/src/cuddle_releaser.rs new file mode 100644 index 0000000..ae676be --- /dev/null +++ b/crates/cuddle-ci/src/cuddle_releaser.rs @@ -0,0 +1,103 @@ +use async_trait::async_trait; +use eyre::Context; + +use crate::{cuddle_file::CuddleFile, MainAction}; + +pub struct CuddleReleaser { + client: dagger_sdk::Query, + env: String, + cuddle_file: CuddleFile, + + folder: String, +} + +pub struct CuddleReleaserOptions { + upstream: String, + cluster: String, + namespace: String, + app: String, +} + +impl CuddleReleaser { + pub async fn new(client: dagger_sdk::Query) -> eyre::Result { + let cuddle_file = CuddleFile::from_cuddle_file().await?; + + let env = std::env::var("CUDDLE_ENV").context("CUDDLE_ENV was not set")?; + + Ok(Self { + client, + cuddle_file, + env, + folder: ".cuddle/tmp/k8s".into(), + }) + } +} + +#[async_trait] +impl MainAction for CuddleReleaser { + async fn execute_main(&self) -> eyre::Result<()> { + let client = self.client.clone(); + + if self.cuddle_file.deployment.is_none() { + return Ok(()); + } + + let chosen_cluster = match self + .cuddle_file + .deployment + .as_ref() + .unwrap() + .env + .0 + .get(&self.env.to_string()) + { + Some(c) => match c.clusters.first().take() { + Some(c) => c, + None => return Ok(()), + }, + None => return Ok(()), + }; + + let cluster = match self.cuddle_file.vars.clusters.0.get(chosen_cluster) { + Some(c) => c, + None => eyre::bail!("no cluster found for: {}", chosen_cluster), + }; + + let options = CuddleReleaserOptions { + cluster: chosen_cluster.clone(), + namespace: cluster.namespace.clone(), + app: self.cuddle_file.vars.service.clone(), + upstream: self + .cuddle_file + .deployment + .as_ref() + .unwrap() + .registry + .clone(), + }; + + let cuddle_releaser_image = "docker.io/kasperhermansen:cuddle-releaser:main-1706438736"; + + let folder = client.host().directory(&self.folder); + + let cuddle_releaser = client + .container() + .from(cuddle_releaser_image) + .with_mounted_directory("/mnt/templates", folder); + + cuddle_releaser + .with_exec(vec![ + "cuddle-releaser", + "release", + &format!("--upstream={}", options.upstream), + &format!("--folder={}", "/mnt/templates"), + &format!("--cluster={}", options.cluster), + &format!("--namespace={}", options.namespace), + &format!("--app={}", options.app), + ]) + .sync() + .await?; + + Ok(()) + } +} diff --git a/crates/cuddle-ci/src/lib.rs b/crates/cuddle-ci/src/lib.rs index e40d12a..46c2eca 100644 --- a/crates/cuddle-ci/src/lib.rs +++ b/crates/cuddle-ci/src/lib.rs @@ -1,67 +1,12 @@ pub mod cli; -pub mod leptos_service; pub use cli::*; -pub mod cuddle_please; -pub mod dagger_middleware; +pub mod leptos_service; pub mod node_service; pub mod rust_lib; pub mod rust_service; -pub mod cuddle_releaser { - use async_trait::async_trait; - - use crate::{rust_service::RustService, MainAction}; - - pub struct CuddleReleaser { - client: dagger_sdk::Query, - options: CuddleReleaserOptions, - } - - pub struct CuddleReleaserOptions { - upstream: String, - folder: String, - cluster: String, - namespace: String, - app: String, - } - - impl CuddleReleaser { - pub async fn new( - client: dagger_sdk::Query, - options: CuddleReleaserOptions, - ) -> eyre::Result { - Ok(Self { client, options }) - } - } - - #[async_trait] - impl MainAction for CuddleReleaser { - async fn execute_main(&self) -> eyre::Result<()> { - let client = self.client; - - let cuddle_releaser_image = "docker.io/kasperhermansen:cuddle-releaser:main-1706438736"; - - let folder = client.host().directory(self.options.folder); - - let cuddle_releaser = client - .container() - .from(cuddle_releaser_image) - .with_mounted_directory("/mnt/templates", folder); - - cuddle_releaser - .with_exec(vec![ - "cuddle-releaser", - "release", - &format!("--upstream={}", self.options.upstream), - &format!("--folder={}", "/mnt/templates"), - &format!("--cluster={}", self.options.cluster), - &format!("--namespace={}", self.options.namespace), - &format!("--app={}", self.options.app), - ]) - .sync()?; - - Ok(()) - } - } -} +pub mod cuddle_file; +pub mod cuddle_please; +pub mod cuddle_releaser; +pub mod dagger_middleware; diff --git a/crates/cuddle-ci/src/rust_service.rs b/crates/cuddle-ci/src/rust_service.rs index 5ba065d..148dcbd 100644 --- a/crates/cuddle-ci/src/rust_service.rs +++ b/crates/cuddle-ci/src/rust_service.rs @@ -466,13 +466,13 @@ mod test { .with_apt(&["git"]) .with_cargo_binstall("latest", ["sqlx-cli"]) .with_mold("2.3.3") - .with_stage(RustServiceStage::BeforeDeps(middleware(|c| { - async move { - // Noop - Ok(c) - } - .boxed() - }))) + // .with_stage(RustServiceStage::BeforeDeps(middleware(|c| { + // async move { + // // Noop + // Ok(c) + // } + // .boxed() + // }))) .with_clap_sanity_test() .build_release() .await?;