feat: enable actual actions

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2024-10-26 12:55:37 +02:00
parent 2d7a053ab0
commit 7804eaa667
Signed by: kjuulh
GPG Key ID: D85D7535F18F35FA
7 changed files with 160 additions and 27 deletions

8
Cargo.lock generated
View File

@ -7,7 +7,7 @@ name = "action"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cuddle-actions 0.1.0", "cuddle-actions 0.2.0 (git+ssh://git@git.front.kjuulh.io/kjuulh/cuddle-v2)",
"tokio", "tokio",
] ]
@ -268,11 +268,11 @@ dependencies = [
[[package]] [[package]]
name = "cuddle-actions" name = "cuddle-actions"
version = "0.1.0" version = "0.2.0"
source = "git+ssh://git@git.front.kjuulh.io/kjuulh/cuddle-v2#fb2e4b3234a249d17aaf9bdff18825a36a132bbd"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"pretty_assertions",
"serde", "serde",
"serde_json", "serde_json",
] ]
@ -280,10 +280,10 @@ dependencies = [
[[package]] [[package]]
name = "cuddle-actions" name = "cuddle-actions"
version = "0.2.0" version = "0.2.0"
source = "git+ssh://git@git.front.kjuulh.io/kjuulh/cuddle-v2#71cc6a0a000dbdb1b62b518cd2d955e9121627f1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"pretty_assertions",
"serde", "serde",
"serde_json", "serde_json",
] ]

View File

@ -12,7 +12,7 @@ anyhow = { version = "1" }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tracing = { version = "0.1", features = ["log"] } tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3.18" } tracing-subscriber = { version = "0.3.18" }
clap = { version = "4", features = ["derive", "env"] } clap = { version = "4", features = ["derive", "env", "string"] }
dotenv = { version = "0.15" } dotenv = { version = "0.15" }
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.127" serde_json = "1.0.127"

View File

@ -3,7 +3,14 @@ use cuddle_actions::AddActionOptions;
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
cuddle_actions::CuddleActions::default() cuddle_actions::CuddleActions::default()
.add_action("something", || Ok(()), &AddActionOptions::default()) .add_action(
"something",
|| {
println!("did something");
Ok(())
},
&AddActionOptions::default(),
)
.execute()?; .execute()?;
Ok(()) Ok(())

View File

