feat: with middleware
Some checks failed
continuous-integration/drone/push Build is failing

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2023-11-25 23:09:49 +01:00
parent 3e9a840851
commit 82ccdefd93
Signed by: kjuulh
GPG Key ID: 57B6E1465221F912
5 changed files with 302 additions and 135 deletions

1
Cargo.lock generated
View File

@ -328,6 +328,7 @@ dependencies = [
"clap", "clap",
"dagger-sdk", "dagger-sdk",
"eyre", "eyre",
"futures",
"tokio", "tokio",
] ]

View File

@ -14,6 +14,7 @@ dagger-sdk.workspace = true
eyre.workspace = true eyre.workspace = true
clap.workspace = true clap.workspace = true
async-trait.workspace = true async-trait.workspace = true
futures.workspace = true
[dev-dependencies] [dev-dependencies]
tokio.workspace = true tokio.workspace = true

156
crates/cuddle-ci/src/cli.rs Normal file
View File

@ -0,0 +1,156 @@
use std::{env::Args, sync::Arc};
use async_trait::async_trait;
pub struct CuddleCI {
pr_action: Arc<dyn PullRequestAction + Send + Sync>,
main_action: Arc<dyn MainAction + Send + Sync>,
release_action: Arc<dyn ReleaseAction + Send + Sync>,
}
impl CuddleCI {
pub fn new(
pr: Arc<dyn PullRequestAction + Send + Sync>,
main: Arc<dyn MainAction + Send + Sync>,
release: Arc<dyn ReleaseAction + Send + Sync>,
) -> Self {
Self {
pr_action: pr,
main_action: main,
release_action: release,
}
}
pub fn with_pull_request(&mut self, pr: Arc<dyn PullRequestAction + Send + Sync>) -> &mut Self {
self.pr_action = pr;
self
}
pub fn with_main(&mut self, main: Arc<dyn MainAction + Send + Sync>) -> &mut Self {
self.main_action = main;
self
}
pub fn with_release(&mut self, release: Arc<dyn ReleaseAction + Send + Sync>) -> &mut Self {
self.release_action = release;
self
}
pub async fn execute(&mut self, args: impl IntoIterator<Item = &str>) -> 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(())
}
}

View File

@ -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<dagger_sdk::Container> {
Ok(container)
}
}
pub struct DaggerMiddlewareFn<F>
where
F: FnMut(Container) -> Pin<Box<dyn Future<Output = eyre::Result<Container>> + Send>>,
{
pub func: F,
}
pub fn middleware<F>(func: F) -> Box<DaggerMiddlewareFn<F>>
where
F: FnMut(Container) -> Pin<Box<dyn Future<Output = eyre::Result<Container>> + Send>>,
{
Box::new(DaggerMiddlewareFn { func })
}
#[async_trait]
impl<F> DaggerMiddleware for DaggerMiddlewareFn<F>
where
F: FnMut(Container) -> Pin<Box<dyn Future<Output = eyre::Result<Container>> + Send>>
+ Send
+ Sync,
{
async fn handle(&mut self, container: Container) -> eyre::Result<Container> {
// 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(())
}
}

View File

@ -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 { pub mod rust_service {
pr_action: Arc<dyn PullRequestAction + Send + Sync>, use std::{future::Future, pin::Pin, sync::Arc};
main_action: Arc<dyn MainAction + Send + Sync>,
release_action: Arc<dyn ReleaseAction + Send + Sync>,
}
impl CuddleCI { use async_trait::async_trait;
pub fn new( use dagger_sdk::Container;
pr: Arc<dyn PullRequestAction + Send + Sync>, use futures::future::BoxFuture;
main: Arc<dyn MainAction + Send + Sync>,
release: Arc<dyn ReleaseAction + Send + Sync>, use crate::{dagger_middleware::DaggerMiddleware, MainAction, PullRequestAction};
) -> Self {
pub type DynMiddleware = Box<dyn DaggerMiddleware + Send + Sync>;
pub enum RustServiceStage {
BeforeBase(DynMiddleware),
AfterBase(DynMiddleware),
BeforeRelease(DynMiddleware),
AfterRelease(DynMiddleware),
}
pub struct RustService {
client: dagger_sdk::Query,
base_image: Option<dagger_sdk::Container>,
stages: Vec<RustServiceStage>,
}
impl From<dagger_sdk::Query> for RustService {
fn from(value: dagger_sdk::Query) -> Self {
Self { Self {
pr_action: pr, client: value,
main_action: main, base_image: None,
release_action: release, stages: Vec::new(),
}
} }
} }
pub fn with_pull_request(&mut self, pr: Arc<dyn PullRequestAction + Send + Sync>) -> &mut Self { impl RustService {
self.pr_action = pr; pub async fn new() -> eyre::Result<Self> {
Ok(Self {
client: dagger_sdk::connect().await?,
base_image: None,
stages: Vec::new(),
})
}
pub fn with_base_image(&mut self, base: dagger_sdk::Container) -> &mut Self {
self.base_image = Some(base);
self self
} }
pub fn with_main(&mut self, main: Arc<dyn MainAction + Send + Sync>) -> &mut Self { pub fn add_stage(&mut self, stage: RustServiceStage) -> &mut Self {
self.main_action = main;
self self
} }
pub fn with_release(&mut self, release: Arc<dyn ReleaseAction + Send + Sync>) -> &mut Self { pub fn with_sqlx(&mut self) -> &mut Self {
self.release_action = release;
self self
} }
pub async fn execute(&mut self, args: impl IntoIterator<Item = &str>) -> eyre::Result<()> { pub async fn build_release(&self) -> eyre::Result<Vec<Container>> {
let matches = clap::Command::new("cuddle-ci") Ok(Vec::new())
.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() { #[async_trait]
Some((name, args)) => match (name, args) { impl PullRequestAction for RustService {
("pr", args) => { async fn execute_pull_request(&self) -> eyre::Result<()> {
eprintln!("starting pr validate"); self.build_release().await?;
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."),
}
Ok(()) Ok(())
} }
}
impl Default for CuddleCI {
fn default() -> Self {
Self::new(
Arc::new(DefaultPullRequestAction {}),
Arc::new(DefaultMainAction {}),
Arc::new(DefaultReleaseAction {}),
)
} }
}
#[async_trait] #[async_trait]
pub trait PullRequestAction { impl MainAction for RustService {
async fn validate(&self) -> eyre::Result<()> { async fn execute_main(&self) -> eyre::Result<()> {
eprintln!("validate pull request: noop");
Ok(()) 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)] #[cfg(test)]
mod test { mod test {
use futures::FutureExt;
use crate::{
dagger_middleware::middleware,
rust_service::{RustService, RustServiceStage},
};
use super::*; use super::*;
#[tokio::test] #[tokio::test]
async fn test_can_call_default() -> eyre::Result<()> { async fn can_build_rust() -> eyre::Result<()> {
CuddleCI::default().execute(["cuddle-ci", "pr"]).await?; let client = dagger_sdk::connect().await?;
Ok(()) RustService::from(client.clone())
} .with_base_image(client.container().from("rustlang/rust:nightly"))
.with_sqlx()
#[tokio::test] .add_stage(RustServiceStage::BeforeBase(middleware(|c| async move { Ok(c) }.boxed())))
async fn test_fails_on_no_command() -> eyre::Result<()> { .build_release()
let res = CuddleCI::default().execute(["cuddle-ci"]).await; .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(()) Ok(())
} }