spin_runtime_factors/
lib.rs1mod build;
2
3pub use build::FactorsBuilder;
4
5use std::cell::OnceCell;
6use std::collections::HashMap;
7use std::path::PathBuf;
8
9use anyhow::Context as _;
10use spin_common::arg_parser::parse_kv;
11use spin_factor_key_value::KeyValueFactor;
12use spin_factor_llm::LlmFactor;
13use spin_factor_otel::OtelFactor;
14use spin_factor_outbound_http::OutboundHttpFactor;
15use spin_factor_outbound_mqtt::{NetworkedMqttClient, OutboundMqttFactor};
16use spin_factor_outbound_mysql::OutboundMysqlFactor;
17use spin_factor_outbound_networking::OutboundNetworkingFactor;
18use spin_factor_outbound_pg::OutboundPgFactor;
19use spin_factor_outbound_redis::OutboundRedisFactor;
20use spin_factor_sqlite::SqliteFactor;
21use spin_factor_variables::VariablesFactor;
22use spin_factor_wasi::{spin::SpinFilesMounter, WasiFactor};
23use spin_factors::RuntimeFactors;
24use spin_runtime_config::{ResolvedRuntimeConfig, TomlRuntimeConfigSource};
25use spin_variables_static::VariableSource;
26
27#[derive(RuntimeFactors)]
28pub struct TriggerFactors {
29 pub otel: OtelFactor,
30 pub wasi: WasiFactor,
31 pub variables: VariablesFactor,
32 pub key_value: KeyValueFactor,
33 pub outbound_networking: OutboundNetworkingFactor,
34 pub outbound_http: OutboundHttpFactor,
35 pub sqlite: SqliteFactor,
36 pub redis: OutboundRedisFactor,
37 pub mqtt: OutboundMqttFactor,
38 pub pg: OutboundPgFactor,
39 pub mysql: OutboundMysqlFactor,
40 pub llm: LlmFactor,
41}
42
43impl TriggerFactors {
44 pub fn new(
45 state_dir: Option<PathBuf>,
46 working_dir: impl Into<PathBuf>,
47 allow_transient_writes: bool,
48 experimental_wasi_otel: bool,
49 spin_version: &str,
50 ) -> anyhow::Result<Self> {
51 Ok(Self {
52 otel: OtelFactor::new(spin_version, experimental_wasi_otel)?,
53 wasi: wasi_factor(working_dir, allow_transient_writes),
54 variables: VariablesFactor::default(),
55 key_value: KeyValueFactor::new(),
56 outbound_networking: outbound_networking_factor(),
57 outbound_http: OutboundHttpFactor::default(),
58 sqlite: SqliteFactor::new(),
59 redis: OutboundRedisFactor::new(),
60 mqtt: OutboundMqttFactor::new(NetworkedMqttClient::creator()),
61 pg: OutboundPgFactor::new(),
62 mysql: OutboundMysqlFactor::new(),
63 llm: LlmFactor::new(
64 spin_factor_llm::spin::default_engine_creator(state_dir)
65 .context("failed to configure LLM factor")?,
66 ),
67 })
68 }
69}
70
71fn wasi_factor(working_dir: impl Into<PathBuf>, allow_transient_writes: bool) -> WasiFactor {
72 WasiFactor::new(SpinFilesMounter::new(working_dir, allow_transient_writes))
73}
74
75fn outbound_networking_factor() -> OutboundNetworkingFactor {
76 fn disallowed_host_handler(scheme: &str, authority: &str) {
77 let host_pattern = format!("{scheme}://{authority}");
78 tracing::error!("Outbound network destination not allowed: {host_pattern}");
79 if scheme.starts_with("http") && authority == "self" {
80 terminal::warn!("A component tried to make an HTTP request to its own app but it does not have permission.");
81 } else {
82 terminal::warn!(
83 "A component tried to make an outbound network connection to disallowed destination '{host_pattern}'."
84 );
85 };
86 eprintln!("To allow this request, add 'allowed_outbound_hosts = [\"{host_pattern}\"]' to the manifest component section.");
87 }
88
89 let mut factor = OutboundNetworkingFactor::new();
90 factor.set_disallowed_host_handler(disallowed_host_handler);
91 factor
92}
93
94#[derive(Default, clap::Args)]
96pub struct TriggerAppArgs {
97 #[clap(long = "allow-transient-write")]
99 pub allow_transient_write: bool,
100
101 #[clap(long = "experimental-wasi-otel")]
103 pub experimental_wasi_otel: bool,
104
105 #[clap(long = "key-value", value_parser = parse_kv)]
109 pub key_values: Vec<(String, String)>,
110
111 #[clap(long = "sqlite")]
114 pub sqlite_statements: Vec<String>,
115
116 #[clap(long, env = "SPIN_MAX_INSTANCE_MEMORY")]
118 pub max_instance_memory: Option<usize>,
119
120 #[clap(long, value_parser = clap::value_parser!(VariableSource),
130 value_name = "KEY=VALUE | KEY=@FILE | @FILE.json | @FILE.toml")]
131 pub variable: Vec<VariableSource>,
132
133 #[clap(skip)]
135 variables_cache: OnceCell<HashMap<String, String>>,
136}
137
138impl From<ResolvedRuntimeConfig<TriggerFactorsRuntimeConfig>> for TriggerFactorsRuntimeConfig {
139 fn from(value: ResolvedRuntimeConfig<TriggerFactorsRuntimeConfig>) -> Self {
140 value.runtime_config
141 }
142}
143
144impl TryFrom<TomlRuntimeConfigSource<'_, '_>> for TriggerFactorsRuntimeConfig {
145 type Error = anyhow::Error;
146
147 fn try_from(value: TomlRuntimeConfigSource<'_, '_>) -> Result<Self, Self::Error> {
148 Self::from_source(value)
149 }
150}
151
152impl TriggerAppArgs {
153 pub fn get_variables(&self) -> anyhow::Result<&HashMap<String, String>> {
155 if self.variables_cache.get().is_none() {
156 let mut variables = HashMap::new();
157 for source in &self.variable {
158 variables.extend(source.get_variables()?);
159 }
160 self.variables_cache.set(variables).unwrap();
161 }
162 Ok(self.variables_cache.get().unwrap())
163 }
164}