Skip to content

Commit

Permalink
feat(up): Spawn multiple trigger commands
Browse files Browse the repository at this point in the history
Signed-off-by: Carlo Kok <carlo@rb2.nl>
  • Loading branch information
carlokok committed Jan 8, 2024
1 parent 74cd215 commit 6af6bb0
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 35 deletions.
16 changes: 16 additions & 0 deletions crates/app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,22 @@ impl<'a, L> App<'a, L> {
.map(|locked| AppTrigger { app: self, locked })
}

/// Returns the trigger metadata for a specific trigger type.
pub fn get_trigger_metadata<'this, T: Deserialize<'this> + Default>(
&'this self,
trigger_type: &'a str,
) -> Result<T> {
Some(&self.locked.metadata["triggers"][trigger_type])
.map(T::deserialize)
.transpose()
.map(|t| t.unwrap_or_default())
.map_err(|err| {
Error::MetadataError(format!(
"invalid metadata value for {trigger_type:?}: {err:?}"
))
})
}

/// Returns an iterator of [`AppTrigger`]s defined for this app with
/// the given `trigger_type`.
pub fn triggers_with_type(
Expand Down
4 changes: 1 addition & 3 deletions crates/http/src/trigger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ use serde::{Deserialize, Serialize};
use spin_locked_app::MetadataKey;

/// Http trigger metadata key
pub const METADATA_KEY: MetadataKey<Metadata> = MetadataKey::new("trigger");
pub const METADATA_KEY: MetadataKey<Metadata> = MetadataKey::new("triggers.http");

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Metadata {
// The type of trigger which should always been "http" in this case
pub r#type: String,
// The based url
#[serde(default = "default_base")]
pub base: String,
Expand Down
7 changes: 2 additions & 5 deletions crates/redis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@ use anyhow::{anyhow, Context, Result};
use futures::{future::join_all, StreamExt};
use redis::{Client, ConnectionLike};
use serde::{de::IgnoredAny, Deserialize, Serialize};
use spin_app::MetadataKey;
use spin_core::async_trait;
use spin_trigger::{cli::NoArgs, TriggerAppEngine, TriggerExecutor};

use crate::spin::SpinRedisExecutor;

const TRIGGER_METADATA_KEY: MetadataKey<TriggerMetadata> = MetadataKey::new("trigger");

pub(crate) type RuntimeData = ();
pub(crate) type Store = spin_core::Store<RuntimeData>;

Expand Down Expand Up @@ -44,7 +41,6 @@ pub struct RedisTriggerConfig {
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
struct TriggerMetadata {
r#type: String,
address: String,
}

Expand All @@ -56,7 +52,8 @@ impl TriggerExecutor for RedisTrigger {
type RunConfig = NoArgs;

async fn new(engine: TriggerAppEngine<Self>) -> Result<Self> {
let address = engine.app().require_metadata(TRIGGER_METADATA_KEY)?.address;
let meta = engine.trigger_metadata::<TriggerMetadata>()?;
let address = meta.address;

let mut channel_components: HashMap<String, Vec<String>> = HashMap::new();

Expand Down
7 changes: 3 additions & 4 deletions crates/trigger-http/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,9 @@ impl TriggerExecutor for HttpTrigger {
type RunConfig = CliArgs;

async fn new(engine: TriggerAppEngine<Self>) -> Result<Self> {
let mut base = engine
.app()
.require_metadata(spin_http::trigger::METADATA_KEY)?
.base;
let meta = engine.trigger_metadata::<spin_http::trigger::Metadata>()?;

let mut base = meta.base;
if !base.starts_with('/') {
base = format!("/{base}");
}
Expand Down
4 changes: 4 additions & 0 deletions crates/trigger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,10 @@ impl<Executor: TriggerExecutor> TriggerAppEngine<Executor> {
self.app.borrowed()
}

pub fn trigger_metadata<T: DeserializeOwned + Default>(&self) -> spin_app::Result<T> {
self.app().get_trigger_metadata(Executor::TRIGGER_TYPE)
}

/// Returns AppTriggers and typed TriggerConfigs for this executor type.
pub fn trigger_configs(&self) -> impl Iterator<Item = (AppTrigger, &Executor::TriggerConfig)> {
self.app()
Expand Down
2 changes: 1 addition & 1 deletion examples/spin-timer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub struct TimerTriggerConfig {
interval_secs: u64,
}

const TRIGGER_METADATA_KEY: MetadataKey<TriggerMetadata> = MetadataKey::new("trigger");
const TRIGGER_METADATA_KEY: MetadataKey<TriggerMetadata> = MetadataKey::new("triggers");

#[async_trait]
impl TriggerExecutor for TimerTrigger {
Expand Down
57 changes: 38 additions & 19 deletions src/commands/up.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use spin_oci::OciLoader;
use spin_trigger::cli::{SPIN_LOCAL_APP_DIR, SPIN_LOCKED_URL, SPIN_WORKING_DIR};
use tempfile::TempDir;

use futures::future::join_all;

use crate::opts::*;

use self::app_source::{AppSource, ResolvedAppSource};
Expand Down Expand Up @@ -154,7 +156,14 @@ impl UpCommand {
.with_context(|| format!("Couldn't find trigger executor for {app_source}"))?;

if self.help {
return self.run_trigger(trigger_cmd, None).await;
return join_all(
trigger_cmd
.iter()
.map(|cmd| async { self.run_trigger(cmd.clone(), None).await }),
)
.await
.into_iter()
.collect();
}

let mut locked_app = self
Expand All @@ -165,13 +174,18 @@ impl UpCommand {

let local_app_dir = app_source.local_app_dir().map(Into::into);

let run_opts = RunTriggerOpts {
locked_app,
working_dir,
local_app_dir,
};

self.run_trigger(trigger_cmd, Some(run_opts)).await
join_all(trigger_cmd.iter().map(|cmd| {
let run_opts = RunTriggerOpts {
locked_app: locked_app.clone(),
working_dir: working_dir.clone(),
local_app_dir: local_app_dir.clone(),
};

self.run_trigger(cmd.clone(), Some(run_opts))
}))
.await
.into_iter()
.collect()
}

fn get_canonical_working_dir(&self) -> Result<WorkingDirectory, anyhow::Error> {
Expand All @@ -191,7 +205,7 @@ impl UpCommand {
}

async fn run_trigger(
self,
&self,
trigger_cmd: Vec<String>,
opts: Option<RunTriggerOpts>,
) -> Result<(), anyhow::Error> {
Expand Down Expand Up @@ -235,10 +249,11 @@ impl UpCommand {
})?;
}

let status = child.wait()?;
let status = tokio::task::spawn(async move { child.wait() }).await??;
if status.success() {
Ok(())
} else {
print!("fail {:?}\n", status);
Err(crate::subprocess::ExitStatusError::new(status).into())
}
}
Expand Down Expand Up @@ -424,16 +439,20 @@ fn trigger_command(trigger_type: &str) -> Vec<String> {
vec!["trigger".to_owned(), trigger_type.to_owned()]
}

fn trigger_command_for_resolved_app_source(resolved: &ResolvedAppSource) -> Result<Vec<String>> {
fn trigger_command_for_resolved_app_source(
resolved: &ResolvedAppSource,
) -> Result<Vec<Vec<String>>> {
let trigger_type = resolved.trigger_type()?;

match trigger_type {
"http" | "redis" => Ok(trigger_command(trigger_type)),
_ => {
let cmd = resolve_trigger_plugin(trigger_type)?;
Ok(vec![cmd])
}
}
trigger_type
.iter()
.map(|&t| match t {
"http" | "redis" => Ok(trigger_command(t)),
_ => {
let cmd = resolve_trigger_plugin(t)?;
Ok(vec![cmd])
}
})
.collect()
}

#[cfg(test)]
Expand Down
5 changes: 2 additions & 3 deletions src/commands/up/app_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub enum ResolvedAppSource {
}

impl ResolvedAppSource {
pub fn trigger_type(&self) -> anyhow::Result<&str> {
pub fn trigger_type(&self) -> anyhow::Result<Vec<&str>> {
let types = match self {
ResolvedAppSource::File { manifest, .. } => {
manifest.triggers.keys().collect::<HashSet<_>>()
Expand All @@ -96,7 +96,6 @@ impl ResolvedAppSource {
};

ensure!(!types.is_empty(), "no triggers in app");
ensure!(types.len() == 1, "multiple trigger types not yet supported");
Ok(types.into_iter().next().unwrap())
Ok(types.into_iter().map(|t| t.as_str()).collect())
}
}

0 comments on commit 6af6bb0

Please sign in to comment.