spin_runtime_factors/
lib.rs

1mod build;
2
3pub use build::FactorsBuilder;
4
5use std::path::PathBuf;
6
7use anyhow::Context as _;
8use spin_common::arg_parser::parse_kv;
9use spin_factor_key_value::KeyValueFactor;
10use spin_factor_llm::LlmFactor;
11use spin_factor_outbound_http::OutboundHttpFactor;
12use spin_factor_outbound_mqtt::{NetworkedMqttClient, OutboundMqttFactor};
13use spin_factor_outbound_mysql::OutboundMysqlFactor;
14use spin_factor_outbound_networking::OutboundNetworkingFactor;
15use spin_factor_outbound_pg::OutboundPgFactor;
16use spin_factor_outbound_redis::OutboundRedisFactor;
17use spin_factor_sqlite::SqliteFactor;
18use spin_factor_variables::VariablesFactor;
19use spin_factor_wasi::{spin::SpinFilesMounter, WasiFactor};
20use spin_factors::RuntimeFactors;
21use spin_runtime_config::{ResolvedRuntimeConfig, TomlRuntimeConfigSource};
22
23#[derive(RuntimeFactors)]
24pub struct TriggerFactors {
25    pub wasi: WasiFactor,
26    pub variables: VariablesFactor,
27    pub key_value: KeyValueFactor,
28    pub outbound_networking: OutboundNetworkingFactor,
29    pub outbound_http: OutboundHttpFactor,
30    pub sqlite: SqliteFactor,
31    pub redis: OutboundRedisFactor,
32    pub mqtt: OutboundMqttFactor,
33    pub pg: OutboundPgFactor,
34    pub mysql: OutboundMysqlFactor,
35    pub llm: LlmFactor,
36}
37
38impl TriggerFactors {
39    pub fn new(
40        state_dir: Option<PathBuf>,
41        working_dir: impl Into<PathBuf>,
42        allow_transient_writes: bool,
43    ) -> anyhow::Result<Self> {
44        Ok(Self {
45            wasi: wasi_factor(working_dir, allow_transient_writes),
46            variables: VariablesFactor::default(),
47            key_value: KeyValueFactor::new(),
48            outbound_networking: outbound_networking_factor(),
49            outbound_http: OutboundHttpFactor::default(),
50            sqlite: SqliteFactor::new(),
51            redis: OutboundRedisFactor::new(),
52            mqtt: OutboundMqttFactor::new(NetworkedMqttClient::creator()),
53            pg: OutboundPgFactor::new(),
54            mysql: OutboundMysqlFactor::new(),
55            llm: LlmFactor::new(
56                spin_factor_llm::spin::default_engine_creator(state_dir)
57                    .context("failed to configure LLM factor")?,
58            ),
59        })
60    }
61}
62
63fn wasi_factor(working_dir: impl Into<PathBuf>, allow_transient_writes: bool) -> WasiFactor {
64    WasiFactor::new(SpinFilesMounter::new(working_dir, allow_transient_writes))
65}
66
67fn outbound_networking_factor() -> OutboundNetworkingFactor {
68    fn disallowed_host_handler(scheme: &str, authority: &str) {
69        let host_pattern = format!("{scheme}://{authority}");
70        tracing::error!("Outbound network destination not allowed: {host_pattern}");
71        if scheme.starts_with("http") && authority == "self" {
72            terminal::warn!("A component tried to make an HTTP request to its own app but it does not have permission.");
73        } else {
74            terminal::warn!(
75                "A component tried to make an outbound network connection to disallowed destination '{host_pattern}'."
76            );
77        };
78        eprintln!("To allow this request, add 'allowed_outbound_hosts = [\"{host_pattern}\"]' to the manifest component section.");
79    }
80
81    let mut factor = OutboundNetworkingFactor::new();
82    factor.set_disallowed_host_handler(disallowed_host_handler);
83    factor
84}
85
86/// Options for building a [`TriggerFactors`].
87#[derive(Default, clap::Args)]
88pub struct TriggerAppArgs {
89    /// Set the static assets of the components in the temporary directory as writable.
90    #[clap(long = "allow-transient-write")]
91    pub allow_transient_write: bool,
92
93    /// Set a key/value pair (key=value) in the application's
94    /// default store. Any existing value will be overwritten.
95    /// Can be used multiple times.
96    #[clap(long = "key-value", parse(try_from_str = parse_kv))]
97    pub key_values: Vec<(String, String)>,
98
99    /// Run a SQLite statement such as a migration against the default database.
100    /// To run from a file, prefix the filename with @ e.g. spin up --sqlite @migration.sql
101    #[clap(long = "sqlite")]
102    pub sqlite_statements: Vec<String>,
103
104    /// Sets the maxmimum memory allocation limit for an instance in bytes.
105    #[clap(long, env = "SPIN_MAX_INSTANCE_MEMORY")]
106    pub max_instance_memory: Option<usize>,
107}
108
109impl From<ResolvedRuntimeConfig<TriggerFactorsRuntimeConfig>> for TriggerFactorsRuntimeConfig {
110    fn from(value: ResolvedRuntimeConfig<TriggerFactorsRuntimeConfig>) -> Self {
111        value.runtime_config
112    }
113}
114
115impl TryFrom<TomlRuntimeConfigSource<'_, '_>> for TriggerFactorsRuntimeConfig {
116    type Error = anyhow::Error;
117
118    fn try_from(value: TomlRuntimeConfigSource<'_, '_>) -> Result<Self, Self::Error> {
119        Self::from_source(value)
120    }
121}