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