spin_trigger/
loader.rs

1use anyhow::Context as _;
2use spin_common::{ui::quoted_path, url::parse_file_url};
3use spin_compose::ComponentSourceLoaderFs;
4use spin_core::{async_trait, wasmtime, Component};
5use spin_factors::{AppComponent, RuntimeFactors};
6
7#[derive(Default)]
8pub struct ComponentLoader {
9    _private: (),
10    #[cfg(feature = "unsafe-aot-compilation")]
11    aot_compilation_enabled: bool,
12}
13
14impl ComponentLoader {
15    /// Create a new `ComponentLoader`
16    pub fn new() -> Self {
17        Self::default()
18    }
19
20    /// Updates the TriggerLoader to load AOT precompiled components
21    ///
22    /// **Warning: This feature may bypass important security guarantees of the
23    /// Wasmtime security sandbox if used incorrectly! Read this documentation
24    /// carefully.**
25    ///
26    /// Usually, components are compiled just-in-time from portable Wasm
27    /// sources. This method causes components to instead be loaded
28    /// ahead-of-time as Wasmtime-precompiled native executable binaries.
29    /// Precompiled binaries must be produced with a compatible Wasmtime engine
30    /// using the same Wasmtime version and compiler target settings - typically
31    /// by a host with the same processor that will be executing them. See the
32    /// Wasmtime documentation for more information:
33    /// https://docs.rs/wasmtime/latest/wasmtime/struct.Module.html#method.deserialize
34    ///
35    /// # Safety
36    ///
37    /// This method is marked as `unsafe` because it enables potentially unsafe
38    /// behavior if used to load malformed or malicious precompiled binaries.
39    /// Loading sources from an incompatible Wasmtime engine will fail but is
40    /// otherwise safe. This method is safe if it can be guaranteed that
41    /// `<TriggerLoader as Loader>::load_component` will only ever be called
42    /// with a trusted `LockedComponentSource`. **Precompiled binaries must
43    /// never be loaded from untrusted sources.**
44    #[cfg(feature = "unsafe-aot-compilation")]
45    pub unsafe fn enable_loading_aot_compiled_components(&mut self) {
46        self.aot_compilation_enabled = true;
47    }
48
49    #[cfg(feature = "unsafe-aot-compilation")]
50    fn load_precompiled_component(
51        &self,
52        engine: &wasmtime::Engine,
53        path: &std::path::Path,
54    ) -> anyhow::Result<Component> {
55        assert!(self.aot_compilation_enabled);
56        match engine.detect_precompiled_file(path)? {
57            Some(wasmtime::Precompiled::Component) => unsafe {
58                Component::deserialize_file(engine, path)
59            },
60            Some(wasmtime::Precompiled::Module) => {
61                anyhow::bail!("expected AOT compiled component but found module");
62            }
63            None => {
64                anyhow::bail!("expected AOT compiled component but found other data");
65            }
66        }
67    }
68}
69
70#[async_trait]
71impl<T: RuntimeFactors, U> spin_factors_executor::ComponentLoader<T, U> for ComponentLoader {
72    async fn load_component(
73        &self,
74        engine: &wasmtime::Engine,
75        component: &AppComponent,
76    ) -> anyhow::Result<Component> {
77        let source = component
78            .source()
79            .content
80            .source
81            .as_ref()
82            .context("LockedComponentSource missing source field")?;
83        let path = parse_file_url(source)?;
84
85        #[cfg(feature = "unsafe-aot-compilation")]
86        if self.aot_compilation_enabled {
87            return self
88                .load_precompiled_component(engine, &path)
89                .with_context(|| format!("error deserializing component from {path:?}"));
90        }
91
92        let composed = spin_compose::compose(&ComponentSourceLoaderFs, component.locked)
93            .await
94            .with_context(|| {
95                format!(
96                    "failed to resolve dependencies for component {:?}",
97                    component.locked.id
98                )
99            })?;
100
101        spin_core::Component::new(engine, composed)
102            .with_context(|| format!("failed to compile component from {}", quoted_path(&path)))
103    }
104}