Skip to content

Commit 2c83cc5

Browse files
authored
Merge pull request #2266 from itowlson/trigger-metadata-protocol
Multi-trigger: ask triggers which flags they can accept
2 parents 84511c0 + febbb99 commit 2c83cc5

File tree

4 files changed

+251
-70
lines changed

4 files changed

+251
-70
lines changed

crates/plugins/src/manifest.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::io::IsTerminal;
2+
13
use anyhow::{anyhow, Context, Result};
24
use semver::{Version, VersionReq};
35
use serde::{Deserialize, Serialize};
@@ -181,7 +183,9 @@ fn inner_warn_unsupported_version(
181183
if !is_version_fully_compatible(supported_on, spin_version)? {
182184
let version = Version::parse(spin_version)?;
183185
if !version.pre.is_empty() {
184-
terminal::warn!("You're using a pre-release version of Spin ({spin_version}). This plugin might not be compatible (supported: {supported_on}). Continuing anyway.");
186+
if std::io::stderr().is_terminal() {
187+
terminal::warn!("You're using a pre-release version of Spin ({spin_version}). This plugin might not be compatible (supported: {supported_on}). Continuing anyway.");
188+
}
185189
} else if override_compatibility_check {
186190
terminal::warn!("Plugin is not compatible with this version of Spin (supported: {supported_on}, actual: {spin_version}). Check overridden ... continuing to install or execute plugin.");
187191
} else {

crates/trigger/src/cli.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ use crate::{
1717
};
1818
use crate::{TriggerExecutor, TriggerExecutorBuilder};
1919

20+
mod launch_metadata;
21+
pub use launch_metadata::LaunchMetadata;
22+
2023
pub const APP_LOG_DIR: &str = "APP_LOG_DIR";
2124
pub const DISABLE_WASMTIME_CACHE: &str = "DISABLE_WASMTIME_CACHE";
2225
pub const FOLLOW_LOG_OPT: &str = "FOLLOW_ID";
@@ -125,6 +128,9 @@ where
125128

126129
#[clap(long = "help-args-only", hide = true)]
127130
pub help_args_only: bool,
131+
132+
#[clap(long = "launch-metadata-only", hide = true)]
133+
pub launch_metadata_only: bool,
128134
}
129135

130136
/// An empty implementation of clap::Args to be used as TriggerExecutor::RunConfig
@@ -147,6 +153,13 @@ where
147153
return Ok(());
148154
}
149155

156+
if self.launch_metadata_only {
157+
let lm = LaunchMetadata::infer::<Executor>();
158+
let json = serde_json::to_string_pretty(&lm)?;
159+
eprintln!("{json}");
160+
return Ok(());
161+
}
162+
150163
// Required env vars
151164
let working_dir = std::env::var(SPIN_WORKING_DIR).context(SPIN_WORKING_DIR)?;
152165
let locked_url = std::env::var(SPIN_LOCKED_URL).context(SPIN_LOCKED_URL)?;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use clap::{Args, CommandFactory};
2+
use serde::{Deserialize, Serialize};
3+
use std::ffi::OsString;
4+
5+
use crate::{cli::TriggerExecutorCommand, TriggerExecutor};
6+
7+
/// Contains information about the trigger flags (and potentially
8+
/// in future configuration) that a consumer (such as `spin up`)
9+
/// can query using `--launch-metadata-only`.
10+
#[derive(Clone, Debug, Deserialize, Serialize)]
11+
pub struct LaunchMetadata {
12+
all_flags: Vec<LaunchFlag>,
13+
}
14+
15+
// This assumes no triggers that want to participate in multi-trigger
16+
// use positional arguments. This is a restriction we'll have to make
17+
// anyway: suppose triggers A and B both take one positional arg, and
18+
// the user writes `spin up 123 456` - which value would go to which trigger?
19+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
20+
struct LaunchFlag {
21+
#[serde(skip_serializing_if = "Option::is_none")]
22+
#[serde(default)]
23+
short: Option<String>,
24+
#[serde(skip_serializing_if = "Option::is_none")]
25+
#[serde(default)]
26+
long: Option<String>,
27+
}
28+
29+
impl LaunchMetadata {
30+
pub fn infer<Executor: TriggerExecutor>() -> Self
31+
where
32+
Executor::RunConfig: Args,
33+
{
34+
let all_flags: Vec<_> = TriggerExecutorCommand::<Executor>::command()
35+
.get_arguments()
36+
.map(LaunchFlag::infer)
37+
.collect();
38+
39+
LaunchMetadata { all_flags }
40+
}
41+
42+
pub fn matches<'a>(&self, groups: &[Vec<&'a OsString>]) -> Vec<&'a OsString> {
43+
let mut matches = vec![];
44+
45+
for group in groups {
46+
if group.is_empty() {
47+
continue;
48+
}
49+
if self.is_match(group[0]) {
50+
matches.extend(group);
51+
}
52+
}
53+
54+
matches
55+
}
56+
57+
fn is_match(&self, arg: &OsString) -> bool {
58+
self.all_flags.iter().any(|f| f.is_match(arg))
59+
}
60+
61+
pub fn is_group_match(&self, group: &[&OsString]) -> bool {
62+
if group.is_empty() {
63+
false
64+
} else {
65+
self.all_flags.iter().any(|f| f.is_match(group[0]))
66+
}
67+
}
68+
}
69+
70+
impl LaunchFlag {
71+
fn infer(arg: &clap::Arg) -> Self {
72+
Self {
73+
long: arg.get_long().map(|s| format!("--{s}")),
74+
short: arg.get_short().map(|ch| format!("-{ch}")),
75+
}
76+
}
77+
78+
fn is_match(&self, candidate: &OsString) -> bool {
79+
let Some(s) = candidate.to_str() else {
80+
return false;
81+
};
82+
let candidate = Some(s.to_owned());
83+
84+
candidate == self.long || candidate == self.short
85+
}
86+
}

0 commit comments

Comments
 (0)