diff --git a/Cargo.lock b/Cargo.lock index 205bd06..f8d4774 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -328,6 +328,7 @@ dependencies = [ "clap", "dagger-sdk", "eyre", + "futures", "tokio", ] diff --git a/crates/cuddle-ci/Cargo.toml b/crates/cuddle-ci/Cargo.toml index 5bdd3e6..a8b3b33 100644 --- a/crates/cuddle-ci/Cargo.toml +++ b/crates/cuddle-ci/Cargo.toml @@ -14,6 +14,7 @@ dagger-sdk.workspace = true eyre.workspace = true clap.workspace = true async-trait.workspace = true +futures.workspace = true [dev-dependencies] tokio.workspace = true diff --git a/crates/cuddle-ci/src/cli.rs b/crates/cuddle-ci/src/cli.rs new file mode 100644 index 0000000..60d31e6 --- /dev/null +++ b/crates/cuddle-ci/src/cli.rs @@ -0,0 +1,156 @@ +use std::{env::Args, sync::Arc}; + +use async_trait::async_trait; + +pub struct CuddleCI { + pr_action: Arc, + main_action: Arc, + release_action: Arc, +} + +impl CuddleCI { + pub fn new( + pr: Arc, + main: Arc, + release: Arc, + ) -> Self { + Self { + pr_action: pr, + main_action: main, + release_action: release, + } + } + + pub fn with_pull_request(&mut self, pr: Arc) -> &mut Self { + self.pr_action = pr; + + self + } + + pub fn with_main(&mut self, main: Arc) -> &mut Self { + self.main_action = main; + + self + } + + pub fn with_release(&mut self, release: Arc) -> &mut Self { + self.release_action = release; + + self + } + + pub async fn execute(&mut self, args: impl IntoIterator) -> eyre::Result<()> { + let matches = clap::Command::new("cuddle-ci") + .about("is a wrapper around common CI actions") + .subcommand(clap::Command::new("pr")) + .subcommand(clap::Command::new("main")) + .subcommand(clap::Command::new("release")) + .subcommand_required(true) + .try_get_matches_from(args.into_iter())?; + + match matches.subcommand() { + Some((name, args)) => match (name, args) { + ("pr", args) => { + eprintln!("starting pr validate"); + self.pr_action.execute_pull_request().await?; + eprintln!("finished pr validate"); + } + ("main", args) => { + eprintln!("starting main validate"); + self.main_action.execute_main().await?; + eprintln!("finished main validate"); + } + ("release", args) => { + eprintln!("starting release validate"); + self.release_action.execute_release().await?; + eprintln!("finished release validate"); + } + (command_name, _) => { + eyre::bail!("command is not recognized: {}", command_name) + } + }, + None => eyre::bail!("command required a subcommand [pr, main, release] etc."), + } + + Ok(()) + } +} + +impl Default for CuddleCI { + fn default() -> Self { + Self::new( + Arc::new(DefaultPullRequestAction {}), + Arc::new(DefaultMainAction {}), + Arc::new(DefaultReleaseAction {}), + ) + } +} + +#[async_trait] +pub trait PullRequestAction { + async fn execute_pull_request(&self) -> eyre::Result<()> { + eprintln!("validate pull request: noop"); + Ok(()) + } +} + +pub struct DefaultPullRequestAction {} +#[async_trait] +impl PullRequestAction for DefaultPullRequestAction {} + +#[async_trait] +pub trait MainAction { + async fn execute_main(&self) -> eyre::Result<()> { + eprintln!("validate main: noop"); + Ok(()) + } +} + +pub struct DefaultMainAction {} +#[async_trait] +impl MainAction for DefaultMainAction {} + +#[async_trait] +pub trait ReleaseAction { + async fn execute_release(&self) -> eyre::Result<()> { + eprintln!("validate release: noop"); + + Ok(()) + } +} + +pub struct DefaultReleaseAction {} +#[async_trait] +impl ReleaseAction for DefaultReleaseAction {} + +#[cfg(test)] +mod test { + use super::*; + + #[tokio::test] + async fn test_can_call_default() -> eyre::Result<()> { + CuddleCI::default().execute(["cuddle-ci", "pr"]).await?; + + Ok(()) + } + + #[tokio::test] + async fn test_fails_on_no_command() -> eyre::Result<()> { + let res = CuddleCI::default().execute(["cuddle-ci"]).await; + + assert!(res.is_err()); + + Ok(()) + } + + #[tokio::test] + async fn test_fails_on_wrong_command() -> eyre::Result<()> { + let res = CuddleCI::default() + .execute(["cuddle-ci", "something"]) + .await; + + assert!(res.is_err()); + + Ok(()) + } +} diff --git a/crates/cuddle-ci/src/dagger_middleware.rs b/crates/cuddle-ci/src/dagger_middleware.rs new file mode 100644 index 0000000..4950f31 --- /dev/null +++ b/crates/cuddle-ci/src/dagger_middleware.rs @@ -0,0 +1,54 @@ +use async_trait::async_trait; +use dagger_sdk::Container; +use std::{future::Future, pin::Pin}; + +#[async_trait] +pub trait DaggerMiddleware { + async fn handle( + &mut self, + container: dagger_sdk::Container, + ) -> eyre::Result { + Ok(container) + } +} + +pub struct DaggerMiddlewareFn +where + F: FnMut(Container) -> Pin> + Send>>, +{ + pub func: F, +} + +pub fn middleware(func: F) -> Box> +where + F: FnMut(Container) -> Pin> + Send>>, +{ + Box::new(DaggerMiddlewareFn { func }) +} + +#[async_trait] +impl DaggerMiddleware for DaggerMiddlewareFn +where + F: FnMut(Container) -> Pin> + Send>> + + Send + + Sync, +{ + async fn handle(&mut self, container: Container) -> eyre::Result { + // Call the closure stored in the struct + (self.func)(container).await + } +} + +#[cfg(test)] +mod test { + use futures::FutureExt; + + use super::*; + + #[tokio::test] + async fn can_add_middleware() -> eyre::Result<()> { + middleware(|c| async move { Ok(c) }.boxed()); + + Ok(()) + } +} diff --git a/crates/cuddle-ci/src/lib.rs b/crates/cuddle-ci/src/lib.rs index fbedd19..b735bf6 100644 --- a/crates/cuddle-ci/src/lib.rs +++ b/crates/cuddle-ci/src/lib.rs @@ -1,155 +1,110 @@ -use std::{env::Args, sync::Arc}; +pub mod cli; +pub use cli::*; -use async_trait::async_trait; +pub mod dagger_middleware; -pub struct CuddleCI { - pr_action: Arc, - main_action: Arc, - release_action: Arc, -} +pub mod rust_service { + use std::{future::Future, pin::Pin, sync::Arc}; -impl CuddleCI { - pub fn new( - pr: Arc, - main: Arc, - release: Arc, - ) -> Self { - Self { - pr_action: pr, - main_action: main, - release_action: release, + use async_trait::async_trait; + use dagger_sdk::Container; + use futures::future::BoxFuture; + + use crate::{dagger_middleware::DaggerMiddleware, MainAction, PullRequestAction}; + + pub type DynMiddleware = Box; + + pub enum RustServiceStage { + BeforeBase(DynMiddleware), + AfterBase(DynMiddleware), + BeforeRelease(DynMiddleware), + AfterRelease(DynMiddleware), + } + + pub struct RustService { + client: dagger_sdk::Query, + + base_image: Option, + + stages: Vec, + } + + impl From for RustService { + fn from(value: dagger_sdk::Query) -> Self { + Self { + client: value, + base_image: None, + stages: Vec::new(), + } } } - pub fn with_pull_request(&mut self, pr: Arc) -> &mut Self { - self.pr_action = pr; - - self - } - - pub fn with_main(&mut self, main: Arc) -> &mut Self { - self.main_action = main; - - self - } - - pub fn with_release(&mut self, release: Arc) -> &mut Self { - self.release_action = release; - - self - } - - pub async fn execute(&mut self, args: impl IntoIterator) -> eyre::Result<()> { - let matches = clap::Command::new("cuddle-ci") - .about("is a wrapper around common CI actions") - .subcommand(clap::Command::new("pr")) - .subcommand(clap::Command::new("main")) - .subcommand(clap::Command::new("release")) - .subcommand_required(true) - .try_get_matches_from(args.into_iter())?; - - match matches.subcommand() { - Some((name, args)) => match (name, args) { - ("pr", args) => { - eprintln!("starting pr validate"); - self.pr_action.validate().await?; - eprintln!("finished pr validate"); - } - ("main", args) => { - eprintln!("starting main validate"); - self.main_action.validate().await?; - eprintln!("finished main validate"); - } - ("release", args) => { - eprintln!("starting release validate"); - self.release_action.validate().await?; - eprintln!("finished release validate"); - } - (command_name, _) => { - eyre::bail!("command is not recognized: {}", command_name) - } - }, - None => eyre::bail!("command required a subcommand [pr, main, release] etc."), + impl RustService { + pub async fn new() -> eyre::Result { + Ok(Self { + client: dagger_sdk::connect().await?, + base_image: None, + stages: Vec::new(), + }) } - Ok(()) + pub fn with_base_image(&mut self, base: dagger_sdk::Container) -> &mut Self { + self.base_image = Some(base); + + self + } + + pub fn add_stage(&mut self, stage: RustServiceStage) -> &mut Self { + self + } + + pub fn with_sqlx(&mut self) -> &mut Self { + self + } + + pub async fn build_release(&self) -> eyre::Result> { + Ok(Vec::new()) + } + } + + #[async_trait] + impl PullRequestAction for RustService { + async fn execute_pull_request(&self) -> eyre::Result<()> { + self.build_release().await?; + + Ok(()) + } + } + + #[async_trait] + impl MainAction for RustService { + async fn execute_main(&self) -> eyre::Result<()> { + Ok(()) + } } } -impl Default for CuddleCI { - fn default() -> Self { - Self::new( - Arc::new(DefaultPullRequestAction {}), - Arc::new(DefaultMainAction {}), - Arc::new(DefaultReleaseAction {}), - ) - } -} - -#[async_trait] -pub trait PullRequestAction { - async fn validate(&self) -> eyre::Result<()> { - eprintln!("validate pull request: noop"); - Ok(()) - } -} - -pub struct DefaultPullRequestAction {} -#[async_trait] -impl PullRequestAction for DefaultPullRequestAction {} - -#[async_trait] -pub trait MainAction { - async fn validate(&self) -> eyre::Result<()> { - eprintln!("validate main: noop"); - Ok(()) - } -} - -pub struct DefaultMainAction {} -#[async_trait] -impl MainAction for DefaultMainAction {} - -#[async_trait] -pub trait ReleaseAction { - async fn validate(&self) -> eyre::Result<()> { - eprintln!("validate release: noop"); - - Ok(()) - } -} - -pub struct DefaultReleaseAction {} -#[async_trait] -impl ReleaseAction for DefaultReleaseAction {} - #[cfg(test)] mod test { + use futures::FutureExt; + + use crate::{ + dagger_middleware::middleware, + rust_service::{RustService, RustServiceStage}, + }; + use super::*; #[tokio::test] - async fn test_can_call_default() -> eyre::Result<()> { - CuddleCI::default().execute(["cuddle-ci", "pr"]).await?; + async fn can_build_rust() -> eyre::Result<()> { + let client = dagger_sdk::connect().await?; - Ok(()) - } - - #[tokio::test] - async fn test_fails_on_no_command() -> eyre::Result<()> { - let res = CuddleCI::default().execute(["cuddle-ci"]).await; - - assert!(res.is_err()); - - Ok(()) - } - - #[tokio::test] - async fn test_fails_on_wrong_command() -> eyre::Result<()> { - let res = CuddleCI::default() - .execute(["cuddle-ci", "something"]) - .await; - - assert!(res.is_err()); + RustService::from(client.clone()) + .with_base_image(client.container().from("rustlang/rust:nightly")) + .with_sqlx() + .add_stage(RustServiceStage::BeforeBase(middleware(|c| async move { Ok(c) }.boxed()))) + .build_release() + .await?; Ok(()) }