@ -1,6 +1,10 @@
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
future::{self, Future},
ops::Deref,
path::{Path, PathBuf}, path::{Path, PathBuf},
pin::Pin,
sync::Arc,
}; };
use rust_builder::RustActionsBuilder; use rust_builder::RustActionsBuilder;
@ -16,6 +20,10 @@ pub mod rust_builder {
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use serde::Deserialize;
use crate::actions::CuddleActionsSchema;
use super::ExecutableActions; use super::ExecutableActions;
pub struct RustActionsBuilder { pub struct RustActionsBuilder {
@ -138,7 +146,7 @@ pub mod rust_builder {
.output() .output()
.await?; .await?;
let actions: ExecutableActions = match serde_json::from_slice(&output.stdout) { let actions: CuddleActionsSchema = match serde_json::from_slice(&output.stdout) {
Ok(output) => output, Ok(output) => output,
Err(e) => { Err(e) => {
let schema_output = std::str::from_utf8(&output.stdout)?; 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 })) Ok(Some(Self { variants }))
} }
pub async fn build(&mut self) -> anyhow::Result<Vec<ExecutableActions>> { pub async fn build(&mut self) -> anyhow::Result<ExecutableActions> {
let mut executable_actions = Vec::default(); let mut executable_actions = Vec::default();
for variant in &mut self.variants { 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<CuddleActionSchema>,
}
#[derive(Debug, Deserialize)]
struct CuddleActionSchema {
name: String,
}
pub enum ActionVariant { pub enum ActionVariant {
Rust { root_path: PathBuf }, Rust { root_path: PathBuf },
Docker, Docker,
} }
#[derive(Default, Serialize, Deserialize)] #[derive(Default)]
pub struct ExecutableActions { pub struct ExecutableActions {
actions: Vec<ExecutableAction>, pub actions: Vec<ExecutableAction>,
} }
#[derive(Debug, Serialize, Deserialize)]
pub struct ExecutableAction { pub struct ExecutableAction {
name: String, pub name: String,
description: String, pub description: String,
flags: BTreeMap<String, ExecutableActionFlag>, pub flags: BTreeMap<String, ExecutableActionFlag>,
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)] #[derive(Debug, Serialize, Deserialize)]
@ -220,3 +252,67 @@ pub enum ExecutableActionFlag {
String, String,
Bool, Bool,
} }
impl CuddleActionsSchema {
fn to_executable(self, action_path: &Path) -> anyhow::Result<ExecutableActions> {
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<dyn Fn() -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>> + Send + Sync>,
);
impl LazyResolve {
pub fn new(
func: Box<
dyn Fn() -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>> + Send + Sync,
>,
) -> Self {
Self(Arc::new(func))
}
}
impl Deref for LazyResolve {
type Target =
Arc<dyn Fn() -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>> + Send + Sync>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View File

@ -40,8 +40,19 @@ impl Cli {
} }
async fn get_commands(&self) -> anyhow::Result<Vec<clap::Command>> { async fn get_commands(&self) -> anyhow::Result<Vec<clap::Command>> {
let actions = self
.cuddle
.state
.actions
.actions
.iter()
.map(|a| clap::Command::new(&a.name))
.collect::<Vec<_>>();
Ok(vec![ Ok(vec![
clap::Command::new("do").subcommand_required(true), clap::Command::new("do")
.subcommand_required(true)
.subcommands(actions.as_slice()),
clap::Command::new("get") clap::Command::new("get")
.about(GetCommand::description()) .about(GetCommand::description())
.arg( .arg(
@ -67,8 +78,23 @@ impl Cli {
.subcommand() .subcommand()
.ok_or(anyhow::anyhow!("failed to find subcommand"))? .ok_or(anyhow::anyhow!("failed to find subcommand"))?
{ {
("do", _args) => { ("do", args) => {
tracing::debug!("executing do"); 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) => { ("get", args) => {
if !self.cuddle.has_project() { if !self.cuddle.has_project() {

View File

@ -54,7 +54,7 @@ impl Cuddle<PrepareProject> {
} }
impl Cuddle<PreparePlan> { impl Cuddle<PreparePlan> {
pub async fn build_state(&self) -> anyhow::Result<Cuddle<ValidatedState>> { pub async fn build_state(&mut self) -> anyhow::Result<Cuddle<ValidatedState>> {
let mut state = if let Some(project) = &self.state.project { let mut state = if let Some(project) = &self.state.project {
let state = state::State::new(); let state = state::State::new();
let raw_state = state.build_state(project, &self.state.plan).await?; let raw_state = state.build_state(project, &self.state.plan).await?;

View File

@ -1,7 +1,7 @@
use validated_project::Project; use validated_project::Project;
use crate::{ use crate::{
actions::Actions, actions::{Actions, ExecutableActions},
plan::{self, ClonedPlan, PlanPathExt}, plan::{self, ClonedPlan, PlanPathExt},
project::{self, ProjectPlan}, project::{self, ProjectPlan},
schema_validator::SchemaValidator, schema_validator::SchemaValidator,
@ -43,7 +43,7 @@ impl State {
Ok(ValidatedState { Ok(ValidatedState {
project: Some(project), project: Some(project),
plan: None, plan: None,
actions: LocalActions::default(), actions: ExecutableActions::default(),
}) })
} }
} }
@ -58,20 +58,21 @@ pub struct ValidatedState {
pub project: Option<Project>, pub project: Option<Project>,
pub plan: Option<Plan>, pub plan: Option<Plan>,
pub actions: LocalActions, pub actions: ExecutableActions,
} }
impl ValidatedState { impl ValidatedState {
pub(crate) async fn build_actions(&mut self) -> anyhow::Result<&mut Self> { pub(crate) async fn build_actions(&mut self) -> anyhow::Result<&mut Self> {
tracing::debug!("building actions"); tracing::debug!("building actions");
let mut local_actions = LocalActions::default();
if let Some(project) = &self.project { if let Some(project) = &self.project {
if let Some(actions) = Actions::new(&project.root, &project.value).await? { 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) Ok(self)
} }
@ -89,12 +90,15 @@ impl LocalActions {
self self
} }
pub async fn build(&mut self) -> anyhow::Result<&mut Self> { pub async fn build(&mut self) -> anyhow::Result<ExecutableActions> {
let mut executable_actions = ExecutableActions::default();
for actions in &mut self.0 { 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)
} }
} }