From 7804eaa6678b8017c1fd259d181627edcd9e1d9f Mon Sep 17 00:00:00 2001 From: kjuulh Date: Sat, 26 Oct 2024 12:55:37 +0200 Subject: [PATCH] feat: enable actual actions Signed-off-by: kjuulh --- Cargo.lock | 8 +- Cargo.toml | 2 +- .../actions/project/actions/rust/src/main.rs | 9 +- crates/cuddle/src/actions.rs | 116 ++++++++++++++++-- crates/cuddle/src/cli.rs | 30 ++++- crates/cuddle/src/cuddle_state.rs | 2 +- crates/cuddle/src/state.rs | 20 +-- 7 files changed, 160 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18928ba..90360ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,7 +7,7 @@ name = "action" version = "0.1.0" dependencies = [ "anyhow", - "cuddle-actions 0.1.0", + "cuddle-actions 0.2.0 (git+ssh://git@git.front.kjuulh.io/kjuulh/cuddle-v2)", "tokio", ] @@ -268,11 +268,11 @@ dependencies = [ [[package]] name = "cuddle-actions" -version = "0.1.0" -source = "git+ssh://git@git.front.kjuulh.io/kjuulh/cuddle-v2#fb2e4b3234a249d17aaf9bdff18825a36a132bbd" +version = "0.2.0" dependencies = [ "anyhow", "clap", + "pretty_assertions", "serde", "serde_json", ] @@ -280,10 +280,10 @@ dependencies = [ [[package]] name = "cuddle-actions" version = "0.2.0" +source = "git+ssh://git@git.front.kjuulh.io/kjuulh/cuddle-v2#71cc6a0a000dbdb1b62b518cd2d955e9121627f1" dependencies = [ "anyhow", "clap", - "pretty_assertions", "serde", "serde_json", ] diff --git a/Cargo.toml b/Cargo.toml index 0ea3ae9..2ff1c23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ anyhow = { version = "1" } tokio = { version = "1", features = ["full"] } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3.18" } -clap = { version = "4", features = ["derive", "env"] } +clap = { version = "4", features = ["derive", "env", "string"] } dotenv = { version = "0.15" } serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.127" diff --git a/crates/cuddle/examples/actions/project/actions/rust/src/main.rs b/crates/cuddle/examples/actions/project/actions/rust/src/main.rs index 76acd8e..cf4e9e7 100644 --- a/crates/cuddle/examples/actions/project/actions/rust/src/main.rs +++ b/crates/cuddle/examples/actions/project/actions/rust/src/main.rs @@ -3,7 +3,14 @@ use cuddle_actions::AddActionOptions; #[tokio::main] async fn main() -> anyhow::Result<()> { cuddle_actions::CuddleActions::default() - .add_action("something", || Ok(()), &AddActionOptions::default()) + .add_action( + "something", + || { + println!("did something"); + Ok(()) + }, + &AddActionOptions::default(), + ) .execute()?; Ok(()) diff --git a/crates/cuddle/src/actions.rs b/crates/cuddle/src/actions.rs index befe656..c12e1d8 100644 --- a/crates/cuddle/src/actions.rs +++ b/crates/cuddle/src/actions.rs @@ -1,6 +1,10 @@ use std::{ collections::BTreeMap, + future::{self, Future}, + ops::Deref, path::{Path, PathBuf}, + pin::Pin, + sync::Arc, }; use rust_builder::RustActionsBuilder; @@ -16,6 +20,10 @@ pub mod rust_builder { path::{Path, PathBuf}, }; + use serde::Deserialize; + + use crate::actions::CuddleActionsSchema; + use super::ExecutableActions; pub struct RustActionsBuilder { @@ -138,7 +146,7 @@ pub mod rust_builder { .output() .await?; - let actions: ExecutableActions = match serde_json::from_slice(&output.stdout) { + let actions: CuddleActionsSchema = match serde_json::from_slice(&output.stdout) { Ok(output) => output, Err(e) => { let schema_output = std::str::from_utf8(&output.stdout)?; @@ -151,7 +159,7 @@ pub mod rust_builder { } }; - Ok(actions) + Ok(actions.to_executable(action_path)?) } } } @@ -180,7 +188,7 @@ impl Actions { Ok(Some(Self { variants })) } - pub async fn build(&mut self) -> anyhow::Result> { + pub async fn build(&mut self) -> anyhow::Result { let mut executable_actions = Vec::default(); for variant in &mut self.variants { @@ -194,25 +202,49 @@ impl Actions { } } - Ok(executable_actions) + let mut exec_actions = ExecutableActions::default(); + for mut actions in executable_actions { + exec_actions.actions.append(&mut actions.actions); + } + + Ok(exec_actions) } } +#[derive(Debug, Deserialize)] +struct CuddleActionsSchema { + actions: Vec, +} + +#[derive(Debug, Deserialize)] +struct CuddleActionSchema { + name: String, +} + pub enum ActionVariant { Rust { root_path: PathBuf }, Docker, } -#[derive(Default, Serialize, Deserialize)] +#[derive(Default)] pub struct ExecutableActions { - actions: Vec, + pub actions: Vec, } -#[derive(Debug, Serialize, Deserialize)] pub struct ExecutableAction { - name: String, - description: String, - flags: BTreeMap, + pub name: String, + pub description: String, + pub flags: BTreeMap, + call_fn: LazyResolve, +} + +impl ExecutableAction { + pub async fn call(&self) -> anyhow::Result<()> { + // Bad hack until .call becomes stable + (self.call_fn.0)().await?; + + Ok(()) + } } #[derive(Debug, Serialize, Deserialize)] @@ -220,3 +252,67 @@ pub enum ExecutableActionFlag { String, Bool, } + +impl CuddleActionsSchema { + fn to_executable(self, action_path: &Path) -> anyhow::Result { + Ok(ExecutableActions { + actions: self + .actions + .into_iter() + .map(|a| { + let name = a.name.clone(); + let action_path = action_path.to_string_lossy().to_string(); + + ExecutableAction { + name: a.name, + description: String::new(), + flags: BTreeMap::default(), + call_fn: LazyResolve::new(Box::new(move || { + let name = name.clone(); + let action_path = action_path.clone(); + + Box::pin(async move { + tracing::debug!("calling: {}", name); + let mut cmd = tokio::process::Command::new(action_path); + cmd.args(["do", &name]); + + let output = cmd.output().await?; + let stdout = std::str::from_utf8(&output.stdout)?; + for line in stdout.lines() { + println!("{}: {}", &name, line); + } + + tracing::debug!("finished call for output: {}", &name); + + Ok(()) + }) + })), + } + }) + .collect(), + }) + } +} + +struct LazyResolve( + Arc Pin> + Send>> + Send + Sync>, +); + +impl LazyResolve { + pub fn new( + func: Box< + dyn Fn() -> Pin> + Send>> + Send + Sync, + >, + ) -> Self { + Self(Arc::new(func)) + } +} + +impl Deref for LazyResolve { + type Target = + Arc Pin> + Send>> + Send + Sync>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/crates/cuddle/src/cli.rs b/crates/cuddle/src/cli.rs index a26b924..902f507 100644 --- a/crates/cuddle/src/cli.rs +++ b/crates/cuddle/src/cli.rs @@ -40,8 +40,19 @@ impl Cli { } async fn get_commands(&self) -> anyhow::Result> { + let actions = self + .cuddle + .state + .actions + .actions + .iter() + .map(|a| clap::Command::new(&a.name)) + .collect::>(); + Ok(vec![ - clap::Command::new("do").subcommand_required(true), + clap::Command::new("do") + .subcommand_required(true) + .subcommands(actions.as_slice()), clap::Command::new("get") .about(GetCommand::description()) .arg( @@ -67,8 +78,23 @@ impl Cli { .subcommand() .ok_or(anyhow::anyhow!("failed to find subcommand"))? { - ("do", _args) => { + ("do", args) => { tracing::debug!("executing do"); + + let (action_name, args) = args + .subcommand() + .ok_or(anyhow::anyhow!("failed to find do subcommand"))?; + + let action = self + .cuddle + .state + .actions + .actions + .iter() + .find(|a| a.name == action_name) + .ok_or(anyhow::anyhow!("failed to find {}", action_name))?; + + action.call().await?; } ("get", args) => { if !self.cuddle.has_project() { diff --git a/crates/cuddle/src/cuddle_state.rs b/crates/cuddle/src/cuddle_state.rs index a33031c..b187d90 100644 --- a/crates/cuddle/src/cuddle_state.rs +++ b/crates/cuddle/src/cuddle_state.rs @@ -54,7 +54,7 @@ impl Cuddle { } impl Cuddle { - pub async fn build_state(&self) -> anyhow::Result> { + pub async fn build_state(&mut self) -> anyhow::Result> { let mut state = if let Some(project) = &self.state.project { let state = state::State::new(); let raw_state = state.build_state(project, &self.state.plan).await?; diff --git a/crates/cuddle/src/state.rs b/crates/cuddle/src/state.rs index 4eb7594..54ed44d 100644 --- a/crates/cuddle/src/state.rs +++ b/crates/cuddle/src/state.rs @@ -1,7 +1,7 @@ use validated_project::Project; use crate::{ - actions::Actions, + actions::{Actions, ExecutableActions}, plan::{self, ClonedPlan, PlanPathExt}, project::{self, ProjectPlan}, schema_validator::SchemaValidator, @@ -43,7 +43,7 @@ impl State { Ok(ValidatedState { project: Some(project), plan: None, - actions: LocalActions::default(), + actions: ExecutableActions::default(), }) } } @@ -58,20 +58,21 @@ pub struct ValidatedState { pub project: Option, pub plan: Option, - pub actions: LocalActions, + pub actions: ExecutableActions, } impl ValidatedState { pub(crate) async fn build_actions(&mut self) -> anyhow::Result<&mut Self> { tracing::debug!("building actions"); + let mut local_actions = LocalActions::default(); if let Some(project) = &self.project { if let Some(actions) = Actions::new(&project.root, &project.value).await? { - self.actions.add(actions); + local_actions.add(actions); } } - self.actions.build().await?; + self.actions = local_actions.build().await?; Ok(self) } @@ -89,12 +90,15 @@ impl LocalActions { self } - pub async fn build(&mut self) -> anyhow::Result<&mut Self> { + pub async fn build(&mut self) -> anyhow::Result { + let mut executable_actions = ExecutableActions::default(); + for actions in &mut self.0 { - actions.build().await?; + let mut exec_actions = actions.build().await?; + executable_actions.actions.append(&mut exec_actions.actions) } - Ok(self) + Ok(executable_actions) } }