From a8f191b306b2a83448683bb61395c10adf97627f Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Mon, 20 May 2024 10:02:50 -0400 Subject: [PATCH 01/22] Add Spin Factors crates Signed-off-by: Lann Martin --- Cargo.lock | 135 +++++++++++++------ crates/factor-wasi/Cargo.toml | 13 ++ crates/factor-wasi/src/lib.rs | 80 +++++++++++ crates/factor-wasi/src/preview1.rs | 32 +++++ crates/factors-derive/Cargo.toml | 20 +++ crates/factors-derive/src/lib.rs | 155 +++++++++++++++++++++ crates/factors/Cargo.toml | 17 +++ crates/factors/src/lib.rs | 209 +++++++++++++++++++++++++++++ crates/factors/tests/smoke.rs | 24 ++++ 9 files changed, 641 insertions(+), 44 deletions(-) create mode 100644 crates/factor-wasi/Cargo.toml create mode 100644 crates/factor-wasi/src/lib.rs create mode 100644 crates/factor-wasi/src/preview1.rs create mode 100644 crates/factors-derive/Cargo.toml create mode 100644 crates/factors-derive/src/lib.rs create mode 100644 crates/factors/Cargo.toml create mode 100644 crates/factors/src/lib.rs create mode 100644 crates/factors/tests/smoke.rs diff --git a/Cargo.lock b/Cargo.lock index ac7a0a983f..bd451ad02b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,18 +23,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher 0.3.0", - "cpufeatures", - "opaque-debug", -] - [[package]] name = "aes" version = "0.8.4" @@ -42,7 +30,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher 0.4.4", + "cipher", "cpufeatures", ] @@ -770,29 +758,31 @@ dependencies = [ ] [[package]] -name = "block-buffer" -version = "0.10.4" +name = "blake2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "generic-array", + "digest", ] [[package]] -name = "block-modes" -version = "0.8.1" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "block-padding", - "cipher 0.3.0", + "generic-array", ] [[package]] name = "block-padding" -version = "0.2.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] [[package]] name = "blocking" @@ -1220,6 +1210,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.0.90" @@ -1266,15 +1265,6 @@ dependencies = [ "windows-targets 0.52.4", ] -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array", -] - [[package]] name = "cipher" version = "0.4.4" @@ -2417,6 +2407,20 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "expander" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e83c02035136f1592a47964ea60c05a50e4ed8b5892cfac197063850898d4d" +dependencies = [ + "blake2", + "fs-err", + "prettier-please", + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -2557,6 +2561,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + [[package]] name = "fs-set-times" version = "0.20.1" @@ -3657,6 +3670,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ + "block-padding", "generic-array", ] @@ -5067,12 +5081,6 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "openssl" version = "0.10.64" @@ -5811,6 +5819,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettier-please" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3" +dependencies = [ + "proc-macro2", + "syn 2.0.58", +] + [[package]] name = "prettyplease" version = "0.2.17" @@ -6827,12 +6845,12 @@ dependencies = [ [[package]] name = "secret-service" -version = "3.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da1a5ad4d28c03536f82f77d9f36603f5e37d8869ac98f0a750d5b5686d8d95" +checksum = "b5204d39df37f06d1944935232fd2dfe05008def7ca599bf28c0800366c8a8f9" dependencies = [ - "aes 0.7.5", - "block-modes", + "aes", + "cbc", "futures-util", "generic-array", "hkdf", @@ -7540,6 +7558,35 @@ dependencies = [ "toml 0.5.11", ] +[[package]] +name = "spin-factor-wasi" +version = "2.6.0-pre0" +dependencies = [ + "anyhow", + "spin-factors", + "wasmtime-wasi", +] + +[[package]] +name = "spin-factors" +version = "2.6.0-pre0" +dependencies = [ + "anyhow", + "spin-app", + "spin-factors-derive", + "wasmtime", +] + +[[package]] +name = "spin-factors-derive" +version = "2.6.0-pre0" +dependencies = [ + "expander", + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "spin-http" version = "2.7.0-pre0" @@ -10838,7 +10885,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" dependencies = [ - "aes 0.8.4", + "aes", "byteorder", "bzip2", "constant_time_eq", diff --git a/crates/factor-wasi/Cargo.toml b/crates/factor-wasi/Cargo.toml new file mode 100644 index 0000000000..c50f2e629e --- /dev/null +++ b/crates/factor-wasi/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "spin-factor-wasi" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } + +[dependencies] +anyhow = "1.0" +spin-factors = { path = "../factors" } +wasmtime-wasi = { workspace = true } + +[lints] +workspace = true diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs new file mode 100644 index 0000000000..bfc3aae0db --- /dev/null +++ b/crates/factor-wasi/src/lib.rs @@ -0,0 +1,80 @@ +pub mod preview1; + +use spin_factors::{Factor, FactorBuilder, InitContext, PrepareContext, Result, SpinFactors}; +use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; + +pub struct WasiFactor; + +impl Factor for WasiFactor { + type Builder = Builder; + type Data = Data; + + fn init(&mut self, mut ctx: InitContext) -> Result<()> { + use wasmtime_wasi::bindings; + ctx.link_bindings(bindings::clocks::wall_clock::add_to_linker_get_host)?; + ctx.link_bindings(bindings::clocks::monotonic_clock::add_to_linker_get_host)?; + ctx.link_bindings(bindings::filesystem::types::add_to_linker_get_host)?; + ctx.link_bindings(bindings::filesystem::preopens::add_to_linker_get_host)?; + ctx.link_bindings(bindings::io::error::add_to_linker_get_host)?; + ctx.link_bindings(bindings::io::poll::add_to_linker_get_host)?; + ctx.link_bindings(bindings::io::streams::add_to_linker_get_host)?; + ctx.link_bindings(bindings::random::random::add_to_linker_get_host)?; + ctx.link_bindings(bindings::random::insecure::add_to_linker_get_host)?; + ctx.link_bindings(bindings::random::insecure_seed::add_to_linker_get_host)?; + ctx.link_bindings(bindings::cli::exit::add_to_linker_get_host)?; + ctx.link_bindings(bindings::cli::environment::add_to_linker_get_host)?; + ctx.link_bindings(bindings::cli::stdin::add_to_linker_get_host)?; + ctx.link_bindings(bindings::cli::stdout::add_to_linker_get_host)?; + ctx.link_bindings(bindings::cli::stderr::add_to_linker_get_host)?; + ctx.link_bindings(bindings::cli::terminal_input::add_to_linker_get_host)?; + ctx.link_bindings(bindings::cli::terminal_output::add_to_linker_get_host)?; + ctx.link_bindings(bindings::cli::terminal_stdin::add_to_linker_get_host)?; + ctx.link_bindings(bindings::cli::terminal_stdout::add_to_linker_get_host)?; + ctx.link_bindings(bindings::cli::terminal_stderr::add_to_linker_get_host)?; + ctx.link_bindings(bindings::sockets::tcp::add_to_linker_get_host)?; + ctx.link_bindings(bindings::sockets::tcp_create_socket::add_to_linker_get_host)?; + ctx.link_bindings(bindings::sockets::udp::add_to_linker_get_host)?; + ctx.link_bindings(bindings::sockets::udp_create_socket::add_to_linker_get_host)?; + ctx.link_bindings(bindings::sockets::instance_network::add_to_linker_get_host)?; + ctx.link_bindings(bindings::sockets::network::add_to_linker_get_host)?; + ctx.link_bindings(bindings::sockets::ip_name_lookup::add_to_linker_get_host)?; + Ok(()) + } +} + +pub struct Builder { + wasi_ctx: WasiCtxBuilder, +} + +impl FactorBuilder for Builder { + fn prepare( + _factor: &WasiFactor, + _ctx: PrepareContext, + ) -> Result { + Ok(Self { + wasi_ctx: WasiCtxBuilder::new(), + }) + } + + fn build(mut self) -> Result { + Ok(Data { + ctx: self.wasi_ctx.build(), + table: Default::default(), + }) + } +} + +pub struct Data { + ctx: WasiCtx, + table: ResourceTable, +} + +impl WasiView for Data { + fn ctx(&mut self) -> &mut WasiCtx { + &mut self.ctx + } + + fn table(&mut self) -> &mut ResourceTable { + &mut self.table + } +} diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs new file mode 100644 index 0000000000..360b713ca1 --- /dev/null +++ b/crates/factor-wasi/src/preview1.rs @@ -0,0 +1,32 @@ +use spin_factors::{Factor, FactorBuilder, ModuleInitContext, PrepareContext, Result, SpinFactors}; +use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder}; + +pub struct WasiPreview1Factor; + +impl Factor for WasiPreview1Factor { + type Builder = Builder; + type Data = WasiP1Ctx; + + fn module_init(&mut self, mut ctx: ModuleInitContext) -> Result<()> { + ctx.link_bindings(wasmtime_wasi::preview1::add_to_linker_async) + } +} + +pub struct Builder { + wasi_ctx: WasiCtxBuilder, +} + +impl FactorBuilder for Builder { + fn prepare( + _factor: &WasiPreview1Factor, + _ctx: PrepareContext, + ) -> Result { + Ok(Self { + wasi_ctx: WasiCtxBuilder::new(), + }) + } + + fn build(mut self) -> Result<::Data> { + Ok(self.wasi_ctx.build_p1()) + } +} diff --git a/crates/factors-derive/Cargo.toml b/crates/factors-derive/Cargo.toml new file mode 100644 index 0000000000..859a3c3290 --- /dev/null +++ b/crates/factors-derive/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "spin-factors-derive" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } + +[lib] +proc-macro = true + +[features] +expander = ["dep:expander"] + +[dependencies] +expander = { version = "2.1.0", optional = true } +proc-macro2 = "1.0.79" +quote = "1.0.35" +syn = "2.0.52" + +[lints] +workspace = true diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs new file mode 100644 index 0000000000..a0aa43b9d6 --- /dev/null +++ b/crates/factors-derive/src/lib.rs @@ -0,0 +1,155 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, Data, DeriveInput, Error}; + +#[proc_macro_derive(SpinFactors)] +pub fn derive_factors(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let expanded = expand_factors(&input).unwrap_or_else(|err| err.into_compile_error()); + + #[cfg(feature = "expander")] + let expanded = expander::Expander::new("factors") + .write_to_out_dir(expanded) + .unwrap(); + + expanded.into() +} + +#[allow(non_snake_case)] +fn expand_factors(input: &DeriveInput) -> syn::Result { + let name = &input.ident; + let vis = &input.vis; + + let builders_name = format_ident!("{name}Builders"); + let data_name = format_ident!("{name}Data"); + + if !input.generics.params.is_empty() { + return Err(Error::new_spanned( + input, + "cannot derive Factors for generic structs", + )); + } + + // Get struct fields + let fields = match &input.data { + Data::Struct(struct_data) => &struct_data.fields, + _ => { + return Err(Error::new_spanned( + input, + "can only derive Factors for structs", + )) + } + }; + let mut factor_names = Vec::with_capacity(fields.len()); + let mut factor_types = Vec::with_capacity(fields.len()); + for field in fields.iter() { + factor_names.push( + field + .ident + .as_ref() + .ok_or_else(|| Error::new_spanned(input, "tuple structs are not supported"))?, + ); + factor_types.push(&field.ty); + } + + let factors_crate = format_ident!("spin_factors"); + let factors_path = quote!(::#factors_crate); + let Factor = quote!(#factors_path::Factor); + let Result = quote!(#factors_path::Result); + let wasmtime = quote!(#factors_path::wasmtime); + let TypeId = quote!(::std::any::TypeId); + + Ok(quote! { + impl #name { + pub fn init( + &mut self, + linker: &mut #wasmtime::component::Linker<#data_name> + ) -> #Result<()> { + #( + self.#factor_names.init( + #factors_path::InitContext::::new( + linker, + |data| &mut data.#factor_names, + ) + )?; + )* + Ok(()) + } + + pub fn module_init( + &mut self, + linker: &mut #wasmtime::Linker<#data_name> + ) -> #Result<()> { + #( + self.#factor_names.module_init::( + #factors_path::ModuleInitContext::::new( + linker, + |data| &mut data.#factor_names, + ) + )?; + )* + Ok(()) + } + + pub fn build_data(&self) -> #Result<#data_name> { + let mut builders = #builders_name { + #( #factor_names: None, )* + }; + #( + builders.#factor_names = Some( + #factors_path::FactorBuilder::<#factor_types>::prepare::<#name>( + &self.#factor_names, + #factors_path::PrepareContext::new(&mut builders), + )? + ); + )* + Ok(#data_name { + #( + #factor_names: #factors_path::FactorBuilder::<#factor_types>::build( + builders.#factor_names.unwrap() + )?, + )* + }) + } + + } + + impl #factors_path::SpinFactors for #name { + type Builders = #builders_name; + type Data = #data_name; + + unsafe fn factor_builder_offset() -> Option { + let type_id = #TypeId::of::(); + #( + if type_id == #TypeId::of::<#factor_types>() { + return Some(std::mem::offset_of!(Self::Builders, #factor_names)); + } + )* + None + } + + unsafe fn factor_data_offset() -> Option { + let type_id = #TypeId::of::(); + #( + if type_id == #TypeId::of::<#factor_types>() { + return Some(std::mem::offset_of!(Self::Data, #factor_names)); + } + )* + None + + } + } + + #vis struct #builders_name { + #( + pub #factor_names: Option<<#factor_types as #Factor>::Builder>, + )* + } + + #vis struct #data_name { + #( + pub #factor_names: <#factor_types as #Factor>::Data, + )* + } + }) +} diff --git a/crates/factors/Cargo.toml b/crates/factors/Cargo.toml new file mode 100644 index 0000000000..55fbd82de2 --- /dev/null +++ b/crates/factors/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "spin-factors" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } + +[dependencies] +anyhow = "1.0" +spin-app = { path = "../app" } +spin-factors-derive = { path = "../factors-derive" } +wasmtime = { workspace = true } + +[dev-dependencies] +spin-factors-derive = { path = "../factors-derive", features = ["expander"] } + +[lints] +workspace = true diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs new file mode 100644 index 0000000000..f65e65e15e --- /dev/null +++ b/crates/factors/src/lib.rs @@ -0,0 +1,209 @@ +use std::{any::Any, marker::PhantomData}; + +use spin_app::App; +pub use spin_factors_derive::SpinFactors; + +pub use wasmtime; + +pub type Error = wasmtime::Error; +pub type Result = std::result::Result; + +pub type Linker = wasmtime::component::Linker<::Data>; +pub type ModuleLinker = wasmtime::Linker<::Data>; + +pub trait Factor: Any + Sized { + type Builder: FactorBuilder; + type Data; + + /// Initializes this Factor for a runtime. This will be called exactly once + fn init(&mut self, mut ctx: InitContext) -> Result<()> { + _ = &mut ctx; + Ok(()) + } + + fn module_init( + &mut self, + mut ctx: ModuleInitContext, + ) -> Result<()> { + _ = &mut ctx; + Ok(()) + } + + fn validate_app(&self, app: &App) -> Result<()> { + _ = app; + Ok(()) + } +} + +pub struct FactorInitContext<'a, Factors: SpinFactors, Fact: Factor, Linker> { + linker: &'a mut Linker, + get_data: fn(&mut Factors::Data) -> &mut Fact::Data, +} + +pub type InitContext<'a, Factors, Fact> = FactorInitContext<'a, Factors, Fact, Linker>; + +pub type ModuleInitContext<'a, Factors, Fact> = + FactorInitContext<'a, Factors, Fact, ModuleLinker>; + +impl<'a, Factors: SpinFactors, Fact: Factor, Linker> FactorInitContext<'a, Factors, Fact, Linker> { + #[doc(hidden)] + pub fn new( + linker: &'a mut Linker, + get_data: fn(&mut Factors::Data) -> &mut Fact::Data, + ) -> Self { + Self { linker, get_data } + } + + pub fn linker(&mut self) -> &mut Linker { + self.linker + } + + pub fn link_bindings( + &mut self, + add_to_linker: impl Fn(&mut Linker, fn(&mut Factors::Data) -> &mut Fact::Data) -> Result<()>, + ) -> Result<()> +where { + add_to_linker(self.linker, self.get_data) + } +} + +impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { + pub fn builder_mut(&mut self) -> Result<&mut T::Builder> { + let err_msg = match Factors::builder_mut::(self.builders) { + Some(Some(builder)) => return Ok(builder), + Some(None) => "builder not yet prepared", + None => "no such factor", + }; + Err(Error::msg(format!( + "could not get builder for {ty}: {err_msg}", + ty = std::any::type_name::() + ))) + } +} + +/// Implemented by `#[derive(SpinFactors)]` +pub trait SpinFactors: Sized { + type Builders; + type Data: Send + 'static; + + #[doc(hidden)] + unsafe fn factor_builder_offset() -> Option; + + #[doc(hidden)] + unsafe fn factor_data_offset() -> Option; + + fn data_getter() -> Option> { + let offset = unsafe { Self::factor_data_offset::()? }; + Some(Getter { + offset, + _phantom: PhantomData, + }) + } + + fn data_getter2() -> Option> { + let offset1 = unsafe { Self::factor_data_offset::()? }; + let offset2 = unsafe { Self::factor_data_offset::()? }; + assert_ne!( + offset1, offset2, + "data_getter2 with same factor twice would alias" + ); + Some(Getter2 { + offset1, + offset2, + _phantom: PhantomData, + }) + } + + fn builder_mut(builders: &mut Self::Builders) -> Option> { + unsafe { + let offset = Self::factor_builder_offset::()?; + let ptr = builders as *mut Self::Builders; + let opt = &mut *ptr.add(offset).cast::>(); + Some(opt.as_mut()) + } + } +} + +pub struct Getter { + offset: usize, + _phantom: PhantomData &mut U>, +} + +impl Getter { + pub fn get_mut<'a>(&self, container: &'a mut T) -> &'a mut U { + let ptr = container as *mut T; + unsafe { &mut *ptr.add(self.offset).cast::() } + } +} + +impl Clone for Getter { + fn clone(&self) -> Self { + *self + } +} +impl Copy for Getter {} + +pub struct Getter2 { + offset1: usize, + offset2: usize, + #[allow(clippy::type_complexity)] + _phantom: PhantomData (&mut U, &mut V)>, +} + +impl Getter2 { + pub fn get_mut<'a>(&self, container: &'a mut T) -> (&'a mut U, &'a mut V) + where + T: 'static, + U: 'static, + V: 'static, + { + let ptr = container as *mut T; + unsafe { + ( + &mut *ptr.add(self.offset1).cast::(), + &mut *ptr.add(self.offset2).cast::(), + ) + } + } +} + +impl Clone for Getter2 { + fn clone(&self) -> Self { + *self + } +} +impl Copy for Getter2 {} + +pub trait FactorBuilder: Sized { + fn prepare(_factor: &T, _ctx: PrepareContext) -> Result; + + fn build(self) -> Result; +} + +pub struct PrepareContext<'a, Factors: SpinFactors> { + builders: &'a mut Factors::Builders, + // TODO: component: &'a AppComponent, +} + +impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { + #[doc(hidden)] + pub fn new(builders: &'a mut Factors::Builders) -> Self { + Self { builders } + } +} + +pub type DefaultBuilder = (); + +impl FactorBuilder for DefaultBuilder +where + T::Data: Default, +{ + fn prepare(factor: &T, ctx: PrepareContext) -> Result { + (_, _) = (factor, ctx); + Ok(()) + } + + fn build(self) -> Result { + Ok(Default::default()) + } +} diff --git a/crates/factors/tests/smoke.rs b/crates/factors/tests/smoke.rs new file mode 100644 index 0000000000..2deb8cc240 --- /dev/null +++ b/crates/factors/tests/smoke.rs @@ -0,0 +1,24 @@ +use spin_factors::SpinFactors; + +#[derive(SpinFactors)] +struct Factors {} + +fn main() -> anyhow::Result<()> { + let mut factors = Factors {}; + + let engine = wasmtime::Engine::default(); + let mut linker = wasmtime::component::Linker::new(&engine); + factors.init(&mut linker).unwrap(); + + let factors = Factors { + // wasi: WasiFactor, + // outbound_networking_factor: OutboundNetworkingFactor, + // outbound_http_factor: OutboundHttpFactor, + }; + let data = factors.build_data().unwrap(); + + let mut store = wasmtime::Store::new(&engine, data); + let component = wasmtime::component::Component::new(&engine, b"(component)").unwrap(); + let _instance = linker.instantiate(&mut store, &component).unwrap(); + Ok(()) +} From 84b500760891b5c2a032a424d41095eaea8b6d74 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Tue, 21 May 2024 14:55:21 -0400 Subject: [PATCH 02/22] factors: Naming updates Signed-off-by: Lann Martin --- crates/factor-wasi/src/lib.rs | 20 +++++++------ crates/factor-wasi/src/preview1.rs | 19 +++++++----- crates/factors-derive/src/lib.rs | 8 +++--- crates/factors/src/lib.rs | 46 +++++++++++++++++------------- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs index bfc3aae0db..fe08d8f35e 100644 --- a/crates/factor-wasi/src/lib.rs +++ b/crates/factor-wasi/src/lib.rs @@ -1,13 +1,15 @@ pub mod preview1; -use spin_factors::{Factor, FactorBuilder, InitContext, PrepareContext, Result, SpinFactors}; +use spin_factors::{ + Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors, +}; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; pub struct WasiFactor; impl Factor for WasiFactor { - type Builder = Builder; - type Data = Data; + type InstancePreparer = Builder; + type InstanceState = InstanceState; fn init(&mut self, mut ctx: InitContext) -> Result<()> { use wasmtime_wasi::bindings; @@ -46,8 +48,8 @@ pub struct Builder { wasi_ctx: WasiCtxBuilder, } -impl FactorBuilder for Builder { - fn prepare( +impl FactorInstancePreparer for Builder { + fn new( _factor: &WasiFactor, _ctx: PrepareContext, ) -> Result { @@ -56,20 +58,20 @@ impl FactorBuilder for Builder { }) } - fn build(mut self) -> Result { - Ok(Data { + fn prepare(mut self) -> Result { + Ok(InstanceState { ctx: self.wasi_ctx.build(), table: Default::default(), }) } } -pub struct Data { +pub struct InstanceState { ctx: WasiCtx, table: ResourceTable, } -impl WasiView for Data { +impl WasiView for InstanceState { fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs index 360b713ca1..5773a0a620 100644 --- a/crates/factor-wasi/src/preview1.rs +++ b/crates/factor-wasi/src/preview1.rs @@ -1,13 +1,18 @@ -use spin_factors::{Factor, FactorBuilder, ModuleInitContext, PrepareContext, Result, SpinFactors}; +use spin_factors::{ + Factor, FactorInstancePreparer, ModuleInitContext, PrepareContext, Result, SpinFactors, +}; use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder}; pub struct WasiPreview1Factor; impl Factor for WasiPreview1Factor { - type Builder = Builder; - type Data = WasiP1Ctx; + type InstancePreparer = Builder; + type InstanceState = WasiP1Ctx; - fn module_init(&mut self, mut ctx: ModuleInitContext) -> Result<()> { + fn module_init( + &mut self, + mut ctx: ModuleInitContext, + ) -> Result<()> { ctx.link_bindings(wasmtime_wasi::preview1::add_to_linker_async) } } @@ -16,8 +21,8 @@ pub struct Builder { wasi_ctx: WasiCtxBuilder, } -impl FactorBuilder for Builder { - fn prepare( +impl FactorInstancePreparer for Builder { + fn new( _factor: &WasiPreview1Factor, _ctx: PrepareContext, ) -> Result { @@ -26,7 +31,7 @@ impl FactorBuilder for Builder { }) } - fn build(mut self) -> Result<::Data> { + fn prepare(mut self) -> Result<::InstanceState> { Ok(self.wasi_ctx.build_p1()) } } diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index a0aa43b9d6..0cb8381b06 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -21,7 +21,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { let vis = &input.vis; let builders_name = format_ident!("{name}Builders"); - let data_name = format_ident!("{name}Data"); + let data_name = format_ident!("{name}InstanceState"); if !input.generics.params.is_empty() { return Err(Error::new_spanned( @@ -116,7 +116,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { impl #factors_path::SpinFactors for #name { type Builders = #builders_name; - type Data = #data_name; + type InstanceState = #data_name; unsafe fn factor_builder_offset() -> Option { let type_id = #TypeId::of::(); @@ -132,7 +132,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { let type_id = #TypeId::of::(); #( if type_id == #TypeId::of::<#factor_types>() { - return Some(std::mem::offset_of!(Self::Data, #factor_names)); + return Some(std::mem::offset_of!(Self::InstanceState, #factor_names)); } )* None @@ -148,7 +148,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { #vis struct #data_name { #( - pub #factor_names: <#factor_types as #Factor>::Data, + pub #factor_names: <#factor_types as #Factor>::InstanceState, )* } }) diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index f65e65e15e..af86ebb3d4 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -8,12 +8,12 @@ pub use wasmtime; pub type Error = wasmtime::Error; pub type Result = std::result::Result; -pub type Linker = wasmtime::component::Linker<::Data>; -pub type ModuleLinker = wasmtime::Linker<::Data>; +pub type Linker = wasmtime::component::Linker<::InstanceState>; +pub type ModuleLinker = wasmtime::Linker<::InstanceState>; pub trait Factor: Any + Sized { - type Builder: FactorBuilder; - type Data; + type InstancePreparer: FactorInstancePreparer; + type InstanceState; /// Initializes this Factor for a runtime. This will be called exactly once fn init(&mut self, mut ctx: InitContext) -> Result<()> { @@ -37,7 +37,7 @@ pub trait Factor: Any + Sized { pub struct FactorInitContext<'a, Factors: SpinFactors, Fact: Factor, Linker> { linker: &'a mut Linker, - get_data: fn(&mut Factors::Data) -> &mut Fact::Data, + get_data: fn(&mut Factors::InstanceState) -> &mut Fact::InstanceState, } pub type InitContext<'a, Factors, Fact> = FactorInitContext<'a, Factors, Fact, Linker>; @@ -49,7 +49,7 @@ impl<'a, Factors: SpinFactors, Fact: Factor, Linker> FactorInitContext<'a, Facto #[doc(hidden)] pub fn new( linker: &'a mut Linker, - get_data: fn(&mut Factors::Data) -> &mut Fact::Data, + get_data: fn(&mut Factors::InstanceState) -> &mut Fact::InstanceState, ) -> Self { Self { linker, get_data } } @@ -60,7 +60,10 @@ impl<'a, Factors: SpinFactors, Fact: Factor, Linker> FactorInitContext<'a, Facto pub fn link_bindings( &mut self, - add_to_linker: impl Fn(&mut Linker, fn(&mut Factors::Data) -> &mut Fact::Data) -> Result<()>, + add_to_linker: impl Fn( + &mut Linker, + fn(&mut Factors::InstanceState) -> &mut Fact::InstanceState, + ) -> Result<()>, ) -> Result<()> where { add_to_linker(self.linker, self.get_data) @@ -68,7 +71,7 @@ where { } impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { - pub fn builder_mut(&mut self) -> Result<&mut T::Builder> { + pub fn builder_mut(&mut self) -> Result<&mut T::InstancePreparer> { let err_msg = match Factors::builder_mut::(self.builders) { Some(Some(builder)) => return Ok(builder), Some(None) => "builder not yet prepared", @@ -84,7 +87,7 @@ impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { /// Implemented by `#[derive(SpinFactors)]` pub trait SpinFactors: Sized { type Builders; - type Data: Send + 'static; + type InstanceState: Send + 'static; #[doc(hidden)] unsafe fn factor_builder_offset() -> Option; @@ -92,7 +95,7 @@ pub trait SpinFactors: Sized { #[doc(hidden)] unsafe fn factor_data_offset() -> Option; - fn data_getter() -> Option> { + fn data_getter() -> Option> { let offset = unsafe { Self::factor_data_offset::()? }; Some(Getter { offset, @@ -100,7 +103,8 @@ pub trait SpinFactors: Sized { }) } - fn data_getter2() -> Option> { + fn data_getter2( + ) -> Option> { let offset1 = unsafe { Self::factor_data_offset::()? }; let offset2 = unsafe { Self::factor_data_offset::()? }; assert_ne!( @@ -114,11 +118,13 @@ pub trait SpinFactors: Sized { }) } - fn builder_mut(builders: &mut Self::Builders) -> Option> { + fn builder_mut( + builders: &mut Self::Builders, + ) -> Option> { unsafe { let offset = Self::factor_builder_offset::()?; let ptr = builders as *mut Self::Builders; - let opt = &mut *ptr.add(offset).cast::>(); + let opt = &mut *ptr.add(offset).cast::>(); Some(opt.as_mut()) } } @@ -174,10 +180,10 @@ impl Clone for Getter2 { } impl Copy for Getter2 {} -pub trait FactorBuilder: Sized { - fn prepare(_factor: &T, _ctx: PrepareContext) -> Result; +pub trait FactorInstancePreparer: Sized { + fn new(_factor: &T, _ctx: PrepareContext) -> Result; - fn build(self) -> Result; + fn prepare(self) -> Result; } pub struct PrepareContext<'a, Factors: SpinFactors> { @@ -194,16 +200,16 @@ impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { pub type DefaultBuilder = (); -impl FactorBuilder for DefaultBuilder +impl FactorInstancePreparer for DefaultBuilder where - T::Data: Default, + T::InstanceState: Default, { - fn prepare(factor: &T, ctx: PrepareContext) -> Result { + fn new(factor: &T, ctx: PrepareContext) -> Result { (_, _) = (factor, ctx); Ok(()) } - fn build(self) -> Result { + fn prepare(self) -> Result { Ok(Default::default()) } } From c5c2e61fc7ab44a641f6a1927df1d40e2d1956fa Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Tue, 21 May 2024 17:00:53 -0400 Subject: [PATCH 03/22] factors: Fix derive macro Signed-off-by: Lann Martin --- Cargo.lock | 1 + crates/factor-wasi/src/lib.rs | 6 ++-- crates/factor-wasi/src/preview1.rs | 4 +-- crates/factors-derive/src/lib.rs | 51 ++++++++++++++++-------------- crates/factors/Cargo.toml | 1 + crates/factors/src/lib.rs | 50 ++++++++++++++--------------- crates/factors/tests/smoke.rs | 15 +++++---- 7 files changed, 66 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd451ad02b..4159011a46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7573,6 +7573,7 @@ version = "2.6.0-pre0" dependencies = [ "anyhow", "spin-app", + "spin-factor-wasi", "spin-factors-derive", "wasmtime", ] diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs index fe08d8f35e..02a135e03a 100644 --- a/crates/factor-wasi/src/lib.rs +++ b/crates/factor-wasi/src/lib.rs @@ -1,8 +1,6 @@ pub mod preview1; -use spin_factors::{ - Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors, -}; +use spin_factors::{Factor, InitContext, InstancePreparer, PrepareContext, Result, SpinFactors}; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; pub struct WasiFactor; @@ -48,7 +46,7 @@ pub struct Builder { wasi_ctx: WasiCtxBuilder, } -impl FactorInstancePreparer for Builder { +impl InstancePreparer for Builder { fn new( _factor: &WasiFactor, _ctx: PrepareContext, diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs index 5773a0a620..4c4cef5a32 100644 --- a/crates/factor-wasi/src/preview1.rs +++ b/crates/factor-wasi/src/preview1.rs @@ -1,5 +1,5 @@ use spin_factors::{ - Factor, FactorInstancePreparer, ModuleInitContext, PrepareContext, Result, SpinFactors, + Factor, InstancePreparer, ModuleInitContext, PrepareContext, Result, SpinFactors, }; use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder}; @@ -21,7 +21,7 @@ pub struct Builder { wasi_ctx: WasiCtxBuilder, } -impl FactorInstancePreparer for Builder { +impl InstancePreparer for Builder { fn new( _factor: &WasiPreview1Factor, _ctx: PrepareContext, diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index 0cb8381b06..18aed6d1c4 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -20,8 +20,8 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { let name = &input.ident; let vis = &input.vis; - let builders_name = format_ident!("{name}Builders"); - let data_name = format_ident!("{name}InstanceState"); + let preparers_name = format_ident!("{name}InstancePreparers"); + let state_name = format_ident!("{name}InstanceState"); if !input.generics.params.is_empty() { return Err(Error::new_spanned( @@ -63,50 +63,53 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { impl #name { pub fn init( &mut self, - linker: &mut #wasmtime::component::Linker<#data_name> + linker: &mut #wasmtime::component::Linker<#state_name> ) -> #Result<()> { #( - self.#factor_names.init( + #Factor::init::( + &mut self.#factor_names, #factors_path::InitContext::::new( linker, - |data| &mut data.#factor_names, + |state| &mut state.#factor_names, ) )?; )* Ok(()) } + #[allow(dead_code)] pub fn module_init( &mut self, - linker: &mut #wasmtime::Linker<#data_name> + linker: &mut #wasmtime::Linker<#state_name> ) -> #Result<()> { #( - self.#factor_names.module_init::( + #Factor::module_init::( + &mut self.#factor_names, #factors_path::ModuleInitContext::::new( linker, - |data| &mut data.#factor_names, + |state| &mut state.#factor_names, ) )?; )* Ok(()) } - pub fn build_data(&self) -> #Result<#data_name> { - let mut builders = #builders_name { + pub fn build_store_data(&self) -> #Result<#state_name> { + let mut preparers = #preparers_name { #( #factor_names: None, )* }; #( - builders.#factor_names = Some( - #factors_path::FactorBuilder::<#factor_types>::prepare::<#name>( + preparers.#factor_names = Some( + #factors_path::InstancePreparer::<#factor_types>::new::<#name>( &self.#factor_names, - #factors_path::PrepareContext::new(&mut builders), + #factors_path::PrepareContext::new(&mut preparers), )? ); )* - Ok(#data_name { + Ok(#state_name { #( - #factor_names: #factors_path::FactorBuilder::<#factor_types>::build( - builders.#factor_names.unwrap() + #factor_names: #factors_path::InstancePreparer::<#factor_types>::prepare( + preparers.#factor_names.unwrap() )?, )* }) @@ -115,20 +118,20 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { } impl #factors_path::SpinFactors for #name { - type Builders = #builders_name; - type InstanceState = #data_name; + type InstancePreparers = #preparers_name; + type InstanceState = #state_name; - unsafe fn factor_builder_offset() -> Option { + unsafe fn instance_preparer_offset() -> Option { let type_id = #TypeId::of::(); #( if type_id == #TypeId::of::<#factor_types>() { - return Some(std::mem::offset_of!(Self::Builders, #factor_names)); + return Some(std::mem::offset_of!(Self::InstancePreparers, #factor_names)); } )* None } - unsafe fn factor_data_offset() -> Option { + unsafe fn instance_state_offset() -> Option { let type_id = #TypeId::of::(); #( if type_id == #TypeId::of::<#factor_types>() { @@ -140,13 +143,13 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { } } - #vis struct #builders_name { + #vis struct #preparers_name { #( - pub #factor_names: Option<<#factor_types as #Factor>::Builder>, + pub #factor_names: Option<<#factor_types as #Factor>::InstancePreparer>, )* } - #vis struct #data_name { + #vis struct #state_name { #( pub #factor_names: <#factor_types as #Factor>::InstanceState, )* diff --git a/crates/factors/Cargo.toml b/crates/factors/Cargo.toml index 55fbd82de2..6e3e75d819 100644 --- a/crates/factors/Cargo.toml +++ b/crates/factors/Cargo.toml @@ -12,6 +12,7 @@ wasmtime = { workspace = true } [dev-dependencies] spin-factors-derive = { path = "../factors-derive", features = ["expander"] } +spin-factor-wasi = { path = "../factor-wasi" } [lints] workspace = true diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index af86ebb3d4..317200622a 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -12,7 +12,7 @@ pub type Linker = wasmtime::component::Linker<: pub type ModuleLinker = wasmtime::Linker<::InstanceState>; pub trait Factor: Any + Sized { - type InstancePreparer: FactorInstancePreparer; + type InstancePreparer: InstancePreparer; type InstanceState; /// Initializes this Factor for a runtime. This will be called exactly once @@ -71,14 +71,14 @@ where { } impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { - pub fn builder_mut(&mut self) -> Result<&mut T::InstancePreparer> { - let err_msg = match Factors::builder_mut::(self.builders) { - Some(Some(builder)) => return Ok(builder), - Some(None) => "builder not yet prepared", + pub fn instance_preparer_mut(&mut self) -> Result<&mut T::InstancePreparer> { + let err_msg = match Factors::instance_preparer_mut::(self.instance_preparers) { + Some(Some(preparer)) => return Ok(preparer), + Some(None) => "preparer not yet initialized", None => "no such factor", }; Err(Error::msg(format!( - "could not get builder for {ty}: {err_msg}", + "could not get instance preparer for {ty}: {err_msg}", ty = std::any::type_name::() ))) } @@ -86,30 +86,30 @@ impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { /// Implemented by `#[derive(SpinFactors)]` pub trait SpinFactors: Sized { - type Builders; + type InstancePreparers; type InstanceState: Send + 'static; #[doc(hidden)] - unsafe fn factor_builder_offset() -> Option; + unsafe fn instance_preparer_offset() -> Option; #[doc(hidden)] - unsafe fn factor_data_offset() -> Option; + unsafe fn instance_state_offset() -> Option; - fn data_getter() -> Option> { - let offset = unsafe { Self::factor_data_offset::()? }; + fn instance_state_getter() -> Option> { + let offset = unsafe { Self::instance_state_offset::()? }; Some(Getter { offset, _phantom: PhantomData, }) } - fn data_getter2( + fn instance_state_getter2( ) -> Option> { - let offset1 = unsafe { Self::factor_data_offset::()? }; - let offset2 = unsafe { Self::factor_data_offset::()? }; + let offset1 = unsafe { Self::instance_state_offset::()? }; + let offset2 = unsafe { Self::instance_state_offset::()? }; assert_ne!( offset1, offset2, - "data_getter2 with same factor twice would alias" + "instance_state_getter2 with same factor twice would alias" ); Some(Getter2 { offset1, @@ -118,12 +118,12 @@ pub trait SpinFactors: Sized { }) } - fn builder_mut( - builders: &mut Self::Builders, + fn instance_preparer_mut( + preparers: &mut Self::InstancePreparers, ) -> Option> { unsafe { - let offset = Self::factor_builder_offset::()?; - let ptr = builders as *mut Self::Builders; + let offset = Self::instance_preparer_offset::()?; + let ptr = preparers as *mut Self::InstancePreparers; let opt = &mut *ptr.add(offset).cast::>(); Some(opt.as_mut()) } @@ -180,27 +180,27 @@ impl Clone for Getter2 { } impl Copy for Getter2 {} -pub trait FactorInstancePreparer: Sized { +pub trait InstancePreparer: Sized { fn new(_factor: &T, _ctx: PrepareContext) -> Result; fn prepare(self) -> Result; } pub struct PrepareContext<'a, Factors: SpinFactors> { - builders: &'a mut Factors::Builders, + instance_preparers: &'a mut Factors::InstancePreparers, // TODO: component: &'a AppComponent, } impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { #[doc(hidden)] - pub fn new(builders: &'a mut Factors::Builders) -> Self { - Self { builders } + pub fn new(instance_preparers: &'a mut Factors::InstancePreparers) -> Self { + Self { instance_preparers } } } -pub type DefaultBuilder = (); +pub type DefaultInstancePreparer = (); -impl FactorInstancePreparer for DefaultBuilder +impl InstancePreparer for DefaultInstancePreparer where T::InstanceState: Default, { diff --git a/crates/factors/tests/smoke.rs b/crates/factors/tests/smoke.rs index 2deb8cc240..2c81173065 100644 --- a/crates/factors/tests/smoke.rs +++ b/crates/factors/tests/smoke.rs @@ -1,21 +1,22 @@ +use spin_factor_wasi::WasiFactor; use spin_factors::SpinFactors; #[derive(SpinFactors)] -struct Factors {} +struct Factors { + wasi: WasiFactor, +} fn main() -> anyhow::Result<()> { - let mut factors = Factors {}; - let engine = wasmtime::Engine::default(); let mut linker = wasmtime::component::Linker::new(&engine); - factors.init(&mut linker).unwrap(); - let factors = Factors { - // wasi: WasiFactor, + let mut factors = Factors { + wasi: WasiFactor, // outbound_networking_factor: OutboundNetworkingFactor, // outbound_http_factor: OutboundHttpFactor, }; - let data = factors.build_data().unwrap(); + factors.init(&mut linker).unwrap(); + let data = factors.build_store_data().unwrap(); let mut store = wasmtime::Store::new(&engine, data); let component = wasmtime::component::Component::new(&engine, b"(component)").unwrap(); From 8cf5e54fc87a6d5db895d5a9b54681edbf165210 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Tue, 21 May 2024 17:19:05 -0400 Subject: [PATCH 04/22] factors: trait InstancePreparer -> FactorInstancePreparer Signed-off-by: Lann Martin --- crates/factor-wasi/src/lib.rs | 10 ++-- crates/factor-wasi/src/preview1.rs | 8 +-- crates/factors-derive/src/lib.rs | 4 +- crates/factors/src/lib.rs | 82 +++++++++++++++--------------- 4 files changed, 54 insertions(+), 50 deletions(-) diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs index 02a135e03a..c6a90f1776 100644 --- a/crates/factor-wasi/src/lib.rs +++ b/crates/factor-wasi/src/lib.rs @@ -1,12 +1,14 @@ pub mod preview1; -use spin_factors::{Factor, InitContext, InstancePreparer, PrepareContext, Result, SpinFactors}; +use spin_factors::{ + Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors, +}; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; pub struct WasiFactor; impl Factor for WasiFactor { - type InstancePreparer = Builder; + type InstancePreparer = InstancePreparer; type InstanceState = InstanceState; fn init(&mut self, mut ctx: InitContext) -> Result<()> { @@ -42,11 +44,11 @@ impl Factor for WasiFactor { } } -pub struct Builder { +pub struct InstancePreparer { wasi_ctx: WasiCtxBuilder, } -impl InstancePreparer for Builder { +impl FactorInstancePreparer for InstancePreparer { fn new( _factor: &WasiFactor, _ctx: PrepareContext, diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs index 4c4cef5a32..1909cf35c8 100644 --- a/crates/factor-wasi/src/preview1.rs +++ b/crates/factor-wasi/src/preview1.rs @@ -1,12 +1,12 @@ use spin_factors::{ - Factor, InstancePreparer, ModuleInitContext, PrepareContext, Result, SpinFactors, + Factor, FactorInstancePreparer, ModuleInitContext, PrepareContext, Result, SpinFactors, }; use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder}; pub struct WasiPreview1Factor; impl Factor for WasiPreview1Factor { - type InstancePreparer = Builder; + type InstancePreparer = InstancePreparer; type InstanceState = WasiP1Ctx; fn module_init( @@ -17,11 +17,11 @@ impl Factor for WasiPreview1Factor { } } -pub struct Builder { +pub struct InstancePreparer { wasi_ctx: WasiCtxBuilder, } -impl InstancePreparer for Builder { +impl FactorInstancePreparer for InstancePreparer { fn new( _factor: &WasiPreview1Factor, _ctx: PrepareContext, diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index 18aed6d1c4..d3126050b9 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -100,7 +100,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { }; #( preparers.#factor_names = Some( - #factors_path::InstancePreparer::<#factor_types>::new::<#name>( + #factors_path::FactorInstancePreparer::<#factor_types>::new::<#name>( &self.#factor_names, #factors_path::PrepareContext::new(&mut preparers), )? @@ -108,7 +108,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { )* Ok(#state_name { #( - #factor_names: #factors_path::InstancePreparer::<#factor_types>::prepare( + #factor_names: #factors_path::FactorInstancePreparer::<#factor_types>::prepare( preparers.#factor_names.unwrap() )?, )* diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index 317200622a..d42de76f86 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -12,7 +12,7 @@ pub type Linker = wasmtime::component::Linker<: pub type ModuleLinker = wasmtime::Linker<::InstanceState>; pub trait Factor: Any + Sized { - type InstancePreparer: InstancePreparer; + type InstancePreparer: FactorInstancePreparer; type InstanceState; /// Initializes this Factor for a runtime. This will be called exactly once @@ -35,9 +35,12 @@ pub trait Factor: Any + Sized { } } +type GetDataFn = + fn(&mut ::InstanceState) -> &mut ::InstanceState; + pub struct FactorInitContext<'a, Factors: SpinFactors, Fact: Factor, Linker> { linker: &'a mut Linker, - get_data: fn(&mut Factors::InstanceState) -> &mut Fact::InstanceState, + get_data: GetDataFn, } pub type InitContext<'a, Factors, Fact> = FactorInitContext<'a, Factors, Fact, Linker>; @@ -47,10 +50,7 @@ pub type ModuleInitContext<'a, Factors, Fact> = impl<'a, Factors: SpinFactors, Fact: Factor, Linker> FactorInitContext<'a, Factors, Fact, Linker> { #[doc(hidden)] - pub fn new( - linker: &'a mut Linker, - get_data: fn(&mut Factors::InstanceState) -> &mut Fact::InstanceState, - ) -> Self { + pub fn new(linker: &'a mut Linker, get_data: GetDataFn) -> Self { Self { linker, get_data } } @@ -58,6 +58,10 @@ impl<'a, Factors: SpinFactors, Fact: Factor, Linker> FactorInitContext<'a, Facto self.linker } + pub fn get_data_fn(&self) -> GetDataFn { + self.get_data + } + pub fn link_bindings( &mut self, add_to_linker: impl Fn( @@ -70,7 +74,23 @@ where { } } +pub trait FactorInstancePreparer: Sized { + fn new(factor: &T, _ctx: PrepareContext) -> Result; + + fn prepare(self) -> Result; +} + +pub struct PrepareContext<'a, Factors: SpinFactors> { + instance_preparers: &'a mut Factors::InstancePreparers, + // TODO: component: &'a AppComponent, +} + impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { + #[doc(hidden)] + pub fn new(instance_preparers: &'a mut Factors::InstancePreparers) -> Self { + Self { instance_preparers } + } + pub fn instance_preparer_mut(&mut self) -> Result<&mut T::InstancePreparer> { let err_msg = match Factors::instance_preparer_mut::(self.instance_preparers) { Some(Some(preparer)) => return Ok(preparer), @@ -84,6 +104,22 @@ impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { } } +pub type DefaultInstancePreparer = (); + +impl FactorInstancePreparer for DefaultInstancePreparer +where + T::InstanceState: Default, +{ + fn new(factor: &T, ctx: PrepareContext) -> Result { + (_, _) = (factor, ctx); + Ok(()) + } + + fn prepare(self) -> Result { + Ok(Default::default()) + } +} + /// Implemented by `#[derive(SpinFactors)]` pub trait SpinFactors: Sized { type InstancePreparers; @@ -179,37 +215,3 @@ impl Clone for Getter2 { } } impl Copy for Getter2 {} - -pub trait InstancePreparer: Sized { - fn new(_factor: &T, _ctx: PrepareContext) -> Result; - - fn prepare(self) -> Result; -} - -pub struct PrepareContext<'a, Factors: SpinFactors> { - instance_preparers: &'a mut Factors::InstancePreparers, - // TODO: component: &'a AppComponent, -} - -impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { - #[doc(hidden)] - pub fn new(instance_preparers: &'a mut Factors::InstancePreparers) -> Self { - Self { instance_preparers } - } -} - -pub type DefaultInstancePreparer = (); - -impl InstancePreparer for DefaultInstancePreparer -where - T::InstanceState: Default, -{ - fn new(factor: &T, ctx: PrepareContext) -> Result { - (_, _) = (factor, ctx); - Ok(()) - } - - fn prepare(self) -> Result { - Ok(Default::default()) - } -} From 69305076d4790dceeb2e873b0e0092b158442afc Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Thu, 23 May 2024 08:55:16 -0400 Subject: [PATCH 05/22] factors: Merge InitContext and ModuleInitContext Signed-off-by: Lann Martin --- crates/factor-wasi/src/preview1.rs | 9 ++--- crates/factors-derive/src/lib.rs | 23 ++--------- crates/factors/src/lib.rs | 63 +++++++++++++++++++----------- crates/factors/tests/smoke.rs | 14 ++++++- 4 files changed, 60 insertions(+), 49 deletions(-) diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs index 1909cf35c8..c1b61e1f1e 100644 --- a/crates/factor-wasi/src/preview1.rs +++ b/crates/factor-wasi/src/preview1.rs @@ -1,5 +1,5 @@ use spin_factors::{ - Factor, FactorInstancePreparer, ModuleInitContext, PrepareContext, Result, SpinFactors, + Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors, }; use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder}; @@ -9,11 +9,8 @@ impl Factor for WasiPreview1Factor { type InstancePreparer = InstancePreparer; type InstanceState = WasiP1Ctx; - fn module_init( - &mut self, - mut ctx: ModuleInitContext, - ) -> Result<()> { - ctx.link_bindings(wasmtime_wasi::preview1::add_to_linker_async) + fn init(&mut self, mut ctx: InitContext) -> Result<()> { + ctx.link_module_bindings(wasmtime_wasi::preview1::add_to_linker_async) } } diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index d3126050b9..d4939da2b8 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -63,30 +63,15 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { impl #name { pub fn init( &mut self, - linker: &mut #wasmtime::component::Linker<#state_name> + mut linker: Option<&mut #wasmtime::component::Linker<#state_name>>, + mut module_linker: Option<&mut #wasmtime::Linker<#state_name>>, ) -> #Result<()> { #( #Factor::init::( &mut self.#factor_names, #factors_path::InitContext::::new( - linker, - |state| &mut state.#factor_names, - ) - )?; - )* - Ok(()) - } - - #[allow(dead_code)] - pub fn module_init( - &mut self, - linker: &mut #wasmtime::Linker<#state_name> - ) -> #Result<()> { - #( - #Factor::module_init::( - &mut self.#factor_names, - #factors_path::ModuleInitContext::::new( - linker, + linker.as_deref_mut(), + module_linker.as_deref_mut(), |state| &mut state.#factor_names, ) )?; diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index d42de76f86..9f9b9ea7bd 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -21,14 +21,6 @@ pub trait Factor: Any + Sized { Ok(()) } - fn module_init( - &mut self, - mut ctx: ModuleInitContext, - ) -> Result<()> { - _ = &mut ctx; - Ok(()) - } - fn validate_app(&self, app: &App) -> Result<()> { _ = app; Ok(()) @@ -38,24 +30,32 @@ pub trait Factor: Any + Sized { type GetDataFn = fn(&mut ::InstanceState) -> &mut ::InstanceState; -pub struct FactorInitContext<'a, Factors: SpinFactors, Fact: Factor, Linker> { - linker: &'a mut Linker, +pub struct InitContext<'a, Factors: SpinFactors, Fact: Factor> { + linker: Option<&'a mut Linker>, + module_linker: Option<&'a mut ModuleLinker>, get_data: GetDataFn, } -pub type InitContext<'a, Factors, Fact> = FactorInitContext<'a, Factors, Fact, Linker>; - -pub type ModuleInitContext<'a, Factors, Fact> = - FactorInitContext<'a, Factors, Fact, ModuleLinker>; - -impl<'a, Factors: SpinFactors, Fact: Factor, Linker> FactorInitContext<'a, Factors, Fact, Linker> { +impl<'a, Factors: SpinFactors, Fact: Factor> InitContext<'a, Factors, Fact> { #[doc(hidden)] - pub fn new(linker: &'a mut Linker, get_data: GetDataFn) -> Self { - Self { linker, get_data } + pub fn new( + linker: Option<&'a mut Linker>, + module_linker: Option<&'a mut ModuleLinker>, + get_data: GetDataFn, + ) -> Self { + Self { + linker, + module_linker, + get_data, + } + } + + pub fn linker(&mut self) -> Option<&mut Linker> { + self.linker.as_deref_mut() } - pub fn linker(&mut self) -> &mut Linker { - self.linker + pub fn module_linker(&mut self) -> Option<&mut ModuleLinker> { + self.module_linker.as_deref_mut() } pub fn get_data_fn(&self) -> GetDataFn { @@ -65,12 +65,31 @@ impl<'a, Factors: SpinFactors, Fact: Factor, Linker> FactorInitContext<'a, Facto pub fn link_bindings( &mut self, add_to_linker: impl Fn( - &mut Linker, + &mut Linker, fn(&mut Factors::InstanceState) -> &mut Fact::InstanceState, ) -> Result<()>, ) -> Result<()> where { - add_to_linker(self.linker, self.get_data) + if let Some(linker) = self.linker.as_deref_mut() { + add_to_linker(linker, self.get_data) + } else { + Ok(()) + } + } + + pub fn link_module_bindings( + &mut self, + add_to_linker: impl Fn( + &mut ModuleLinker, + fn(&mut Factors::InstanceState) -> &mut Fact::InstanceState, + ) -> Result<()>, + ) -> Result<()> +where { + if let Some(linker) = self.module_linker.as_deref_mut() { + add_to_linker(linker, self.get_data) + } else { + Ok(()) + } } } diff --git a/crates/factors/tests/smoke.rs b/crates/factors/tests/smoke.rs index 2c81173065..12e67fba00 100644 --- a/crates/factors/tests/smoke.rs +++ b/crates/factors/tests/smoke.rs @@ -1,25 +1,35 @@ -use spin_factor_wasi::WasiFactor; +use spin_factor_wasi::{preview1::WasiPreview1Factor, WasiFactor}; use spin_factors::SpinFactors; #[derive(SpinFactors)] struct Factors { wasi: WasiFactor, + wasip1: WasiPreview1Factor, } fn main() -> anyhow::Result<()> { let engine = wasmtime::Engine::default(); let mut linker = wasmtime::component::Linker::new(&engine); + let mut module_linker = wasmtime::Linker::new(&engine); let mut factors = Factors { wasi: WasiFactor, + wasip1: WasiPreview1Factor, // outbound_networking_factor: OutboundNetworkingFactor, // outbound_http_factor: OutboundHttpFactor, }; - factors.init(&mut linker).unwrap(); + factors + .init(Some(&mut linker), Some(&mut module_linker)) + .unwrap(); let data = factors.build_store_data().unwrap(); let mut store = wasmtime::Store::new(&engine, data); + let component = wasmtime::component::Component::new(&engine, b"(component)").unwrap(); let _instance = linker.instantiate(&mut store, &component).unwrap(); + + let module = wasmtime::Module::new(&engine, b"(module)").unwrap(); + let _module_instance = module_linker.instantiate(&mut store, &module).unwrap(); + Ok(()) } From 2f580a9776f80c9a4f93cc4c6ce6759b00c09575 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Thu, 23 May 2024 15:28:48 -0400 Subject: [PATCH 06/22] factors: Add comments Signed-off-by: Lann Martin --- crates/factors/src/lib.rs | 23 ++++++++++++++++++++++- crates/factors/tests/smoke.rs | 9 +++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index 9f9b9ea7bd..33a031e811 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -12,15 +12,22 @@ pub type Linker = wasmtime::component::Linker<: pub type ModuleLinker = wasmtime::Linker<::InstanceState>; pub trait Factor: Any + Sized { + /// The [`FactorInstancePreparer`] for this factor. type InstancePreparer: FactorInstancePreparer; + + /// The per-instance state for this factor, constructed by a + /// [`FactorInstancePreparer`] and available to any host-provided imports + /// defined by this factor. type InstanceState; - /// Initializes this Factor for a runtime. This will be called exactly once + /// Initializes this Factor for a runtime. This should be called at most once. fn init(&mut self, mut ctx: InitContext) -> Result<()> { _ = &mut ctx; Ok(()) } + /// Performs factor-specific validation of the given [`App`]`. This may be + /// called before, after, or instead of `init`. fn validate_app(&self, app: &App) -> Result<()> { _ = app; Ok(()) @@ -30,6 +37,8 @@ pub trait Factor: Any + Sized { type GetDataFn = fn(&mut ::InstanceState) -> &mut ::InstanceState; +/// An InitContext is passed to [`Factor::init`], giving access to the global +/// common [`wasmtime::component::Linker`]. pub struct InitContext<'a, Factors: SpinFactors, Fact: Factor> { linker: Option<&'a mut Linker>, module_linker: Option<&'a mut ModuleLinker>, @@ -94,11 +103,16 @@ where { } pub trait FactorInstancePreparer: Sized { + /// Returns a new instance of this preparer for the given [`Factor`]. fn new(factor: &T, _ctx: PrepareContext) -> Result; + /// Returns a new instance of the associated [`Factor::InstanceState`]. fn prepare(self) -> Result; } +/// A PrepareContext is passed to [`FactorInstancePreparer::new`], giving access +/// to any already-initialized [`FactorInstancePreparer`]s, allowing for +/// inter-[`Factor`] dependencies. pub struct PrepareContext<'a, Factors: SpinFactors> { instance_preparers: &'a mut Factors::InstancePreparers, // TODO: component: &'a AppComponent, @@ -110,6 +124,11 @@ impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { Self { instance_preparers } } + /// Returns a already-initialized preparer for the given [`Factor`]. + /// + /// Fails if the current [`SpinFactors`] does not include the given + /// [`Factor`] or if the given [`Factor`]'s preparer has not been + /// initialized yet (because it is sequenced after this factor). pub fn instance_preparer_mut(&mut self) -> Result<&mut T::InstancePreparer> { let err_msg = match Factors::instance_preparer_mut::(self.instance_preparers) { Some(Some(preparer)) => return Ok(preparer), @@ -123,6 +142,8 @@ impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { } } +/// DefaultInstancePreparer can be used as a [`FactorInstancePreparer`] to +/// produce a [`Default`] [`Factor::InstanceState`]. pub type DefaultInstancePreparer = (); impl FactorInstancePreparer for DefaultInstancePreparer diff --git a/crates/factors/tests/smoke.rs b/crates/factors/tests/smoke.rs index 12e67fba00..5cb72dd1d0 100644 --- a/crates/factors/tests/smoke.rs +++ b/crates/factors/tests/smoke.rs @@ -8,16 +8,17 @@ struct Factors { } fn main() -> anyhow::Result<()> { - let engine = wasmtime::Engine::default(); - let mut linker = wasmtime::component::Linker::new(&engine); - let mut module_linker = wasmtime::Linker::new(&engine); - let mut factors = Factors { wasi: WasiFactor, wasip1: WasiPreview1Factor, // outbound_networking_factor: OutboundNetworkingFactor, // outbound_http_factor: OutboundHttpFactor, }; + + let engine = wasmtime::Engine::default(); + let mut linker = wasmtime::component::Linker::new(&engine); + let mut module_linker = wasmtime::Linker::new(&engine); + factors .init(Some(&mut linker), Some(&mut module_linker)) .unwrap(); From d0ea626b7f5e1f643db898e47a4f522c668f5b04 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Fri, 24 May 2024 16:34:45 -0400 Subject: [PATCH 07/22] factors: Add configure_app hook Signed-off-by: Lann Martin --- Cargo.lock | 14 +++ crates/app/src/lib.rs | 8 ++ crates/factor-outbound-networking/Cargo.toml | 16 +++ crates/factor-outbound-networking/src/lib.rs | 86 +++++++++++++ crates/factor-wasi/Cargo.toml | 3 +- crates/factor-wasi/src/lib.rs | 106 +++++++++++++++- crates/factor-wasi/src/preview1.rs | 4 +- crates/factors-derive/src/lib.rs | 45 ++++++- crates/factors/Cargo.toml | 2 + crates/factors/src/lib.rs | 126 ++++++++++++++----- crates/factors/tests/smoke.rs | 27 +++- 11 files changed, 393 insertions(+), 44 deletions(-) create mode 100644 crates/factor-outbound-networking/Cargo.toml create mode 100644 crates/factor-outbound-networking/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 4159011a46..b5a3d5c012 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7558,11 +7558,23 @@ dependencies = [ "toml 0.5.11", ] +[[package]] +name = "spin-factor-outbound-networking" +version = "2.6.0-pre0" +dependencies = [ + "anyhow", + "ipnet", + "spin-factor-wasi", + "spin-factors", + "spin-outbound-networking", +] + [[package]] name = "spin-factor-wasi" version = "2.6.0-pre0" dependencies = [ "anyhow", + "cap-primitives 3.0.0", "spin-factors", "wasmtime-wasi", ] @@ -7572,7 +7584,9 @@ name = "spin-factors" version = "2.6.0-pre0" dependencies = [ "anyhow", + "serde_json", "spin-app", + "spin-factor-outbound-networking", "spin-factor-wasi", "spin-factors-derive", "wasmtime", diff --git a/crates/app/src/lib.rs b/crates/app/src/lib.rs index 70d6a59b6b..97077032db 100644 --- a/crates/app/src/lib.rs +++ b/crates/app/src/lib.rs @@ -296,6 +296,14 @@ impl<'a, L> AppComponent<'a, L> { &self.locked.source } + /// Returns an iterator of environment variable (key, value) pairs. + pub fn environment(&self) -> impl IntoIterator { + self.locked + .env + .iter() + .map(|(k, v)| (k.as_str(), v.as_str())) + } + /// Returns an iterator of [`ContentPath`]s for this component's configured /// "directory mounts". pub fn files(&self) -> std::slice::Iter { diff --git a/crates/factor-outbound-networking/Cargo.toml b/crates/factor-outbound-networking/Cargo.toml new file mode 100644 index 0000000000..d4be541729 --- /dev/null +++ b/crates/factor-outbound-networking/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "spin-factor-outbound-networking" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } + +[dependencies] +anyhow = "1" +ipnet = "2.9.0" +spin-factor-wasi = { path = "../factor-wasi" } +spin-factors = { path = "../factors" } +# TODO: merge with this crate +spin-outbound-networking = { path = "../outbound-networking" } + +[lints] +workspace = true diff --git a/crates/factor-outbound-networking/src/lib.rs b/crates/factor-outbound-networking/src/lib.rs new file mode 100644 index 0000000000..d994296a86 --- /dev/null +++ b/crates/factor-outbound-networking/src/lib.rs @@ -0,0 +1,86 @@ +use std::{collections::HashMap, sync::Arc}; + +use anyhow::Context; +use spin_factor_wasi::WasiFactor; +use spin_factors::{Factor, FactorInstancePreparer, Result, SpinFactors}; +use spin_outbound_networking::{AllowedHostsConfig, HostConfig, PortConfig, ALLOWED_HOSTS_KEY}; + +pub struct OutboundNetworkingFactor; + +impl Factor for OutboundNetworkingFactor { + type AppConfig = AppConfig; + type InstancePreparer = InstancePreparer; + type InstanceState = (); + + fn configure_app( + &self, + app: &spin_factors::App, + _ctx: spin_factors::ConfigureAppContext, + ) -> Result { + let mut cfg = AppConfig::default(); + // TODO: resolve resolver resolution + let resolver = Default::default(); + for component in app.components() { + if let Some(hosts) = component.get_metadata(ALLOWED_HOSTS_KEY)? { + let allowed_hosts = AllowedHostsConfig::parse(&hosts, &resolver)?; + cfg.component_allowed_hosts + .insert(component.id().to_string(), Arc::new(allowed_hosts)); + } + } + Ok(cfg) + } +} + +#[derive(Default)] +pub struct AppConfig { + component_allowed_hosts: HashMap>, +} + +pub struct InstancePreparer { + allowed_hosts: Arc, +} + +impl InstancePreparer { + pub fn allowed_hosts(&self) -> &Arc { + &self.allowed_hosts + } +} + +impl FactorInstancePreparer for InstancePreparer { + fn new( + _factor: &OutboundNetworkingFactor, + app_component: &spin_factors::AppComponent, + mut ctx: spin_factors::PrepareContext, + ) -> Result { + let allowed_hosts = ctx + .app_config::()? + .component_allowed_hosts + .get(app_component.id()) + .context("missing component")? + .clone(); + + // Update Wasi socket allowed ports + let wasi_preparer = ctx.instance_preparer_mut::()?; + match &*allowed_hosts { + AllowedHostsConfig::All => wasi_preparer.inherit_network(), + AllowedHostsConfig::SpecificHosts(configs) => { + for config in configs { + if config.scheme().allows_any() { + match (config.host(), config.port()) { + (HostConfig::Cidr(ip_net), PortConfig::Any) => { + wasi_preparer.socket_allow_ports(*ip_net, 0, None) + } + _ => todo!(), // TODO: complete and validate against existing Network TriggerHooks + } + } + } + } + } + + Ok(Self { allowed_hosts }) + } + + fn prepare(self) -> Result<::InstanceState> { + Ok(()) + } +} diff --git a/crates/factor-wasi/Cargo.toml b/crates/factor-wasi/Cargo.toml index c50f2e629e..58d4b5ff49 100644 --- a/crates/factor-wasi/Cargo.toml +++ b/crates/factor-wasi/Cargo.toml @@ -5,7 +5,8 @@ authors = { workspace = true } edition = { workspace = true } [dependencies] -anyhow = "1.0" +anyhow = "1" +cap-primitives = "3.0.0" spin-factors = { path = "../factors" } wasmtime-wasi = { workspace = true } diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs index c6a90f1776..03ca3c14e8 100644 --- a/crates/factor-wasi/src/lib.rs +++ b/crates/factor-wasi/src/lib.rs @@ -1,13 +1,28 @@ pub mod preview1; +use std::path::Path; + +use anyhow::ensure; +use cap_primitives::{ipnet::IpNet, net::Pool}; use spin_factors::{ - Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors, + AppComponent, Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors, }; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; -pub struct WasiFactor; +pub struct WasiFactor { + files_mounter: Box, +} + +impl WasiFactor { + pub fn new(files_mounter: impl FilesMounter + 'static) -> Self { + Self { + files_mounter: Box::new(files_mounter), + } + } +} impl Factor for WasiFactor { + type AppConfig = (); type InstancePreparer = InstancePreparer; type InstanceState = InstanceState; @@ -44,28 +59,107 @@ impl Factor for WasiFactor { } } +pub trait FilesMounter { + fn mount_files(&self, app_component: &AppComponent, ctx: MountFilesContext) -> Result<()>; +} + +pub struct DummyFilesMounter; + +impl FilesMounter for DummyFilesMounter { + fn mount_files(&self, app_component: &AppComponent, _ctx: MountFilesContext) -> Result<()> { + ensure!( + app_component.files().next().is_none(), + "DummyFilesMounter can't actually mount files" + ); + Ok(()) + } +} + +pub struct MountFilesContext<'a> { + wasi_ctx: &'a mut WasiCtxBuilder, +} + +impl<'a> MountFilesContext<'a> { + pub fn preopened_dir( + &mut self, + host_path: impl AsRef, + guest_path: impl AsRef, + writable: bool, + ) -> Result<()> { + use wasmtime_wasi::{DirPerms, FilePerms}; + let (dir_perms, file_perms) = if writable { + (DirPerms::all(), FilePerms::all()) + } else { + (DirPerms::READ, FilePerms::READ) + }; + self.wasi_ctx + .preopened_dir(host_path, guest_path, dir_perms, file_perms)?; + Ok(()) + } +} + pub struct InstancePreparer { wasi_ctx: WasiCtxBuilder, + socket_allow_ports: Pool, } impl FactorInstancePreparer for InstancePreparer { + // NOTE: Replaces WASI parts of AppComponent::apply_store_config fn new( - _factor: &WasiFactor, + factor: &WasiFactor, + app_component: &AppComponent, _ctx: PrepareContext, ) -> Result { + let mut wasi_ctx = WasiCtxBuilder::new(); + + // Apply environment variables + for (key, val) in app_component.environment() { + wasi_ctx.env(key, val); + } + + // Mount files + let mount_ctx = MountFilesContext { + wasi_ctx: &mut wasi_ctx, + }; + factor.files_mounter.mount_files(app_component, mount_ctx)?; + Ok(Self { - wasi_ctx: WasiCtxBuilder::new(), + wasi_ctx, + socket_allow_ports: Default::default(), }) } - fn prepare(mut self) -> Result { + fn prepare(self) -> Result { + let Self { + mut wasi_ctx, + socket_allow_ports, + } = self; + + // Enforce socket_allow_ports + wasi_ctx.socket_addr_check(move |addr, _| socket_allow_ports.check_addr(addr).is_ok()); + Ok(InstanceState { - ctx: self.wasi_ctx.build(), + ctx: wasi_ctx.build(), table: Default::default(), }) } } +impl InstancePreparer { + pub fn inherit_network(&mut self) { + self.wasi_ctx.inherit_network(); + } + + pub fn socket_allow_ports(&mut self, ip_net: IpNet, ports_start: u16, ports_end: Option) { + self.socket_allow_ports.insert_ip_net_port_range( + ip_net, + ports_start, + ports_end, + cap_primitives::ambient_authority(), + ); + } +} + pub struct InstanceState { ctx: WasiCtx, table: ResourceTable, diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs index c1b61e1f1e..a4441a0189 100644 --- a/crates/factor-wasi/src/preview1.rs +++ b/crates/factor-wasi/src/preview1.rs @@ -1,11 +1,12 @@ use spin_factors::{ - Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors, + AppComponent, Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors, }; use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder}; pub struct WasiPreview1Factor; impl Factor for WasiPreview1Factor { + type AppConfig = (); type InstancePreparer = InstancePreparer; type InstanceState = WasiP1Ctx; @@ -21,6 +22,7 @@ pub struct InstancePreparer { impl FactorInstancePreparer for InstancePreparer { fn new( _factor: &WasiPreview1Factor, + _app_component: &AppComponent, _ctx: PrepareContext, ) -> Result { Ok(Self { diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index d4939da2b8..456068c2c6 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -20,6 +20,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { let name = &input.ident; let vis = &input.vis; + let app_configs_name = format_ident!("{name}AppConfigs"); let preparers_name = format_ident!("{name}InstancePreparers"); let state_name = format_ident!("{name}InstanceState"); @@ -57,6 +58,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { let Factor = quote!(#factors_path::Factor); let Result = quote!(#factors_path::Result); let wasmtime = quote!(#factors_path::wasmtime); + let ConfiguredApp = quote!(#factors_path::ConfiguredApp); let TypeId = quote!(::std::any::TypeId); Ok(quote! { @@ -79,7 +81,26 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { Ok(()) } - pub fn build_store_data(&self) -> #Result<#state_name> { + pub fn configure_app(&self, app: #factors_path::App) -> #Result<#ConfiguredApp> { + let mut app_configs = #app_configs_name { + #( #factor_names: None, )* + }; + #( + app_configs.#factor_names = Some( + #Factor::configure_app( + &self.#factor_names, + &app, + #factors_path::ConfigureAppContext::::new(&app_configs), + )? + ); + )* + Ok(#ConfiguredApp::new(app, app_configs)) + } + + pub fn build_store_data(&self, configured_app: &#ConfiguredApp, component_id: &str) -> #Result<#state_name> { + let app_component = configured_app.app().get_component(component_id).ok_or_else(|| { + #factors_path::Error::msg(format!("unknown component {component_id:?}")) + })?; let mut preparers = #preparers_name { #( #factor_names: None, )* }; @@ -87,14 +108,15 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { preparers.#factor_names = Some( #factors_path::FactorInstancePreparer::<#factor_types>::new::<#name>( &self.#factor_names, - #factors_path::PrepareContext::new(&mut preparers), + &app_component, + #factors_path::PrepareContext::new(configured_app, &mut preparers), )? ); )* Ok(#state_name { #( #factor_names: #factors_path::FactorInstancePreparer::<#factor_types>::prepare( - preparers.#factor_names.unwrap() + preparers.#factor_names.unwrap(), )?, )* }) @@ -103,6 +125,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { } impl #factors_path::SpinFactors for #name { + type AppConfigs = #app_configs_name; type InstancePreparers = #preparers_name; type InstanceState = #state_name; @@ -126,6 +149,22 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { None } + + fn app_config(app_configs: &Self::AppConfigs) -> Option<&T::AppConfig> { + let type_id = #TypeId::of::(); + #( + if type_id == #TypeId::of::<#factor_types>() { + return Some(unsafe { std::mem::transmute(&app_configs.#factor_names) }); + } + )* + None + } + } + + #vis struct #app_configs_name { + #( + pub #factor_names: Option<<#factor_types as #Factor>::AppConfig>, + )* } #vis struct #preparers_name { diff --git a/crates/factors/Cargo.toml b/crates/factors/Cargo.toml index 6e3e75d819..779b95ba08 100644 --- a/crates/factors/Cargo.toml +++ b/crates/factors/Cargo.toml @@ -11,7 +11,9 @@ spin-factors-derive = { path = "../factors-derive" } wasmtime = { workspace = true } [dev-dependencies] +serde_json = "1.0" spin-factors-derive = { path = "../factors-derive", features = ["expander"] } +spin-factor-outbound-networking = { path = "../factor-outbound-networking" } spin-factor-wasi = { path = "../factor-wasi" } [lints] diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index 33a031e811..d917c392ea 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -1,6 +1,6 @@ use std::{any::Any, marker::PhantomData}; -use spin_app::App; +use anyhow::Context; pub use spin_factors_derive::SpinFactors; pub use wasmtime; @@ -11,7 +11,16 @@ pub type Result = std::result::Result; pub type Linker = wasmtime::component::Linker<::InstanceState>; pub type ModuleLinker = wasmtime::Linker<::InstanceState>; +// Temporary wrappers while refactoring +pub type App = spin_app::App<'static, spin_app::InertLoader>; +pub type AppComponent<'a> = spin_app::AppComponent<'a, spin_app::InertLoader>; + pub trait Factor: Any + Sized { + /// App configuration for this factor. + /// + /// See [`Factor::configure_app`]. + type AppConfig: Default; + /// The [`FactorInstancePreparer`] for this factor. type InstancePreparer: FactorInstancePreparer; @@ -20,17 +29,25 @@ pub trait Factor: Any + Sized { /// defined by this factor. type InstanceState; - /// Initializes this Factor for a runtime. This should be called at most once. + /// Initializes this Factor for a runtime. This will be called at most once, + /// before any call to [`FactorInstancePreparer::new`] fn init(&mut self, mut ctx: InitContext) -> Result<()> { _ = &mut ctx; Ok(()) } - /// Performs factor-specific validation of the given [`App`]`. This may be - /// called before, after, or instead of `init`. - fn validate_app(&self, app: &App) -> Result<()> { + /// Performs factor-specific validation and configuration for the given + /// [`App`] and [`RuntimeConfig`]. A runtime may - but is not required to - + /// reuse the returned config across multiple instances. Note that this may + /// be called without any call to `init` in cases where only validation is + /// needed. + fn configure_app( + &self, + app: &App, + _ctx: ConfigureAppContext, + ) -> Result { _ = app; - Ok(()) + Ok(Default::default()) } } @@ -102,9 +119,28 @@ where { } } +pub struct ConfigureAppContext<'a, Factors: SpinFactors> { + app_configs: &'a Factors::AppConfigs, +} + +impl<'a, Factors: SpinFactors> ConfigureAppContext<'a, Factors> { + #[doc(hidden)] + pub fn new(app_configs: &'a Factors::AppConfigs) -> Self { + Self { app_configs } + } + + pub fn app_config(&self) -> Result<&T::AppConfig> { + Factors::app_config::(self.app_configs).context("no such factor") + } +} + pub trait FactorInstancePreparer: Sized { /// Returns a new instance of this preparer for the given [`Factor`]. - fn new(factor: &T, _ctx: PrepareContext) -> Result; + fn new( + factor: &T, + app_component: &AppComponent, + _ctx: PrepareContext, + ) -> Result; /// Returns a new instance of the associated [`Factor::InstanceState`]. fn prepare(self) -> Result; @@ -114,14 +150,24 @@ pub trait FactorInstancePreparer: Sized { /// to any already-initialized [`FactorInstancePreparer`]s, allowing for /// inter-[`Factor`] dependencies. pub struct PrepareContext<'a, Factors: SpinFactors> { + configured_app: &'a ConfiguredApp, instance_preparers: &'a mut Factors::InstancePreparers, - // TODO: component: &'a AppComponent, } impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { #[doc(hidden)] - pub fn new(instance_preparers: &'a mut Factors::InstancePreparers) -> Self { - Self { instance_preparers } + pub fn new( + configured_app: &'a ConfiguredApp, + instance_preparers: &'a mut Factors::InstancePreparers, + ) -> Self { + Self { + configured_app, + instance_preparers, + } + } + + pub fn app_config(&self) -> Result<&T::AppConfig> { + self.configured_app.app_config::() } /// Returns a already-initialized preparer for the given [`Factor`]. @@ -130,28 +176,27 @@ impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { /// [`Factor`] or if the given [`Factor`]'s preparer has not been /// initialized yet (because it is sequenced after this factor). pub fn instance_preparer_mut(&mut self) -> Result<&mut T::InstancePreparer> { - let err_msg = match Factors::instance_preparer_mut::(self.instance_preparers) { - Some(Some(preparer)) => return Ok(preparer), - Some(None) => "preparer not yet initialized", - None => "no such factor", - }; - Err(Error::msg(format!( - "could not get instance preparer for {ty}: {err_msg}", - ty = std::any::type_name::() - ))) + Factors::instance_preparer_mut::(self.instance_preparers) + .and_then(|maybe_preparer| maybe_preparer.context("preparer not yet initialized")) + .with_context(|| { + format!( + "could not get instance preparer for {}", + std::any::type_name::() + ) + }) } } -/// DefaultInstancePreparer can be used as a [`FactorInstancePreparer`] to -/// produce a [`Default`] [`Factor::InstanceState`]. -pub type DefaultInstancePreparer = (); - -impl FactorInstancePreparer for DefaultInstancePreparer +impl FactorInstancePreparer for () where T::InstanceState: Default, { - fn new(factor: &T, ctx: PrepareContext) -> Result { - (_, _) = (factor, ctx); + fn new( + factor: &T, + app_component: &AppComponent, + _ctx: PrepareContext, + ) -> Result { + (_, _) = (factor, app_component); Ok(()) } @@ -160,8 +205,29 @@ where } } +pub struct ConfiguredApp { + app: App, + app_configs: Factors::AppConfigs, +} + +impl ConfiguredApp { + #[doc(hidden)] + pub fn new(app: App, app_configs: Factors::AppConfigs) -> Self { + Self { app, app_configs } + } + + pub fn app(&self) -> &App { + &self.app + } + + pub fn app_config(&self) -> Result<&T::AppConfig> { + Factors::app_config::(&self.app_configs).context("no such factor") + } +} + /// Implemented by `#[derive(SpinFactors)]` pub trait SpinFactors: Sized { + type AppConfigs; type InstancePreparers; type InstanceState: Send + 'static; @@ -171,6 +237,8 @@ pub trait SpinFactors: Sized { #[doc(hidden)] unsafe fn instance_state_offset() -> Option; + fn app_config(app_configs: &Self::AppConfigs) -> Option<&T::AppConfig>; + fn instance_state_getter() -> Option> { let offset = unsafe { Self::instance_state_offset::()? }; Some(Getter { @@ -196,12 +264,12 @@ pub trait SpinFactors: Sized { fn instance_preparer_mut( preparers: &mut Self::InstancePreparers, - ) -> Option> { + ) -> Result> { unsafe { - let offset = Self::instance_preparer_offset::()?; + let offset = Self::instance_preparer_offset::().context("no such factor")?; let ptr = preparers as *mut Self::InstancePreparers; let opt = &mut *ptr.add(offset).cast::>(); - Some(opt.as_mut()) + Ok(opt.as_mut()) } } } diff --git a/crates/factors/tests/smoke.rs b/crates/factors/tests/smoke.rs index 5cb72dd1d0..e1f39f2c59 100644 --- a/crates/factors/tests/smoke.rs +++ b/crates/factors/tests/smoke.rs @@ -1,20 +1,37 @@ -use spin_factor_wasi::{preview1::WasiPreview1Factor, WasiFactor}; +use spin_app::App; +use spin_factor_outbound_networking::OutboundNetworkingFactor; +use spin_factor_wasi::{preview1::WasiPreview1Factor, DummyFilesMounter, WasiFactor}; use spin_factors::SpinFactors; #[derive(SpinFactors)] struct Factors { wasi: WasiFactor, wasip1: WasiPreview1Factor, + outbound_networking_factor: OutboundNetworkingFactor, } fn main() -> anyhow::Result<()> { let mut factors = Factors { - wasi: WasiFactor, + wasi: WasiFactor::new(DummyFilesMounter), wasip1: WasiPreview1Factor, - // outbound_networking_factor: OutboundNetworkingFactor, + outbound_networking_factor: OutboundNetworkingFactor, // outbound_http_factor: OutboundHttpFactor, }; + let locked = serde_json::from_value(serde_json::json!({ + "spin_locked_version": 1, + "triggers": [], + "components": [{ + "id": "test", + "source": { + "content_type": "application/wasm", + "content": {"inline": "KGNvbXBvbmVudCk="} + } + }] + })) + .unwrap(); + let app = App::inert(locked); + let engine = wasmtime::Engine::default(); let mut linker = wasmtime::component::Linker::new(&engine); let mut module_linker = wasmtime::Linker::new(&engine); @@ -22,7 +39,9 @@ fn main() -> anyhow::Result<()> { factors .init(Some(&mut linker), Some(&mut module_linker)) .unwrap(); - let data = factors.build_store_data().unwrap(); + + let configured_app = factors.configure_app(app).unwrap(); + let data = factors.build_store_data(&configured_app, "test").unwrap(); let mut store = wasmtime::Store::new(&engine, data); From a081459f70911514d2625ef94749ee955ae3722a Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Fri, 31 May 2024 10:43:52 -0400 Subject: [PATCH 08/22] VariablesFactor wip Signed-off-by: Lann Martin --- Cargo.lock | 1 + crates/factor-outbound-networking/Cargo.toml | 3 + crates/factor-outbound-networking/src/lib.rs | 118 ++++++++++------- crates/factor-variables/Cargo.toml | 14 +++ crates/factor-variables/src/lib.rs | 125 ++++++++++++++++++ crates/factor-wasi/src/lib.rs | 126 ++++++++++--------- crates/factor-wasi/src/preview1.rs | 7 +- crates/factors-derive/src/lib.rs | 9 +- crates/factors/Cargo.toml | 1 + crates/factors/src/lib.rs | 106 +++++++++------- crates/factors/tests/smoke.rs | 3 + crates/world/Cargo.toml | 1 + crates/world/src/lib.rs | 2 + 13 files changed, 361 insertions(+), 155 deletions(-) create mode 100644 crates/factor-variables/Cargo.toml create mode 100644 crates/factor-variables/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index b5a3d5c012..032f8dcc51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7567,6 +7567,7 @@ dependencies = [ "spin-factor-wasi", "spin-factors", "spin-outbound-networking", + "tracing", ] [[package]] diff --git a/crates/factor-outbound-networking/Cargo.toml b/crates/factor-outbound-networking/Cargo.toml index d4be541729..8eddb55eb3 100644 --- a/crates/factor-outbound-networking/Cargo.toml +++ b/crates/factor-outbound-networking/Cargo.toml @@ -6,11 +6,14 @@ edition = { workspace = true } [dependencies] anyhow = "1" +futures-util = "0.3" ipnet = "2.9.0" +spin-factor-variables = { path = "../factor-variables" } spin-factor-wasi = { path = "../factor-wasi" } spin-factors = { path = "../factors" } # TODO: merge with this crate spin-outbound-networking = { path = "../outbound-networking" } +tracing = { workspace = true } [lints] workspace = true diff --git a/crates/factor-outbound-networking/src/lib.rs b/crates/factor-outbound-networking/src/lib.rs index d994296a86..39013b8061 100644 --- a/crates/factor-outbound-networking/src/lib.rs +++ b/crates/factor-outbound-networking/src/lib.rs @@ -1,9 +1,16 @@ use std::{collections::HashMap, sync::Arc}; use anyhow::Context; +use futures_util::{ + future::{BoxFuture, Shared}, + FutureExt, +}; +use spin_factor_variables::VariablesFactor; use spin_factor_wasi::WasiFactor; -use spin_factors::{Factor, FactorInstancePreparer, Result, SpinFactors}; -use spin_outbound_networking::{AllowedHostsConfig, HostConfig, PortConfig, ALLOWED_HOSTS_KEY}; +use spin_factors::{ + Factor, FactorInstancePreparer, InstancePreparers, PrepareContext, Result, SpinFactors, +}; +use spin_outbound_networking::{AllowedHostsConfig, ALLOWED_HOSTS_KEY}; pub struct OutboundNetworkingFactor; @@ -17,70 +24,93 @@ impl Factor for OutboundNetworkingFactor { app: &spin_factors::App, _ctx: spin_factors::ConfigureAppContext, ) -> Result { - let mut cfg = AppConfig::default(); - // TODO: resolve resolver resolution - let resolver = Default::default(); - for component in app.components() { - if let Some(hosts) = component.get_metadata(ALLOWED_HOSTS_KEY)? { - let allowed_hosts = AllowedHostsConfig::parse(&hosts, &resolver)?; - cfg.component_allowed_hosts - .insert(component.id().to_string(), Arc::new(allowed_hosts)); - } - } - Ok(cfg) + // Extract allowed_outbound_hosts for all components + let component_allowed_hosts = app + .components() + .map(|component| { + Ok(( + component.id().to_string(), + component + .get_metadata(ALLOWED_HOSTS_KEY)? + .unwrap_or_default() + .into_boxed_slice() + .into(), + )) + }) + .collect::>()?; + Ok(AppConfig { + component_allowed_hosts, + }) } } #[derive(Default)] pub struct AppConfig { - component_allowed_hosts: HashMap>, + component_allowed_hosts: HashMap>, } -pub struct InstancePreparer { - allowed_hosts: Arc, -} +type AllowedHostsFuture = Shared>>>; -impl InstancePreparer { - pub fn allowed_hosts(&self) -> &Arc { - &self.allowed_hosts - } +pub struct InstancePreparer { + allowed_hosts_future: AllowedHostsFuture, } impl FactorInstancePreparer for InstancePreparer { fn new( - _factor: &OutboundNetworkingFactor, - app_component: &spin_factors::AppComponent, - mut ctx: spin_factors::PrepareContext, + ctx: PrepareContext, + mut preparers: InstancePreparers, ) -> Result { - let allowed_hosts = ctx - .app_config::()? + let hosts = ctx + .app_config() .component_allowed_hosts - .get(app_component.id()) - .context("missing component")? - .clone(); + .get(ctx.app_component().id()) + .cloned() + .context("missing component allowed hosts")?; + let resolver = preparers.get_mut::()?.resolver().clone(); + let allowed_hosts_future = async move { + let prepared = resolver.prepare().await?; + AllowedHostsConfig::parse(&hosts, &prepared) + } + .map(Arc::new) + .boxed() + .shared(); + // let prepared_resolver = resolver.prepare().await?; + // let allowed_hosts = AllowedHostsConfig::parse( + // .context("missing component allowed hosts")?, + // &prepared_resolver, + // )?; // Update Wasi socket allowed ports - let wasi_preparer = ctx.instance_preparer_mut::()?; - match &*allowed_hosts { - AllowedHostsConfig::All => wasi_preparer.inherit_network(), - AllowedHostsConfig::SpecificHosts(configs) => { - for config in configs { - if config.scheme().allows_any() { - match (config.host(), config.port()) { - (HostConfig::Cidr(ip_net), PortConfig::Any) => { - wasi_preparer.socket_allow_ports(*ip_net, 0, None) - } - _ => todo!(), // TODO: complete and validate against existing Network TriggerHooks - } + let wasi_preparer = preparers.get_mut::()?; + let hosts_future = allowed_hosts_future.clone(); + wasi_preparer.outbound_socket_addr_check(move |addr| { + let hosts_future = hosts_future.clone(); + async move { + match &*hosts_future.await { + Ok(allowed_hosts) => { + // TODO: verify this actually works... + spin_outbound_networking::check_url(&addr.to_string(), "*", allowed_hosts) + } + Err(err) => { + // TODO: should this trap (somehow)? + tracing::error!(%err, "allowed_outbound_hosts variable resolution failed"); + false } } } - } - - Ok(Self { allowed_hosts }) + }); + Ok(Self { + allowed_hosts_future, + }) } fn prepare(self) -> Result<::InstanceState> { Ok(()) } } + +impl InstancePreparer { + pub async fn resolve_allowed_hosts(&self) -> Arc> { + self.allowed_hosts_future.clone().await + } +} diff --git a/crates/factor-variables/Cargo.toml b/crates/factor-variables/Cargo.toml new file mode 100644 index 0000000000..27ab8498bd --- /dev/null +++ b/crates/factor-variables/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "spin-factor-variables" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } + +[dependencies] +anyhow = "1" +spin-expressions = { path = "../expressions" } +spin-factors = { path = "../factors" } +spin-world = { path = "../world" } + +[lints] +workspace = true diff --git a/crates/factor-variables/src/lib.rs b/crates/factor-variables/src/lib.rs new file mode 100644 index 0000000000..360956aad2 --- /dev/null +++ b/crates/factor-variables/src/lib.rs @@ -0,0 +1,125 @@ +use std::sync::Arc; + +use spin_expressions::ProviderResolver; +use spin_factors::{ + Factor, FactorInstancePreparer, InstancePreparers, PrepareContext, Result, SpinFactors, +}; +use spin_world::{async_trait, v1::config as v1_config, v2::variables}; + +pub struct VariablesFactor; + +impl Factor for VariablesFactor { + type AppConfig = AppConfig; + type InstancePreparer = InstancePreparer; + type InstanceState = InstanceState; + + fn init( + &mut self, + mut ctx: spin_factors::InitContext, + ) -> Result<()> { + ctx.link_bindings(v1_config::add_to_linker)?; + ctx.link_bindings(variables::add_to_linker)?; + Ok(()) + } + + fn configure_app( + &self, + app: &spin_factors::App, + _ctx: spin_factors::ConfigureAppContext, + ) -> Result { + let mut resolver = + ProviderResolver::new(app.variables().map(|(key, val)| (key.clone(), val.clone())))?; + for component in app.components() { + resolver.add_component_variables( + component.id(), + component.config().map(|(k, v)| (k.into(), v.into())), + )?; + } + // TODO: add providers from runtime config + Ok(AppConfig { + resolver: Arc::new(resolver), + }) + } +} + +#[derive(Default)] +pub struct AppConfig { + resolver: Arc, +} + +pub struct InstancePreparer { + state: InstanceState, +} + +impl InstancePreparer { + pub fn resolver(&self) -> &Arc { + &self.state.resolver + } +} + +impl FactorInstancePreparer for InstancePreparer { + fn new( + ctx: PrepareContext, + _preparers: InstancePreparers, + ) -> Result { + let component_id = ctx.app_component().id().to_string(); + let resolver = ctx.app_config().resolver.clone(); + Ok(Self { + state: InstanceState { + component_id, + resolver, + }, + }) + } + + fn prepare(self) -> Result<::InstanceState> { + Ok(self.state) + } +} + +pub struct InstanceState { + component_id: String, + resolver: Arc, +} + +#[async_trait] +impl variables::Host for InstanceState { + async fn get(&mut self, key: String) -> Result { + let key = spin_expressions::Key::new(&key).map_err(expressions_to_variables_err)?; + self.resolver + .resolve(&self.component_id, key) + .await + .map_err(expressions_to_variables_err) + } + + fn convert_error(&mut self, error: variables::Error) -> Result { + Ok(error) + } +} + +#[async_trait] +impl v1_config::Host for InstanceState { + async fn get_config(&mut self, key: String) -> Result { + ::get(self, key) + .await + .map_err(|err| match err { + variables::Error::InvalidName(msg) => v1_config::Error::InvalidKey(msg), + variables::Error::Undefined(msg) => v1_config::Error::Provider(msg), + other => v1_config::Error::Other(format!("{other}")), + }) + } + + fn convert_error(&mut self, err: v1_config::Error) -> anyhow::Result { + Ok(err) + } +} + +fn expressions_to_variables_err(err: spin_expressions::Error) -> variables::Error { + use spin_expressions::Error; + match err { + Error::InvalidName(msg) => variables::Error::InvalidName(msg), + Error::Undefined(msg) => variables::Error::Undefined(msg), + Error::Provider(err) => variables::Error::Provider(err.to_string()), + other => variables::Error::Other(format!("{other}")), + } +} diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs index 03ca3c14e8..1bc01f3760 100644 --- a/crates/factor-wasi/src/lib.rs +++ b/crates/factor-wasi/src/lib.rs @@ -1,11 +1,11 @@ pub mod preview1; -use std::path::Path; +use std::{future::Future, net::SocketAddr, path::Path}; use anyhow::ensure; -use cap_primitives::{ipnet::IpNet, net::Pool}; use spin_factors::{ - AppComponent, Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors, + AppComponent, Factor, FactorInstancePreparer, InitContext, InstancePreparers, PrepareContext, + Result, SpinFactors, }; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; @@ -27,34 +27,44 @@ impl Factor for WasiFactor { type InstanceState = InstanceState; fn init(&mut self, mut ctx: InitContext) -> Result<()> { - use wasmtime_wasi::bindings; - ctx.link_bindings(bindings::clocks::wall_clock::add_to_linker_get_host)?; - ctx.link_bindings(bindings::clocks::monotonic_clock::add_to_linker_get_host)?; - ctx.link_bindings(bindings::filesystem::types::add_to_linker_get_host)?; - ctx.link_bindings(bindings::filesystem::preopens::add_to_linker_get_host)?; - ctx.link_bindings(bindings::io::error::add_to_linker_get_host)?; - ctx.link_bindings(bindings::io::poll::add_to_linker_get_host)?; - ctx.link_bindings(bindings::io::streams::add_to_linker_get_host)?; - ctx.link_bindings(bindings::random::random::add_to_linker_get_host)?; - ctx.link_bindings(bindings::random::insecure::add_to_linker_get_host)?; - ctx.link_bindings(bindings::random::insecure_seed::add_to_linker_get_host)?; - ctx.link_bindings(bindings::cli::exit::add_to_linker_get_host)?; - ctx.link_bindings(bindings::cli::environment::add_to_linker_get_host)?; - ctx.link_bindings(bindings::cli::stdin::add_to_linker_get_host)?; - ctx.link_bindings(bindings::cli::stdout::add_to_linker_get_host)?; - ctx.link_bindings(bindings::cli::stderr::add_to_linker_get_host)?; - ctx.link_bindings(bindings::cli::terminal_input::add_to_linker_get_host)?; - ctx.link_bindings(bindings::cli::terminal_output::add_to_linker_get_host)?; - ctx.link_bindings(bindings::cli::terminal_stdin::add_to_linker_get_host)?; - ctx.link_bindings(bindings::cli::terminal_stdout::add_to_linker_get_host)?; - ctx.link_bindings(bindings::cli::terminal_stderr::add_to_linker_get_host)?; - ctx.link_bindings(bindings::sockets::tcp::add_to_linker_get_host)?; - ctx.link_bindings(bindings::sockets::tcp_create_socket::add_to_linker_get_host)?; - ctx.link_bindings(bindings::sockets::udp::add_to_linker_get_host)?; - ctx.link_bindings(bindings::sockets::udp_create_socket::add_to_linker_get_host)?; - ctx.link_bindings(bindings::sockets::instance_network::add_to_linker_get_host)?; - ctx.link_bindings(bindings::sockets::network::add_to_linker_get_host)?; - ctx.link_bindings(bindings::sockets::ip_name_lookup::add_to_linker_get_host)?; + fn type_annotate(f: F) -> F + where + F: Fn(&mut T) -> &mut dyn WasiView, + { + f + } + let get_data = ctx.get_data_fn(); + let closure = type_annotate(move |data| get_data(data) as &mut dyn WasiView); + if let Some(linker) = ctx.linker() { + use wasmtime_wasi::bindings; + bindings::clocks::wall_clock::add_to_linker_get_host(linker, closure)?; + bindings::clocks::monotonic_clock::add_to_linker_get_host(linker, closure)?; + bindings::filesystem::types::add_to_linker_get_host(linker, closure)?; + bindings::filesystem::preopens::add_to_linker_get_host(linker, closure)?; + bindings::io::error::add_to_linker_get_host(linker, closure)?; + bindings::io::poll::add_to_linker_get_host(linker, closure)?; + bindings::io::streams::add_to_linker_get_host(linker, closure)?; + bindings::random::random::add_to_linker_get_host(linker, closure)?; + bindings::random::insecure::add_to_linker_get_host(linker, closure)?; + bindings::random::insecure_seed::add_to_linker_get_host(linker, closure)?; + bindings::cli::exit::add_to_linker_get_host(linker, closure)?; + bindings::cli::environment::add_to_linker_get_host(linker, closure)?; + bindings::cli::stdin::add_to_linker_get_host(linker, closure)?; + bindings::cli::stdout::add_to_linker_get_host(linker, closure)?; + bindings::cli::stderr::add_to_linker_get_host(linker, closure)?; + bindings::cli::terminal_input::add_to_linker_get_host(linker, closure)?; + bindings::cli::terminal_output::add_to_linker_get_host(linker, closure)?; + bindings::cli::terminal_stdin::add_to_linker_get_host(linker, closure)?; + bindings::cli::terminal_stdout::add_to_linker_get_host(linker, closure)?; + bindings::cli::terminal_stderr::add_to_linker_get_host(linker, closure)?; + bindings::sockets::tcp::add_to_linker_get_host(linker, closure)?; + bindings::sockets::tcp_create_socket::add_to_linker_get_host(linker, closure)?; + bindings::sockets::udp::add_to_linker_get_host(linker, closure)?; + bindings::sockets::udp_create_socket::add_to_linker_get_host(linker, closure)?; + bindings::sockets::instance_network::add_to_linker_get_host(linker, closure)?; + bindings::sockets::network::add_to_linker_get_host(linker, closure)?; + bindings::sockets::ip_name_lookup::add_to_linker_get_host(linker, closure)?; + } Ok(()) } } @@ -100,20 +110,18 @@ impl<'a> MountFilesContext<'a> { pub struct InstancePreparer { wasi_ctx: WasiCtxBuilder, - socket_allow_ports: Pool, } impl FactorInstancePreparer for InstancePreparer { // NOTE: Replaces WASI parts of AppComponent::apply_store_config fn new( - factor: &WasiFactor, - app_component: &AppComponent, - _ctx: PrepareContext, + ctx: PrepareContext, + _preparers: InstancePreparers, ) -> Result { let mut wasi_ctx = WasiCtxBuilder::new(); // Apply environment variables - for (key, val) in app_component.environment() { + for (key, val) in ctx.app_component().environment() { wasi_ctx.env(key, val); } @@ -121,23 +129,15 @@ impl FactorInstancePreparer for InstancePreparer { let mount_ctx = MountFilesContext { wasi_ctx: &mut wasi_ctx, }; - factor.files_mounter.mount_files(app_component, mount_ctx)?; + ctx.factor() + .files_mounter + .mount_files(ctx.app_component(), mount_ctx)?; - Ok(Self { - wasi_ctx, - socket_allow_ports: Default::default(), - }) + Ok(Self { wasi_ctx }) } fn prepare(self) -> Result { - let Self { - mut wasi_ctx, - socket_allow_ports, - } = self; - - // Enforce socket_allow_ports - wasi_ctx.socket_addr_check(move |addr, _| socket_allow_ports.check_addr(addr).is_ok()); - + let Self { mut wasi_ctx } = self; Ok(InstanceState { ctx: wasi_ctx.build(), table: Default::default(), @@ -146,17 +146,23 @@ impl FactorInstancePreparer for InstancePreparer { } impl InstancePreparer { - pub fn inherit_network(&mut self) { - self.wasi_ctx.inherit_network(); - } - - pub fn socket_allow_ports(&mut self, ip_net: IpNet, ports_start: u16, ports_end: Option) { - self.socket_allow_ports.insert_ip_net_port_range( - ip_net, - ports_start, - ports_end, - cap_primitives::ambient_authority(), - ); + pub fn outbound_socket_addr_check(&mut self, check: F) + where + F: Fn(SocketAddr) -> Fut + Send + Sync + Clone + 'static, + Fut: Future + Send + Sync, + { + self.wasi_ctx.socket_addr_check(move |addr, addr_use| { + let check = check.clone(); + Box::pin(async move { + match addr_use { + wasmtime_wasi::SocketAddrUse::TcpBind => false, + wasmtime_wasi::SocketAddrUse::TcpConnect + | wasmtime_wasi::SocketAddrUse::UdpBind + | wasmtime_wasi::SocketAddrUse::UdpConnect + | wasmtime_wasi::SocketAddrUse::UdpOutgoingDatagram => check(addr).await, + } + }) + }); } } diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs index a4441a0189..bc8f0813bc 100644 --- a/crates/factor-wasi/src/preview1.rs +++ b/crates/factor-wasi/src/preview1.rs @@ -1,5 +1,5 @@ use spin_factors::{ - AppComponent, Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors, + Factor, FactorInstancePreparer, InitContext, InstancePreparers, PrepareContext, Result, SpinFactors }; use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder}; @@ -21,9 +21,8 @@ pub struct InstancePreparer { impl FactorInstancePreparer for InstancePreparer { fn new( - _factor: &WasiPreview1Factor, - _app_component: &AppComponent, - _ctx: PrepareContext, + _ctx: PrepareContext, + _preparers: InstancePreparers, ) -> Result { Ok(Self { wasi_ctx: WasiCtxBuilder::new(), diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index 456068c2c6..85d712ea02 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -107,9 +107,12 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { #( preparers.#factor_names = Some( #factors_path::FactorInstancePreparer::<#factor_types>::new::<#name>( - &self.#factor_names, - &app_component, - #factors_path::PrepareContext::new(configured_app, &mut preparers), + #factors_path::PrepareContext::new( + &self.#factor_names, + configured_app.app_config::<#factor_types>().unwrap(), + &app_component, + ), + #factors_path::InstancePreparers::new(&mut preparers), )? ); )* diff --git a/crates/factors/Cargo.toml b/crates/factors/Cargo.toml index 779b95ba08..6c54eb427e 100644 --- a/crates/factors/Cargo.toml +++ b/crates/factors/Cargo.toml @@ -14,6 +14,7 @@ wasmtime = { workspace = true } serde_json = "1.0" spin-factors-derive = { path = "../factors-derive", features = ["expander"] } spin-factor-outbound-networking = { path = "../factor-outbound-networking" } +spin-factor-variables = { path = "../factor-variables" } spin-factor-wasi = { path = "../factor-wasi" } [lints] diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index d917c392ea..5d36498d5c 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -37,10 +37,9 @@ pub trait Factor: Any + Sized { } /// Performs factor-specific validation and configuration for the given - /// [`App`] and [`RuntimeConfig`]. A runtime may - but is not required to - - /// reuse the returned config across multiple instances. Note that this may - /// be called without any call to `init` in cases where only validation is - /// needed. + /// [`App`]. A runtime may - but is not required to - reuse the returned + /// config across multiple instances. Note that this may be called without + /// any call to `init` in cases where only validation is needed. fn configure_app( &self, app: &App, @@ -56,18 +55,18 @@ type GetDataFn = /// An InitContext is passed to [`Factor::init`], giving access to the global /// common [`wasmtime::component::Linker`]. -pub struct InitContext<'a, Factors: SpinFactors, Fact: Factor> { +pub struct InitContext<'a, Factors: SpinFactors, T: Factor> { linker: Option<&'a mut Linker>, module_linker: Option<&'a mut ModuleLinker>, - get_data: GetDataFn, + get_data: GetDataFn, } -impl<'a, Factors: SpinFactors, Fact: Factor> InitContext<'a, Factors, Fact> { +impl<'a, Factors: SpinFactors, T: Factor> InitContext<'a, Factors, T> { #[doc(hidden)] pub fn new( linker: Option<&'a mut Linker>, module_linker: Option<&'a mut ModuleLinker>, - get_data: GetDataFn, + get_data: GetDataFn, ) -> Self { Self { linker, @@ -84,7 +83,7 @@ impl<'a, Factors: SpinFactors, Fact: Factor> InitContext<'a, Factors, Fact> { self.module_linker.as_deref_mut() } - pub fn get_data_fn(&self) -> GetDataFn { + pub fn get_data_fn(&self) -> GetDataFn { self.get_data } @@ -92,7 +91,7 @@ impl<'a, Factors: SpinFactors, Fact: Factor> InitContext<'a, Factors, Fact> { &mut self, add_to_linker: impl Fn( &mut Linker, - fn(&mut Factors::InstanceState) -> &mut Fact::InstanceState, + fn(&mut Factors::InstanceState) -> &mut T::InstanceState, ) -> Result<()>, ) -> Result<()> where { @@ -107,7 +106,7 @@ where { &mut self, add_to_linker: impl Fn( &mut ModuleLinker, - fn(&mut Factors::InstanceState) -> &mut Fact::InstanceState, + fn(&mut Factors::InstanceState) -> &mut T::InstanceState, ) -> Result<()>, ) -> Result<()> where { @@ -137,37 +136,74 @@ impl<'a, Factors: SpinFactors> ConfigureAppContext<'a, Factors> { pub trait FactorInstancePreparer: Sized { /// Returns a new instance of this preparer for the given [`Factor`]. fn new( - factor: &T, - app_component: &AppComponent, - _ctx: PrepareContext, + ctx: PrepareContext, + _preparers: InstancePreparers, ) -> Result; /// Returns a new instance of the associated [`Factor::InstanceState`]. fn prepare(self) -> Result; } +impl FactorInstancePreparer for () +where + T::InstanceState: Default, +{ + fn new( + _ctx: PrepareContext, + _preparers: InstancePreparers, + ) -> Result { + Ok(()) + } + + fn prepare(self) -> Result { + Ok(Default::default()) + } +} + /// A PrepareContext is passed to [`FactorInstancePreparer::new`], giving access /// to any already-initialized [`FactorInstancePreparer`]s, allowing for /// inter-[`Factor`] dependencies. -pub struct PrepareContext<'a, Factors: SpinFactors> { - configured_app: &'a ConfiguredApp, - instance_preparers: &'a mut Factors::InstancePreparers, +pub struct PrepareContext<'a, T: Factor> { + factor: &'a T, + app_config: &'a T::AppConfig, + app_component: &'a AppComponent<'a>, } -impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { +impl<'a, T: Factor> PrepareContext<'a, T> { #[doc(hidden)] pub fn new( - configured_app: &'a ConfiguredApp, - instance_preparers: &'a mut Factors::InstancePreparers, + factor: &'a T, + app_config: &'a T::AppConfig, + app_component: &'a AppComponent, ) -> Self { Self { - configured_app, - instance_preparers, + factor, + app_config, + app_component, } } - pub fn app_config(&self) -> Result<&T::AppConfig> { - self.configured_app.app_config::() + pub fn factor(&self) -> &T { + self.factor + } + + pub fn app_config(&self) -> &T::AppConfig { + self.app_config + } + + pub fn app_component(&self) -> &AppComponent { + self.app_component + } +} + +pub struct InstancePreparers<'a, Factors: SpinFactors> { + inner: &'a mut Factors::InstancePreparers, +} + +impl<'a, Factors: SpinFactors> InstancePreparers<'a, Factors> { + #[doc(hidden)] + pub fn new(inner: &'a mut Factors::InstancePreparers) -> Self { + Self { inner } } /// Returns a already-initialized preparer for the given [`Factor`]. @@ -175,8 +211,8 @@ impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { /// Fails if the current [`SpinFactors`] does not include the given /// [`Factor`] or if the given [`Factor`]'s preparer has not been /// initialized yet (because it is sequenced after this factor). - pub fn instance_preparer_mut(&mut self) -> Result<&mut T::InstancePreparer> { - Factors::instance_preparer_mut::(self.instance_preparers) + pub fn get_mut(&mut self) -> Result<&mut T::InstancePreparer> { + Factors::instance_preparer_mut::(self.inner) .and_then(|maybe_preparer| maybe_preparer.context("preparer not yet initialized")) .with_context(|| { format!( @@ -187,24 +223,6 @@ impl<'a, Factors: SpinFactors> PrepareContext<'a, Factors> { } } -impl FactorInstancePreparer for () -where - T::InstanceState: Default, -{ - fn new( - factor: &T, - app_component: &AppComponent, - _ctx: PrepareContext, - ) -> Result { - (_, _) = (factor, app_component); - Ok(()) - } - - fn prepare(self) -> Result { - Ok(Default::default()) - } -} - pub struct ConfiguredApp { app: App, app_configs: Factors::AppConfigs, diff --git a/crates/factors/tests/smoke.rs b/crates/factors/tests/smoke.rs index e1f39f2c59..a4157bc6a2 100644 --- a/crates/factors/tests/smoke.rs +++ b/crates/factors/tests/smoke.rs @@ -1,5 +1,6 @@ use spin_app::App; use spin_factor_outbound_networking::OutboundNetworkingFactor; +use spin_factor_variables::VariablesFactor; use spin_factor_wasi::{preview1::WasiPreview1Factor, DummyFilesMounter, WasiFactor}; use spin_factors::SpinFactors; @@ -7,6 +8,7 @@ use spin_factors::SpinFactors; struct Factors { wasi: WasiFactor, wasip1: WasiPreview1Factor, + variables: VariablesFactor, outbound_networking_factor: OutboundNetworkingFactor, } @@ -14,6 +16,7 @@ fn main() -> anyhow::Result<()> { let mut factors = Factors { wasi: WasiFactor::new(DummyFilesMounter), wasip1: WasiPreview1Factor, + variables: VariablesFactor, outbound_networking_factor: OutboundNetworkingFactor, // outbound_http_factor: OutboundHttpFactor, }; diff --git a/crates/world/Cargo.toml b/crates/world/Cargo.toml index 3c51d5b247..270b46a174 100644 --- a/crates/world/Cargo.toml +++ b/crates/world/Cargo.toml @@ -5,4 +5,5 @@ authors = { workspace = true } edition = { workspace = true } [dependencies] +async-trait = "0.1" wasmtime = { workspace = true } diff --git a/crates/world/src/lib.rs b/crates/world/src/lib.rs index 42c4d35451..d7adeb19f0 100644 --- a/crates/world/src/lib.rs +++ b/crates/world/src/lib.rs @@ -1,5 +1,7 @@ #![allow(missing_docs)] +pub use async_trait::async_trait; + wasmtime::component::bindgen!({ inline: r#" package fermyon:runtime; From e539cf6e5cd731e0423fc2d50bab4b43ac170db6 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Tue, 4 Jun 2024 09:53:06 -0400 Subject: [PATCH 09/22] factors: Reorganize spin-factors Signed-off-by: Lann Martin --- Cargo.lock | 18 +- crates/factor-outbound-networking/Cargo.toml | 1 - crates/factor-outbound-networking/src/lib.rs | 24 +- crates/factor-variables/Cargo.toml | 1 - crates/factor-variables/src/lib.rs | 20 +- crates/factor-wasi/Cargo.toml | 1 - crates/factor-wasi/src/lib.rs | 30 +- crates/factor-wasi/src/preview1.rs | 12 +- crates/factors-derive/src/lib.rs | 20 +- crates/factors/Cargo.toml | 3 + crates/factors/src/factor.rs | 152 ++++++++ crates/factors/src/instance_preparer.rs | 86 +++++ crates/factors/src/lib.rs | 348 +------------------ crates/factors/src/runtime_config.rs | 94 +++++ crates/factors/src/spin_factors.rs | 109 ++++++ crates/factors/tests/smoke.rs | 2 +- 16 files changed, 544 insertions(+), 377 deletions(-) create mode 100644 crates/factors/src/factor.rs create mode 100644 crates/factors/src/instance_preparer.rs create mode 100644 crates/factors/src/runtime_config.rs create mode 100644 crates/factors/src/spin_factors.rs diff --git a/Cargo.lock b/Cargo.lock index 032f8dcc51..9c575470f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7562,19 +7562,28 @@ dependencies = [ name = "spin-factor-outbound-networking" version = "2.6.0-pre0" dependencies = [ - "anyhow", + "futures-util", "ipnet", + "spin-factor-variables", "spin-factor-wasi", "spin-factors", "spin-outbound-networking", "tracing", ] +[[package]] +name = "spin-factor-variables" +version = "2.6.0-pre0" +dependencies = [ + "spin-expressions", + "spin-factors", + "spin-world", +] + [[package]] name = "spin-factor-wasi" version = "2.6.0-pre0" dependencies = [ - "anyhow", "cap-primitives 3.0.0", "spin-factors", "wasmtime-wasi", @@ -7585,11 +7594,15 @@ name = "spin-factors" version = "2.6.0-pre0" dependencies = [ "anyhow", + "serde 1.0.197", "serde_json", "spin-app", "spin-factor-outbound-networking", + "spin-factor-variables", "spin-factor-wasi", "spin-factors-derive", + "thiserror", + "tracing", "wasmtime", ] @@ -8138,6 +8151,7 @@ dependencies = [ name = "spin-world" version = "2.7.0-pre0" dependencies = [ + "async-trait", "wasmtime", ] diff --git a/crates/factor-outbound-networking/Cargo.toml b/crates/factor-outbound-networking/Cargo.toml index 8eddb55eb3..13dd49c9e3 100644 --- a/crates/factor-outbound-networking/Cargo.toml +++ b/crates/factor-outbound-networking/Cargo.toml @@ -5,7 +5,6 @@ authors = { workspace = true } edition = { workspace = true } [dependencies] -anyhow = "1" futures-util = "0.3" ipnet = "2.9.0" spin-factor-variables = { path = "../factor-variables" } diff --git a/crates/factor-outbound-networking/src/lib.rs b/crates/factor-outbound-networking/src/lib.rs index 39013b8061..7387d69a21 100644 --- a/crates/factor-outbound-networking/src/lib.rs +++ b/crates/factor-outbound-networking/src/lib.rs @@ -1,6 +1,5 @@ use std::{collections::HashMap, sync::Arc}; -use anyhow::Context; use futures_util::{ future::{BoxFuture, Shared}, FutureExt, @@ -8,7 +7,9 @@ use futures_util::{ use spin_factor_variables::VariablesFactor; use spin_factor_wasi::WasiFactor; use spin_factors::{ - Factor, FactorInstancePreparer, InstancePreparers, PrepareContext, Result, SpinFactors, + anyhow::{self, Context}, + ConfigureAppContext, Factor, FactorInstancePreparer, InstancePreparers, PrepareContext, + RuntimeConfig, SpinFactors, }; use spin_outbound_networking::{AllowedHostsConfig, ALLOWED_HOSTS_KEY}; @@ -21,11 +22,12 @@ impl Factor for OutboundNetworkingFactor { fn configure_app( &self, - app: &spin_factors::App, - _ctx: spin_factors::ConfigureAppContext, - ) -> Result { + ctx: ConfigureAppContext, + _runtime_config: &mut impl RuntimeConfig, + ) -> anyhow::Result { // Extract allowed_outbound_hosts for all components - let component_allowed_hosts = app + let component_allowed_hosts = ctx + .app() .components() .map(|component| { Ok(( @@ -37,7 +39,7 @@ impl Factor for OutboundNetworkingFactor { .into(), )) }) - .collect::>()?; + .collect::>()?; Ok(AppConfig { component_allowed_hosts, }) @@ -49,17 +51,17 @@ pub struct AppConfig { component_allowed_hosts: HashMap>, } -type AllowedHostsFuture = Shared>>>; +type SharedFutureResult = Shared>>>; pub struct InstancePreparer { - allowed_hosts_future: AllowedHostsFuture, + allowed_hosts_future: SharedFutureResult, } impl FactorInstancePreparer for InstancePreparer { fn new( ctx: PrepareContext, mut preparers: InstancePreparers, - ) -> Result { + ) -> anyhow::Result { let hosts = ctx .app_config() .component_allowed_hosts @@ -104,7 +106,7 @@ impl FactorInstancePreparer for InstancePreparer { }) } - fn prepare(self) -> Result<::InstanceState> { + fn prepare(self) -> anyhow::Result<::InstanceState> { Ok(()) } } diff --git a/crates/factor-variables/Cargo.toml b/crates/factor-variables/Cargo.toml index 27ab8498bd..7621fb266b 100644 --- a/crates/factor-variables/Cargo.toml +++ b/crates/factor-variables/Cargo.toml @@ -5,7 +5,6 @@ authors = { workspace = true } edition = { workspace = true } [dependencies] -anyhow = "1" spin-expressions = { path = "../expressions" } spin-factors = { path = "../factors" } spin-world = { path = "../world" } diff --git a/crates/factor-variables/src/lib.rs b/crates/factor-variables/src/lib.rs index 360956aad2..423c8bba8d 100644 --- a/crates/factor-variables/src/lib.rs +++ b/crates/factor-variables/src/lib.rs @@ -2,7 +2,8 @@ use std::sync::Arc; use spin_expressions::ProviderResolver; use spin_factors::{ - Factor, FactorInstancePreparer, InstancePreparers, PrepareContext, Result, SpinFactors, + anyhow, ConfigureAppContext, Factor, FactorInstancePreparer, InitContext, InstancePreparers, + PrepareContext, RuntimeConfig, SpinFactors, }; use spin_world::{async_trait, v1::config as v1_config, v2::variables}; @@ -15,8 +16,8 @@ impl Factor for VariablesFactor { fn init( &mut self, - mut ctx: spin_factors::InitContext, - ) -> Result<()> { + mut ctx: InitContext, + ) -> anyhow::Result<()> { ctx.link_bindings(v1_config::add_to_linker)?; ctx.link_bindings(variables::add_to_linker)?; Ok(()) @@ -24,9 +25,10 @@ impl Factor for VariablesFactor { fn configure_app( &self, - app: &spin_factors::App, - _ctx: spin_factors::ConfigureAppContext, - ) -> Result { + ctx: ConfigureAppContext, + _runtime_config: &mut impl RuntimeConfig, + ) -> anyhow::Result { + let app = ctx.app(); let mut resolver = ProviderResolver::new(app.variables().map(|(key, val)| (key.clone(), val.clone())))?; for component in app.components() { @@ -61,7 +63,7 @@ impl FactorInstancePreparer for InstancePreparer { fn new( ctx: PrepareContext, _preparers: InstancePreparers, - ) -> Result { + ) -> anyhow::Result { let component_id = ctx.app_component().id().to_string(); let resolver = ctx.app_config().resolver.clone(); Ok(Self { @@ -72,7 +74,7 @@ impl FactorInstancePreparer for InstancePreparer { }) } - fn prepare(self) -> Result<::InstanceState> { + fn prepare(self) -> anyhow::Result { Ok(self.state) } } @@ -92,7 +94,7 @@ impl variables::Host for InstanceState { .map_err(expressions_to_variables_err) } - fn convert_error(&mut self, error: variables::Error) -> Result { + fn convert_error(&mut self, error: variables::Error) -> anyhow::Result { Ok(error) } } diff --git a/crates/factor-wasi/Cargo.toml b/crates/factor-wasi/Cargo.toml index 58d4b5ff49..d1572e555a 100644 --- a/crates/factor-wasi/Cargo.toml +++ b/crates/factor-wasi/Cargo.toml @@ -5,7 +5,6 @@ authors = { workspace = true } edition = { workspace = true } [dependencies] -anyhow = "1" cap-primitives = "3.0.0" spin-factors = { path = "../factors" } wasmtime-wasi = { workspace = true } diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs index 1bc01f3760..6320682dac 100644 --- a/crates/factor-wasi/src/lib.rs +++ b/crates/factor-wasi/src/lib.rs @@ -2,10 +2,9 @@ pub mod preview1; use std::{future::Future, net::SocketAddr, path::Path}; -use anyhow::ensure; use spin_factors::{ - AppComponent, Factor, FactorInstancePreparer, InitContext, InstancePreparers, PrepareContext, - Result, SpinFactors, + anyhow, AppComponent, Factor, FactorInstancePreparer, InitContext, InstancePreparers, + PrepareContext, SpinFactors, }; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; @@ -26,7 +25,10 @@ impl Factor for WasiFactor { type InstancePreparer = InstancePreparer; type InstanceState = InstanceState; - fn init(&mut self, mut ctx: InitContext) -> Result<()> { + fn init( + &mut self, + mut ctx: InitContext, + ) -> anyhow::Result<()> { fn type_annotate(f: F) -> F where F: Fn(&mut T) -> &mut dyn WasiView, @@ -70,14 +72,22 @@ impl Factor for WasiFactor { } pub trait FilesMounter { - fn mount_files(&self, app_component: &AppComponent, ctx: MountFilesContext) -> Result<()>; + fn mount_files( + &self, + app_component: &AppComponent, + ctx: MountFilesContext, + ) -> anyhow::Result<()>; } pub struct DummyFilesMounter; impl FilesMounter for DummyFilesMounter { - fn mount_files(&self, app_component: &AppComponent, _ctx: MountFilesContext) -> Result<()> { - ensure!( + fn mount_files( + &self, + app_component: &AppComponent, + _ctx: MountFilesContext, + ) -> anyhow::Result<()> { + anyhow::ensure!( app_component.files().next().is_none(), "DummyFilesMounter can't actually mount files" ); @@ -95,7 +105,7 @@ impl<'a> MountFilesContext<'a> { host_path: impl AsRef, guest_path: impl AsRef, writable: bool, - ) -> Result<()> { + ) -> anyhow::Result<()> { use wasmtime_wasi::{DirPerms, FilePerms}; let (dir_perms, file_perms) = if writable { (DirPerms::all(), FilePerms::all()) @@ -117,7 +127,7 @@ impl FactorInstancePreparer for InstancePreparer { fn new( ctx: PrepareContext, _preparers: InstancePreparers, - ) -> Result { + ) -> anyhow::Result { let mut wasi_ctx = WasiCtxBuilder::new(); // Apply environment variables @@ -136,7 +146,7 @@ impl FactorInstancePreparer for InstancePreparer { Ok(Self { wasi_ctx }) } - fn prepare(self) -> Result { + fn prepare(self) -> anyhow::Result { let Self { mut wasi_ctx } = self; Ok(InstanceState { ctx: wasi_ctx.build(), diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs index bc8f0813bc..426ebff6df 100644 --- a/crates/factor-wasi/src/preview1.rs +++ b/crates/factor-wasi/src/preview1.rs @@ -1,5 +1,6 @@ use spin_factors::{ - Factor, FactorInstancePreparer, InitContext, InstancePreparers, PrepareContext, Result, SpinFactors + anyhow, Factor, FactorInstancePreparer, InitContext, InstancePreparers, PrepareContext, + SpinFactors, }; use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder}; @@ -10,7 +11,10 @@ impl Factor for WasiPreview1Factor { type InstancePreparer = InstancePreparer; type InstanceState = WasiP1Ctx; - fn init(&mut self, mut ctx: InitContext) -> Result<()> { + fn init( + &mut self, + mut ctx: InitContext, + ) -> anyhow::Result<()> { ctx.link_module_bindings(wasmtime_wasi::preview1::add_to_linker_async) } } @@ -23,13 +27,13 @@ impl FactorInstancePreparer for InstancePreparer { fn new( _ctx: PrepareContext, _preparers: InstancePreparers, - ) -> Result { + ) -> anyhow::Result { Ok(Self { wasi_ctx: WasiCtxBuilder::new(), }) } - fn prepare(mut self) -> Result<::InstanceState> { + fn prepare(mut self) -> anyhow::Result { Ok(self.wasi_ctx.build_p1()) } } diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index 85d712ea02..399606708c 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -53,13 +53,14 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { factor_types.push(&field.ty); } + let TypeId = quote!(::std::any::TypeId); let factors_crate = format_ident!("spin_factors"); let factors_path = quote!(::#factors_crate); - let Factor = quote!(#factors_path::Factor); - let Result = quote!(#factors_path::Result); let wasmtime = quote!(#factors_path::wasmtime); + let Result = quote!(#factors_path::Result); + let Factor = quote!(#factors_path::Factor); let ConfiguredApp = quote!(#factors_path::ConfiguredApp); - let TypeId = quote!(::std::any::TypeId); + let RuntimeConfigTracker = quote!(#factors_path::__internal::RuntimeConfigTracker); Ok(quote! { impl #name { @@ -81,16 +82,21 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { Ok(()) } - pub fn configure_app(&self, app: #factors_path::App) -> #Result<#ConfiguredApp> { + pub fn configure_app( + &self, + app: #factors_path::App, + runtime_config: impl #factors_path::RuntimeConfigSource + ) -> #Result<#ConfiguredApp> { let mut app_configs = #app_configs_name { #( #factor_names: None, )* }; + let mut runtime_config = #RuntimeConfigTracker::new(runtime_config); #( app_configs.#factor_names = Some( #Factor::configure_app( &self.#factor_names, - &app, - #factors_path::ConfigureAppContext::::new(&app_configs), + #factors_path::ConfigureAppContext::::new(&app, &app_configs), + &mut runtime_config, )? ); )* @@ -99,7 +105,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { pub fn build_store_data(&self, configured_app: &#ConfiguredApp, component_id: &str) -> #Result<#state_name> { let app_component = configured_app.app().get_component(component_id).ok_or_else(|| { - #factors_path::Error::msg(format!("unknown component {component_id:?}")) + #wasmtime::Error::msg("unknown component") })?; let mut preparers = #preparers_name { #( #factor_names: None, )* diff --git a/crates/factors/Cargo.toml b/crates/factors/Cargo.toml index 6c54eb427e..2d9347dbef 100644 --- a/crates/factors/Cargo.toml +++ b/crates/factors/Cargo.toml @@ -6,8 +6,11 @@ edition = { workspace = true } [dependencies] anyhow = "1.0" +serde = "1.0" spin-app = { path = "../app" } spin-factors-derive = { path = "../factors-derive" } +thiserror = "1.0" +tracing = { workspace = true } wasmtime = { workspace = true } [dev-dependencies] diff --git a/crates/factors/src/factor.rs b/crates/factors/src/factor.rs new file mode 100644 index 0000000000..552e844171 --- /dev/null +++ b/crates/factors/src/factor.rs @@ -0,0 +1,152 @@ +use std::any::Any; + +use anyhow::Context; + +use crate::{App, FactorInstancePreparer, Linker, ModuleLinker, RuntimeConfig, SpinFactors}; + +pub trait Factor: Any + Sized { + /// Per-app configuration for this factor. + /// + /// See [`Factor::configure_app`]. + type AppConfig: Default; + + /// The [`FactorInstancePreparer`] for this factor. + type InstancePreparer: FactorInstancePreparer; + + /// The per-instance state for this factor, constructed by a + /// [`FactorInstancePreparer`] and available to any host-provided imports + /// defined by this factor. + type InstanceState; + + /// Initializes this Factor for a runtime. This will be called at most once, + /// before any call to [`FactorInstancePreparer::new`] + fn init( + &mut self, + mut ctx: InitContext, + ) -> anyhow::Result<()> { + // TODO: Should `ctx` always be immut? Rename this param/type? + _ = &mut ctx; + Ok(()) + } + + /// Performs factor-specific validation and configuration for the given + /// [`App`]. A runtime may - but is not required to - reuse the returned + /// config across multiple instances. Note that this may be called without + /// any call to `init` in cases where only validation is needed. + fn configure_app( + &self, + ctx: ConfigureAppContext, + _runtime_config: &mut impl RuntimeConfig, + ) -> anyhow::Result { + _ = ctx; + Ok(Default::default()) + } +} + +pub(crate) type GetDataFn = + fn(&mut ::InstanceState) -> &mut ::InstanceState; + +/// An InitContext is passed to [`Factor::init`], giving access to the global +/// common [`wasmtime::component::Linker`]. +pub struct InitContext<'a, Factors: SpinFactors, T: Factor> { + pub(crate) linker: Option<&'a mut Linker>, + pub(crate) module_linker: Option<&'a mut ModuleLinker>, + pub(crate) get_data: GetDataFn, +} + +impl<'a, Factors: SpinFactors, T: Factor> InitContext<'a, Factors, T> { + #[doc(hidden)] + pub fn new( + linker: Option<&'a mut Linker>, + module_linker: Option<&'a mut ModuleLinker>, + get_data: GetDataFn, + ) -> Self { + Self { + linker, + module_linker, + get_data, + } + } + + pub fn linker(&mut self) -> Option<&mut Linker> { + self.linker.as_deref_mut() + } + + pub fn module_linker(&mut self) -> Option<&mut ModuleLinker> { + self.module_linker.as_deref_mut() + } + + pub fn get_data_fn(&self) -> GetDataFn { + self.get_data + } + + pub fn link_bindings( + &mut self, + add_to_linker: impl Fn( + &mut Linker, + fn(&mut Factors::InstanceState) -> &mut T::InstanceState, + ) -> anyhow::Result<()>, + ) -> anyhow::Result<()> +where { + if let Some(linker) = self.linker.as_deref_mut() { + add_to_linker(linker, self.get_data) + } else { + Ok(()) + } + } + + pub fn link_module_bindings( + &mut self, + add_to_linker: impl Fn( + &mut ModuleLinker, + fn(&mut Factors::InstanceState) -> &mut T::InstanceState, + ) -> anyhow::Result<()>, + ) -> anyhow::Result<()> +where { + if let Some(linker) = self.module_linker.as_deref_mut() { + add_to_linker(linker, self.get_data) + } else { + Ok(()) + } + } +} + +pub struct ConfigureAppContext<'a, Factors: SpinFactors> { + pub(crate) app: &'a App, + pub(crate) app_configs: &'a Factors::AppConfigs, +} + +impl<'a, Factors: SpinFactors> ConfigureAppContext<'a, Factors> { + #[doc(hidden)] + pub fn new(app: &'a App, app_configs: &'a Factors::AppConfigs) -> Self { + Self { app, app_configs } + } + + pub fn app(&self) -> &App { + self.app + } + + pub fn app_config(&self) -> crate::Result<&T::AppConfig> { + Factors::app_config::(self.app_configs).context("no such factor") + } +} + +pub struct ConfiguredApp { + app: App, + app_configs: Factors::AppConfigs, +} + +impl ConfiguredApp { + #[doc(hidden)] + pub fn new(app: App, app_configs: Factors::AppConfigs) -> Self { + Self { app, app_configs } + } + + pub fn app(&self) -> &App { + &self.app + } + + pub fn app_config(&self) -> crate::Result<&T::AppConfig> { + Factors::app_config::(&self.app_configs).context("no such factor") + } +} diff --git a/crates/factors/src/instance_preparer.rs b/crates/factors/src/instance_preparer.rs new file mode 100644 index 0000000000..43fb01fce3 --- /dev/null +++ b/crates/factors/src/instance_preparer.rs @@ -0,0 +1,86 @@ +use anyhow::Context; + +use crate::{AppComponent, Factor, SpinFactors}; + +pub trait FactorInstancePreparer: Sized { + /// Returns a new instance of this preparer for the given [`Factor`]. + fn new( + ctx: PrepareContext, + _preparers: InstancePreparers, + ) -> anyhow::Result; + + /// Returns a new instance of the associated [`Factor::InstanceState`]. + fn prepare(self) -> anyhow::Result; +} + +impl FactorInstancePreparer for () +where + T::InstanceState: Default, +{ + fn new( + _ctx: PrepareContext, + _preparers: InstancePreparers, + ) -> anyhow::Result { + Ok(()) + } + + fn prepare(self) -> anyhow::Result { + Ok(Default::default()) + } +} + +/// A PrepareContext is passed to [`FactorInstancePreparer::new`], giving access +/// to any already-initialized [`FactorInstancePreparer`]s, allowing for +/// inter-[`Factor`] dependencies. +pub struct PrepareContext<'a, T: Factor> { + pub(crate) factor: &'a T, + pub(crate) app_config: &'a T::AppConfig, + pub(crate) app_component: &'a AppComponent<'a>, +} + +impl<'a, T: Factor> PrepareContext<'a, T> { + #[doc(hidden)] + pub fn new( + factor: &'a T, + app_config: &'a T::AppConfig, + app_component: &'a AppComponent, + ) -> Self { + Self { + factor, + app_config, + app_component, + } + } + + pub fn factor(&self) -> &T { + self.factor + } + + pub fn app_config(&self) -> &T::AppConfig { + self.app_config + } + + pub fn app_component(&self) -> &AppComponent { + self.app_component + } +} + +pub struct InstancePreparers<'a, Factors: SpinFactors> { + pub(crate) inner: &'a mut Factors::InstancePreparers, +} + +impl<'a, Factors: SpinFactors> InstancePreparers<'a, Factors> { + #[doc(hidden)] + pub fn new(inner: &'a mut Factors::InstancePreparers) -> Self { + Self { inner } + } + + /// Returns a already-initialized preparer for the given [`Factor`]. + /// + /// Fails if the current [`SpinFactors`] does not include the given + /// [`Factor`] or if the given [`Factor`]'s preparer has not been + /// initialized yet (because it is sequenced after this factor). + pub fn get_mut(&mut self) -> crate::Result<&mut T::InstancePreparer> { + Factors::instance_preparer_mut::(self.inner)?.context("preparer not initialized") + } +} diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index 5d36498d5c..0d18109cfd 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -1,12 +1,19 @@ -use std::{any::Any, marker::PhantomData}; - -use anyhow::Context; -pub use spin_factors_derive::SpinFactors; +mod factor; +mod instance_preparer; +mod runtime_config; +mod spin_factors; +pub use anyhow; pub use wasmtime; -pub type Error = wasmtime::Error; -pub type Result = std::result::Result; +pub use spin_factors_derive::SpinFactors; + +pub use crate::{ + factor::{ConfigureAppContext, ConfiguredApp, Factor, InitContext}, + instance_preparer::{FactorInstancePreparer, InstancePreparers, PrepareContext}, + runtime_config::{RuntimeConfig, RuntimeConfigSource}, + spin_factors::SpinFactors, +}; pub type Linker = wasmtime::component::Linker<::InstanceState>; pub type ModuleLinker = wasmtime::Linker<::InstanceState>; @@ -15,329 +22,10 @@ pub type ModuleLinker = wasmtime::Linker<::Inst pub type App = spin_app::App<'static, spin_app::InertLoader>; pub type AppComponent<'a> = spin_app::AppComponent<'a, spin_app::InertLoader>; -pub trait Factor: Any + Sized { - /// App configuration for this factor. - /// - /// See [`Factor::configure_app`]. - type AppConfig: Default; - - /// The [`FactorInstancePreparer`] for this factor. - type InstancePreparer: FactorInstancePreparer; - - /// The per-instance state for this factor, constructed by a - /// [`FactorInstancePreparer`] and available to any host-provided imports - /// defined by this factor. - type InstanceState; - - /// Initializes this Factor for a runtime. This will be called at most once, - /// before any call to [`FactorInstancePreparer::new`] - fn init(&mut self, mut ctx: InitContext) -> Result<()> { - _ = &mut ctx; - Ok(()) - } - - /// Performs factor-specific validation and configuration for the given - /// [`App`]. A runtime may - but is not required to - reuse the returned - /// config across multiple instances. Note that this may be called without - /// any call to `init` in cases where only validation is needed. - fn configure_app( - &self, - app: &App, - _ctx: ConfigureAppContext, - ) -> Result { - _ = app; - Ok(Default::default()) - } -} - -type GetDataFn = - fn(&mut ::InstanceState) -> &mut ::InstanceState; - -/// An InitContext is passed to [`Factor::init`], giving access to the global -/// common [`wasmtime::component::Linker`]. -pub struct InitContext<'a, Factors: SpinFactors, T: Factor> { - linker: Option<&'a mut Linker>, - module_linker: Option<&'a mut ModuleLinker>, - get_data: GetDataFn, -} - -impl<'a, Factors: SpinFactors, T: Factor> InitContext<'a, Factors, T> { - #[doc(hidden)] - pub fn new( - linker: Option<&'a mut Linker>, - module_linker: Option<&'a mut ModuleLinker>, - get_data: GetDataFn, - ) -> Self { - Self { - linker, - module_linker, - get_data, - } - } - - pub fn linker(&mut self) -> Option<&mut Linker> { - self.linker.as_deref_mut() - } - - pub fn module_linker(&mut self) -> Option<&mut ModuleLinker> { - self.module_linker.as_deref_mut() - } - - pub fn get_data_fn(&self) -> GetDataFn { - self.get_data - } - - pub fn link_bindings( - &mut self, - add_to_linker: impl Fn( - &mut Linker, - fn(&mut Factors::InstanceState) -> &mut T::InstanceState, - ) -> Result<()>, - ) -> Result<()> -where { - if let Some(linker) = self.linker.as_deref_mut() { - add_to_linker(linker, self.get_data) - } else { - Ok(()) - } - } - - pub fn link_module_bindings( - &mut self, - add_to_linker: impl Fn( - &mut ModuleLinker, - fn(&mut Factors::InstanceState) -> &mut T::InstanceState, - ) -> Result<()>, - ) -> Result<()> -where { - if let Some(linker) = self.module_linker.as_deref_mut() { - add_to_linker(linker, self.get_data) - } else { - Ok(()) - } - } -} - -pub struct ConfigureAppContext<'a, Factors: SpinFactors> { - app_configs: &'a Factors::AppConfigs, -} - -impl<'a, Factors: SpinFactors> ConfigureAppContext<'a, Factors> { - #[doc(hidden)] - pub fn new(app_configs: &'a Factors::AppConfigs) -> Self { - Self { app_configs } - } - - pub fn app_config(&self) -> Result<&T::AppConfig> { - Factors::app_config::(self.app_configs).context("no such factor") - } -} - -pub trait FactorInstancePreparer: Sized { - /// Returns a new instance of this preparer for the given [`Factor`]. - fn new( - ctx: PrepareContext, - _preparers: InstancePreparers, - ) -> Result; - - /// Returns a new instance of the associated [`Factor::InstanceState`]. - fn prepare(self) -> Result; -} - -impl FactorInstancePreparer for () -where - T::InstanceState: Default, -{ - fn new( - _ctx: PrepareContext, - _preparers: InstancePreparers, - ) -> Result { - Ok(()) - } - - fn prepare(self) -> Result { - Ok(Default::default()) - } -} - -/// A PrepareContext is passed to [`FactorInstancePreparer::new`], giving access -/// to any already-initialized [`FactorInstancePreparer`]s, allowing for -/// inter-[`Factor`] dependencies. -pub struct PrepareContext<'a, T: Factor> { - factor: &'a T, - app_config: &'a T::AppConfig, - app_component: &'a AppComponent<'a>, -} - -impl<'a, T: Factor> PrepareContext<'a, T> { - #[doc(hidden)] - pub fn new( - factor: &'a T, - app_config: &'a T::AppConfig, - app_component: &'a AppComponent, - ) -> Self { - Self { - factor, - app_config, - app_component, - } - } - - pub fn factor(&self) -> &T { - self.factor - } - - pub fn app_config(&self) -> &T::AppConfig { - self.app_config - } - - pub fn app_component(&self) -> &AppComponent { - self.app_component - } -} - -pub struct InstancePreparers<'a, Factors: SpinFactors> { - inner: &'a mut Factors::InstancePreparers, -} - -impl<'a, Factors: SpinFactors> InstancePreparers<'a, Factors> { - #[doc(hidden)] - pub fn new(inner: &'a mut Factors::InstancePreparers) -> Self { - Self { inner } - } - - /// Returns a already-initialized preparer for the given [`Factor`]. - /// - /// Fails if the current [`SpinFactors`] does not include the given - /// [`Factor`] or if the given [`Factor`]'s preparer has not been - /// initialized yet (because it is sequenced after this factor). - pub fn get_mut(&mut self) -> Result<&mut T::InstancePreparer> { - Factors::instance_preparer_mut::(self.inner) - .and_then(|maybe_preparer| maybe_preparer.context("preparer not yet initialized")) - .with_context(|| { - format!( - "could not get instance preparer for {}", - std::any::type_name::() - ) - }) - } -} - -pub struct ConfiguredApp { - app: App, - app_configs: Factors::AppConfigs, -} - -impl ConfiguredApp { - #[doc(hidden)] - pub fn new(app: App, app_configs: Factors::AppConfigs) -> Self { - Self { app, app_configs } - } - - pub fn app(&self) -> &App { - &self.app - } - - pub fn app_config(&self) -> Result<&T::AppConfig> { - Factors::app_config::(&self.app_configs).context("no such factor") - } -} - -/// Implemented by `#[derive(SpinFactors)]` -pub trait SpinFactors: Sized { - type AppConfigs; - type InstancePreparers; - type InstanceState: Send + 'static; - - #[doc(hidden)] - unsafe fn instance_preparer_offset() -> Option; - - #[doc(hidden)] - unsafe fn instance_state_offset() -> Option; - - fn app_config(app_configs: &Self::AppConfigs) -> Option<&T::AppConfig>; - - fn instance_state_getter() -> Option> { - let offset = unsafe { Self::instance_state_offset::()? }; - Some(Getter { - offset, - _phantom: PhantomData, - }) - } - - fn instance_state_getter2( - ) -> Option> { - let offset1 = unsafe { Self::instance_state_offset::()? }; - let offset2 = unsafe { Self::instance_state_offset::()? }; - assert_ne!( - offset1, offset2, - "instance_state_getter2 with same factor twice would alias" - ); - Some(Getter2 { - offset1, - offset2, - _phantom: PhantomData, - }) - } - - fn instance_preparer_mut( - preparers: &mut Self::InstancePreparers, - ) -> Result> { - unsafe { - let offset = Self::instance_preparer_offset::().context("no such factor")?; - let ptr = preparers as *mut Self::InstancePreparers; - let opt = &mut *ptr.add(offset).cast::>(); - Ok(opt.as_mut()) - } - } -} - -pub struct Getter { - offset: usize, - _phantom: PhantomData &mut U>, -} - -impl Getter { - pub fn get_mut<'a>(&self, container: &'a mut T) -> &'a mut U { - let ptr = container as *mut T; - unsafe { &mut *ptr.add(self.offset).cast::() } - } -} - -impl Clone for Getter { - fn clone(&self) -> Self { - *self - } -} -impl Copy for Getter {} - -pub struct Getter2 { - offset1: usize, - offset2: usize, - #[allow(clippy::type_complexity)] - _phantom: PhantomData (&mut U, &mut V)>, -} - -impl Getter2 { - pub fn get_mut<'a>(&self, container: &'a mut T) -> (&'a mut U, &'a mut V) - where - T: 'static, - U: 'static, - V: 'static, - { - let ptr = container as *mut T; - unsafe { - ( - &mut *ptr.add(self.offset1).cast::(), - &mut *ptr.add(self.offset2).cast::(), - ) - } - } -} +// TODO: Add a real Error type +pub type Result = wasmtime::Result; -impl Clone for Getter2 { - fn clone(&self) -> Self { - *self - } +#[doc(hidden)] +pub mod __internal { + pub use crate::runtime_config::RuntimeConfigTracker; } -impl Copy for Getter2 {} diff --git a/crates/factors/src/runtime_config.rs b/crates/factors/src/runtime_config.rs new file mode 100644 index 0000000000..020707395d --- /dev/null +++ b/crates/factors/src/runtime_config.rs @@ -0,0 +1,94 @@ +use std::collections::HashSet; + +use anyhow::bail; +use serde::de::DeserializeOwned; + +/// RuntimeConfig represents an application's runtime configuration. +/// +/// Runtime configuration is partitioned, with each partition being the +/// responsibility of exactly one [`crate::Factor`]. If configuration needs to +/// be shared between Factors, one Factor can be selected as the owner and the +/// others will have a dependency relationship with that owner. +pub trait RuntimeConfig { + /// Returns deserialized runtime config of the given type for the given + /// factor config key. + /// + /// Returns Ok(None) if no configuration is available for the given key. + /// Returns Err if configuration is available but deserialization fails, + /// or if the given config key has already been retrieved. + fn get_config( + &mut self, + factor_config_key: &str, + ) -> anyhow::Result>; +} + +pub struct RuntimeConfigTracker { + source: Source, + used_keys: HashSet, + unused_keys: HashSet, +} + +impl RuntimeConfigTracker { + #[doc(hidden)] + pub fn new(source: Source) -> Self { + let unused_keys = source.factor_config_keys().map(ToOwned::to_owned).collect(); + Self { + source, + used_keys: Default::default(), + unused_keys, + } + } + + #[doc(hidden)] + pub fn validate_all_keys_used(self) -> Result<(), impl IntoIterator> { + if self.unused_keys.is_empty() { + Ok(()) + } else { + Err(self.unused_keys) + } + } +} + +impl RuntimeConfig for RuntimeConfigTracker { + fn get_config( + &mut self, + factor_config_key: &str, + ) -> anyhow::Result> { + if !self.used_keys.insert(factor_config_key.to_owned()) { + bail!("already got runtime config key {factor_config_key:?}"); + } + self.unused_keys.remove(factor_config_key); + self.source.get_config::(factor_config_key) + } +} + +pub trait RuntimeConfigSource { + /// Returns deserialized runtime config of the given type for the given + /// factor config key. + /// + /// Returns Ok(None) if no configuration is available for the given key. + /// Returns Err if configuration is available but deserialization fails. + fn get_config( + &self, + factor_config_key: &str, + ) -> anyhow::Result>; + + /// Returns an iterator of factor config keys available in this source. + /// + /// Should only include keys that have been positively provided. A runtime + /// may treat unrecognized keys as a warning or error. + fn factor_config_keys(&self) -> impl Iterator; +} + +impl RuntimeConfigSource for () { + fn get_config( + &self, + _factor_config_key: &str, + ) -> anyhow::Result> { + Ok(None) + } + + fn factor_config_keys(&self) -> impl Iterator { + std::iter::empty() + } +} diff --git a/crates/factors/src/spin_factors.rs b/crates/factors/src/spin_factors.rs new file mode 100644 index 0000000000..43887aaa5f --- /dev/null +++ b/crates/factors/src/spin_factors.rs @@ -0,0 +1,109 @@ +use std::marker::PhantomData; + +use anyhow::Context; + +use crate::Factor; + +// TODO(lann): Most of the unsafe shenanigans here probably aren't worth it; +// consider replacing with e.g. `Any::downcast`. + +/// Implemented by `#[derive(SpinFactors)]` +pub trait SpinFactors: Sized { + type AppConfigs; + type InstancePreparers; + type InstanceState: Send + 'static; + + #[doc(hidden)] + unsafe fn instance_preparer_offset() -> Option; + + #[doc(hidden)] + unsafe fn instance_state_offset() -> Option; + + fn app_config(app_configs: &Self::AppConfigs) -> Option<&T::AppConfig>; + + fn instance_state_getter() -> Option> { + let offset = unsafe { Self::instance_state_offset::()? }; + Some(Getter { + offset, + _phantom: PhantomData, + }) + } + + fn instance_state_getter2( + ) -> Option> { + let offset1 = unsafe { Self::instance_state_offset::()? }; + let offset2 = unsafe { Self::instance_state_offset::()? }; + assert_ne!( + offset1, offset2, + "instance_state_getter2 with same factor twice would alias" + ); + Some(Getter2 { + offset1, + offset2, + _phantom: PhantomData, + }) + } + + fn instance_preparer_mut( + preparers: &mut Self::InstancePreparers, + ) -> crate::Result> { + unsafe { + let offset = Self::instance_preparer_offset::().context("no such factor")?; + let ptr = preparers as *mut Self::InstancePreparers; + let opt = &mut *ptr.add(offset).cast::>(); + Ok(opt.as_mut()) + } + } +} + +pub struct Getter { + pub(crate) offset: usize, + pub(crate) _phantom: PhantomData &mut U>, +} + +impl Getter { + pub fn get_mut<'a>(&self, container: &'a mut T) -> &'a mut U { + let ptr = container as *mut T; + unsafe { &mut *ptr.add(self.offset).cast::() } + } +} + +impl Clone for Getter { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Getter {} + +pub struct Getter2 { + pub(crate) offset1: usize, + pub(crate) offset2: usize, + #[allow(clippy::type_complexity)] + pub(crate) _phantom: PhantomData (&mut U, &mut V)>, +} + +impl Getter2 { + pub fn get_mut<'a>(&self, container: &'a mut T) -> (&'a mut U, &'a mut V) + where + T: 'static, + U: 'static, + V: 'static, + { + let ptr = container as *mut T; + unsafe { + ( + &mut *ptr.add(self.offset1).cast::(), + &mut *ptr.add(self.offset2).cast::(), + ) + } + } +} + +impl Clone for Getter2 { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Getter2 {} diff --git a/crates/factors/tests/smoke.rs b/crates/factors/tests/smoke.rs index a4157bc6a2..cf341e4662 100644 --- a/crates/factors/tests/smoke.rs +++ b/crates/factors/tests/smoke.rs @@ -43,7 +43,7 @@ fn main() -> anyhow::Result<()> { .init(Some(&mut linker), Some(&mut module_linker)) .unwrap(); - let configured_app = factors.configure_app(app).unwrap(); + let configured_app = factors.configure_app(app, ()).unwrap(); let data = factors.build_store_data(&configured_app, "test").unwrap(); let mut store = wasmtime::Store::new(&engine, data); From 5985dcb9ab084a75598f37d346bfed2ae1db9a7e Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Thu, 6 Jun 2024 13:47:49 -0400 Subject: [PATCH 10/22] factors: Rename SpinFactors->RuntimeFactors And shorten some generic params. Signed-off-by: Lann Martin --- crates/factor-outbound-networking/src/lib.rs | 14 ++-- crates/factor-variables/src/lib.rs | 16 ++--- crates/factor-wasi/src/lib.rs | 8 +-- crates/factor-wasi/src/preview1.rs | 8 +-- crates/factors-derive/src/lib.rs | 12 ++-- crates/factors/src/factor.rs | 75 ++++++++++---------- crates/factors/src/instance_preparer.rs | 54 +++++++------- crates/factors/src/lib.rs | 8 +-- crates/factors/src/spin_factors.rs | 32 ++++----- crates/factors/tests/smoke.rs | 4 +- 10 files changed, 114 insertions(+), 117 deletions(-) diff --git a/crates/factor-outbound-networking/src/lib.rs b/crates/factor-outbound-networking/src/lib.rs index 7387d69a21..5013725ec9 100644 --- a/crates/factor-outbound-networking/src/lib.rs +++ b/crates/factor-outbound-networking/src/lib.rs @@ -9,22 +9,22 @@ use spin_factor_wasi::WasiFactor; use spin_factors::{ anyhow::{self, Context}, ConfigureAppContext, Factor, FactorInstancePreparer, InstancePreparers, PrepareContext, - RuntimeConfig, SpinFactors, + RuntimeConfig, RuntimeFactors, }; use spin_outbound_networking::{AllowedHostsConfig, ALLOWED_HOSTS_KEY}; pub struct OutboundNetworkingFactor; impl Factor for OutboundNetworkingFactor { - type AppConfig = AppConfig; + type AppState = AppState; type InstancePreparer = InstancePreparer; type InstanceState = (); - fn configure_app( + fn configure_app( &self, ctx: ConfigureAppContext, _runtime_config: &mut impl RuntimeConfig, - ) -> anyhow::Result { + ) -> anyhow::Result { // Extract allowed_outbound_hosts for all components let component_allowed_hosts = ctx .app() @@ -40,14 +40,14 @@ impl Factor for OutboundNetworkingFactor { )) }) .collect::>()?; - Ok(AppConfig { + Ok(AppState { component_allowed_hosts, }) } } #[derive(Default)] -pub struct AppConfig { +pub struct AppState { component_allowed_hosts: HashMap>, } @@ -58,7 +58,7 @@ pub struct InstancePreparer { } impl FactorInstancePreparer for InstancePreparer { - fn new( + fn new( ctx: PrepareContext, mut preparers: InstancePreparers, ) -> anyhow::Result { diff --git a/crates/factor-variables/src/lib.rs b/crates/factor-variables/src/lib.rs index 423c8bba8d..68b1da814e 100644 --- a/crates/factor-variables/src/lib.rs +++ b/crates/factor-variables/src/lib.rs @@ -3,18 +3,18 @@ use std::sync::Arc; use spin_expressions::ProviderResolver; use spin_factors::{ anyhow, ConfigureAppContext, Factor, FactorInstancePreparer, InitContext, InstancePreparers, - PrepareContext, RuntimeConfig, SpinFactors, + PrepareContext, RuntimeConfig, RuntimeFactors, }; use spin_world::{async_trait, v1::config as v1_config, v2::variables}; pub struct VariablesFactor; impl Factor for VariablesFactor { - type AppConfig = AppConfig; + type AppState = AppState; type InstancePreparer = InstancePreparer; type InstanceState = InstanceState; - fn init( + fn init( &mut self, mut ctx: InitContext, ) -> anyhow::Result<()> { @@ -23,11 +23,11 @@ impl Factor for VariablesFactor { Ok(()) } - fn configure_app( + fn configure_app( &self, ctx: ConfigureAppContext, _runtime_config: &mut impl RuntimeConfig, - ) -> anyhow::Result { + ) -> anyhow::Result { let app = ctx.app(); let mut resolver = ProviderResolver::new(app.variables().map(|(key, val)| (key.clone(), val.clone())))?; @@ -38,14 +38,14 @@ impl Factor for VariablesFactor { )?; } // TODO: add providers from runtime config - Ok(AppConfig { + Ok(AppState { resolver: Arc::new(resolver), }) } } #[derive(Default)] -pub struct AppConfig { +pub struct AppState { resolver: Arc, } @@ -60,7 +60,7 @@ impl InstancePreparer { } impl FactorInstancePreparer for InstancePreparer { - fn new( + fn new( ctx: PrepareContext, _preparers: InstancePreparers, ) -> anyhow::Result { diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs index 6320682dac..7af726ff78 100644 --- a/crates/factor-wasi/src/lib.rs +++ b/crates/factor-wasi/src/lib.rs @@ -4,7 +4,7 @@ use std::{future::Future, net::SocketAddr, path::Path}; use spin_factors::{ anyhow, AppComponent, Factor, FactorInstancePreparer, InitContext, InstancePreparers, - PrepareContext, SpinFactors, + PrepareContext, RuntimeFactors, }; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; @@ -21,11 +21,11 @@ impl WasiFactor { } impl Factor for WasiFactor { - type AppConfig = (); + type AppState = (); type InstancePreparer = InstancePreparer; type InstanceState = InstanceState; - fn init( + fn init( &mut self, mut ctx: InitContext, ) -> anyhow::Result<()> { @@ -124,7 +124,7 @@ pub struct InstancePreparer { impl FactorInstancePreparer for InstancePreparer { // NOTE: Replaces WASI parts of AppComponent::apply_store_config - fn new( + fn new( ctx: PrepareContext, _preparers: InstancePreparers, ) -> anyhow::Result { diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs index 426ebff6df..88a6bc5b68 100644 --- a/crates/factor-wasi/src/preview1.rs +++ b/crates/factor-wasi/src/preview1.rs @@ -1,17 +1,17 @@ use spin_factors::{ anyhow, Factor, FactorInstancePreparer, InitContext, InstancePreparers, PrepareContext, - SpinFactors, + RuntimeFactors, }; use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder}; pub struct WasiPreview1Factor; impl Factor for WasiPreview1Factor { - type AppConfig = (); + type AppState = (); type InstancePreparer = InstancePreparer; type InstanceState = WasiP1Ctx; - fn init( + fn init( &mut self, mut ctx: InitContext, ) -> anyhow::Result<()> { @@ -24,7 +24,7 @@ pub struct InstancePreparer { } impl FactorInstancePreparer for InstancePreparer { - fn new( + fn new( _ctx: PrepareContext, _preparers: InstancePreparers, ) -> anyhow::Result { diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index 399606708c..62439bcca8 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -2,7 +2,7 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{parse_macro_input, Data, DeriveInput, Error}; -#[proc_macro_derive(SpinFactors)] +#[proc_macro_derive(RuntimeFactors)] pub fn derive_factors(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); let expanded = expand_factors(&input).unwrap_or_else(|err| err.into_compile_error()); @@ -20,7 +20,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { let name = &input.ident; let vis = &input.vis; - let app_configs_name = format_ident!("{name}AppConfigs"); + let app_configs_name = format_ident!("{name}AppState"); let preparers_name = format_ident!("{name}InstancePreparers"); let state_name = format_ident!("{name}InstanceState"); @@ -133,8 +133,8 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { } - impl #factors_path::SpinFactors for #name { - type AppConfigs = #app_configs_name; + impl #factors_path::RuntimeFactors for #name { + type AppState = #app_configs_name; type InstancePreparers = #preparers_name; type InstanceState = #state_name; @@ -159,7 +159,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { } - fn app_config(app_configs: &Self::AppConfigs) -> Option<&T::AppConfig> { + fn app_config(app_configs: &Self::AppState) -> Option<&T::AppState> { let type_id = #TypeId::of::(); #( if type_id == #TypeId::of::<#factor_types>() { @@ -172,7 +172,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { #vis struct #app_configs_name { #( - pub #factor_names: Option<<#factor_types as #Factor>::AppConfig>, + pub #factor_names: Option<<#factor_types as #Factor>::AppState>, )* } diff --git a/crates/factors/src/factor.rs b/crates/factors/src/factor.rs index 552e844171..162ddb8d6a 100644 --- a/crates/factors/src/factor.rs +++ b/crates/factors/src/factor.rs @@ -2,13 +2,13 @@ use std::any::Any; use anyhow::Context; -use crate::{App, FactorInstancePreparer, Linker, ModuleLinker, RuntimeConfig, SpinFactors}; +use crate::{App, FactorInstancePreparer, Linker, ModuleLinker, RuntimeConfig, RuntimeFactors}; pub trait Factor: Any + Sized { - /// Per-app configuration for this factor. + /// Per-app state for this factor. /// /// See [`Factor::configure_app`]. - type AppConfig: Default; + type AppState: Default; /// The [`FactorInstancePreparer`] for this factor. type InstancePreparer: FactorInstancePreparer; @@ -20,10 +20,7 @@ pub trait Factor: Any + Sized { /// Initializes this Factor for a runtime. This will be called at most once, /// before any call to [`FactorInstancePreparer::new`] - fn init( - &mut self, - mut ctx: InitContext, - ) -> anyhow::Result<()> { + fn init(&mut self, mut ctx: InitContext) -> anyhow::Result<()> { // TODO: Should `ctx` always be immut? Rename this param/type? _ = &mut ctx; Ok(()) @@ -33,33 +30,33 @@ pub trait Factor: Any + Sized { /// [`App`]. A runtime may - but is not required to - reuse the returned /// config across multiple instances. Note that this may be called without /// any call to `init` in cases where only validation is needed. - fn configure_app( + fn configure_app( &self, - ctx: ConfigureAppContext, + ctx: ConfigureAppContext, _runtime_config: &mut impl RuntimeConfig, - ) -> anyhow::Result { + ) -> anyhow::Result { _ = ctx; Ok(Default::default()) } } -pub(crate) type GetDataFn = - fn(&mut ::InstanceState) -> &mut ::InstanceState; +pub(crate) type GetDataFn = + fn(&mut ::InstanceState) -> &mut ::InstanceState; /// An InitContext is passed to [`Factor::init`], giving access to the global /// common [`wasmtime::component::Linker`]. -pub struct InitContext<'a, Factors: SpinFactors, T: Factor> { - pub(crate) linker: Option<&'a mut Linker>, - pub(crate) module_linker: Option<&'a mut ModuleLinker>, - pub(crate) get_data: GetDataFn, +pub struct InitContext<'a, T: RuntimeFactors, F: Factor> { + pub(crate) linker: Option<&'a mut Linker>, + pub(crate) module_linker: Option<&'a mut ModuleLinker>, + pub(crate) get_data: GetDataFn, } -impl<'a, Factors: SpinFactors, T: Factor> InitContext<'a, Factors, T> { +impl<'a, T: RuntimeFactors, F: Factor> InitContext<'a, T, F> { #[doc(hidden)] pub fn new( - linker: Option<&'a mut Linker>, - module_linker: Option<&'a mut ModuleLinker>, - get_data: GetDataFn, + linker: Option<&'a mut Linker>, + module_linker: Option<&'a mut ModuleLinker>, + get_data: GetDataFn, ) -> Self { Self { linker, @@ -68,23 +65,23 @@ impl<'a, Factors: SpinFactors, T: Factor> InitContext<'a, Factors, T> { } } - pub fn linker(&mut self) -> Option<&mut Linker> { + pub fn linker(&mut self) -> Option<&mut Linker> { self.linker.as_deref_mut() } - pub fn module_linker(&mut self) -> Option<&mut ModuleLinker> { + pub fn module_linker(&mut self) -> Option<&mut ModuleLinker> { self.module_linker.as_deref_mut() } - pub fn get_data_fn(&self) -> GetDataFn { + pub fn get_data_fn(&self) -> GetDataFn { self.get_data } pub fn link_bindings( &mut self, add_to_linker: impl Fn( - &mut Linker, - fn(&mut Factors::InstanceState) -> &mut T::InstanceState, + &mut Linker, + fn(&mut T::InstanceState) -> &mut F::InstanceState, ) -> anyhow::Result<()>, ) -> anyhow::Result<()> where { @@ -98,8 +95,8 @@ where { pub fn link_module_bindings( &mut self, add_to_linker: impl Fn( - &mut ModuleLinker, - fn(&mut Factors::InstanceState) -> &mut T::InstanceState, + &mut ModuleLinker, + fn(&mut T::InstanceState) -> &mut F::InstanceState, ) -> anyhow::Result<()>, ) -> anyhow::Result<()> where { @@ -111,14 +108,14 @@ where { } } -pub struct ConfigureAppContext<'a, Factors: SpinFactors> { +pub struct ConfigureAppContext<'a, T: RuntimeFactors> { pub(crate) app: &'a App, - pub(crate) app_configs: &'a Factors::AppConfigs, + pub(crate) app_configs: &'a T::AppState, } -impl<'a, Factors: SpinFactors> ConfigureAppContext<'a, Factors> { +impl<'a, T: RuntimeFactors> ConfigureAppContext<'a, T> { #[doc(hidden)] - pub fn new(app: &'a App, app_configs: &'a Factors::AppConfigs) -> Self { + pub fn new(app: &'a App, app_configs: &'a T::AppState) -> Self { Self { app, app_configs } } @@ -126,19 +123,19 @@ impl<'a, Factors: SpinFactors> ConfigureAppContext<'a, Factors> { self.app } - pub fn app_config(&self) -> crate::Result<&T::AppConfig> { - Factors::app_config::(self.app_configs).context("no such factor") + pub fn app_config(&self) -> crate::Result<&F::AppState> { + T::app_config::(self.app_configs).context("no such factor") } } -pub struct ConfiguredApp { +pub struct ConfiguredApp { app: App, - app_configs: Factors::AppConfigs, + app_configs: T::AppState, } -impl ConfiguredApp { +impl ConfiguredApp { #[doc(hidden)] - pub fn new(app: App, app_configs: Factors::AppConfigs) -> Self { + pub fn new(app: App, app_configs: T::AppState) -> Self { Self { app, app_configs } } @@ -146,7 +143,7 @@ impl ConfiguredApp { &self.app } - pub fn app_config(&self) -> crate::Result<&T::AppConfig> { - Factors::app_config::(&self.app_configs).context("no such factor") + pub fn app_config(&self) -> crate::Result<&F::AppState> { + T::app_config::(&self.app_configs).context("no such factor") } } diff --git a/crates/factors/src/instance_preparer.rs b/crates/factors/src/instance_preparer.rs index 43fb01fce3..8cec89f351 100644 --- a/crates/factors/src/instance_preparer.rs +++ b/crates/factors/src/instance_preparer.rs @@ -1,30 +1,30 @@ use anyhow::Context; -use crate::{AppComponent, Factor, SpinFactors}; +use crate::{AppComponent, Factor, RuntimeFactors}; -pub trait FactorInstancePreparer: Sized { +pub trait FactorInstancePreparer: Sized { /// Returns a new instance of this preparer for the given [`Factor`]. - fn new( - ctx: PrepareContext, - _preparers: InstancePreparers, + fn new( + ctx: PrepareContext, + _preparers: InstancePreparers, ) -> anyhow::Result; /// Returns a new instance of the associated [`Factor::InstanceState`]. - fn prepare(self) -> anyhow::Result; + fn prepare(self) -> anyhow::Result; } -impl FactorInstancePreparer for () +impl FactorInstancePreparer for () where - T::InstanceState: Default, + F::InstanceState: Default, { - fn new( - _ctx: PrepareContext, - _preparers: InstancePreparers, + fn new( + _ctx: PrepareContext, + _preparers: InstancePreparers, ) -> anyhow::Result { Ok(()) } - fn prepare(self) -> anyhow::Result { + fn prepare(self) -> anyhow::Result { Ok(Default::default()) } } @@ -32,17 +32,17 @@ where /// A PrepareContext is passed to [`FactorInstancePreparer::new`], giving access /// to any already-initialized [`FactorInstancePreparer`]s, allowing for /// inter-[`Factor`] dependencies. -pub struct PrepareContext<'a, T: Factor> { - pub(crate) factor: &'a T, - pub(crate) app_config: &'a T::AppConfig, +pub struct PrepareContext<'a, F: Factor> { + pub(crate) factor: &'a F, + pub(crate) app_config: &'a F::AppState, pub(crate) app_component: &'a AppComponent<'a>, } -impl<'a, T: Factor> PrepareContext<'a, T> { +impl<'a, F: Factor> PrepareContext<'a, F> { #[doc(hidden)] pub fn new( - factor: &'a T, - app_config: &'a T::AppConfig, + factor: &'a F, + app_config: &'a F::AppState, app_component: &'a AppComponent, ) -> Self { Self { @@ -52,11 +52,11 @@ impl<'a, T: Factor> PrepareContext<'a, T> { } } - pub fn factor(&self) -> &T { + pub fn factor(&self) -> &F { self.factor } - pub fn app_config(&self) -> &T::AppConfig { + pub fn app_config(&self) -> &F::AppState { self.app_config } @@ -65,22 +65,22 @@ impl<'a, T: Factor> PrepareContext<'a, T> { } } -pub struct InstancePreparers<'a, Factors: SpinFactors> { - pub(crate) inner: &'a mut Factors::InstancePreparers, +pub struct InstancePreparers<'a, T: RuntimeFactors> { + pub(crate) inner: &'a mut T::InstancePreparers, } -impl<'a, Factors: SpinFactors> InstancePreparers<'a, Factors> { +impl<'a, T: RuntimeFactors> InstancePreparers<'a, T> { #[doc(hidden)] - pub fn new(inner: &'a mut Factors::InstancePreparers) -> Self { + pub fn new(inner: &'a mut T::InstancePreparers) -> Self { Self { inner } } /// Returns a already-initialized preparer for the given [`Factor`]. /// - /// Fails if the current [`SpinFactors`] does not include the given + /// Fails if the current [`RuntimeFactors`] does not include the given /// [`Factor`] or if the given [`Factor`]'s preparer has not been /// initialized yet (because it is sequenced after this factor). - pub fn get_mut(&mut self) -> crate::Result<&mut T::InstancePreparer> { - Factors::instance_preparer_mut::(self.inner)?.context("preparer not initialized") + pub fn get_mut(&mut self) -> crate::Result<&mut F::InstancePreparer> { + T::instance_preparer_mut::(self.inner)?.context("preparer not initialized") } } diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index 0d18109cfd..a23644e2b3 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -6,17 +6,17 @@ mod spin_factors; pub use anyhow; pub use wasmtime; -pub use spin_factors_derive::SpinFactors; +pub use spin_factors_derive::RuntimeFactors; pub use crate::{ factor::{ConfigureAppContext, ConfiguredApp, Factor, InitContext}, instance_preparer::{FactorInstancePreparer, InstancePreparers, PrepareContext}, runtime_config::{RuntimeConfig, RuntimeConfigSource}, - spin_factors::SpinFactors, + spin_factors::RuntimeFactors, }; -pub type Linker = wasmtime::component::Linker<::InstanceState>; -pub type ModuleLinker = wasmtime::Linker<::InstanceState>; +pub type Linker = wasmtime::component::Linker<::InstanceState>; +pub type ModuleLinker = wasmtime::Linker<::InstanceState>; // Temporary wrappers while refactoring pub type App = spin_app::App<'static, spin_app::InertLoader>; diff --git a/crates/factors/src/spin_factors.rs b/crates/factors/src/spin_factors.rs index 43887aaa5f..fd961e6791 100644 --- a/crates/factors/src/spin_factors.rs +++ b/crates/factors/src/spin_factors.rs @@ -7,32 +7,32 @@ use crate::Factor; // TODO(lann): Most of the unsafe shenanigans here probably aren't worth it; // consider replacing with e.g. `Any::downcast`. -/// Implemented by `#[derive(SpinFactors)]` -pub trait SpinFactors: Sized { - type AppConfigs; +/// Implemented by `#[derive(RuntimeFactors)]` +pub trait RuntimeFactors: Sized { + type AppState; type InstancePreparers; type InstanceState: Send + 'static; #[doc(hidden)] - unsafe fn instance_preparer_offset() -> Option; + unsafe fn instance_preparer_offset() -> Option; #[doc(hidden)] - unsafe fn instance_state_offset() -> Option; + unsafe fn instance_state_offset() -> Option; - fn app_config(app_configs: &Self::AppConfigs) -> Option<&T::AppConfig>; + fn app_config(app_configs: &Self::AppState) -> Option<&F::AppState>; - fn instance_state_getter() -> Option> { - let offset = unsafe { Self::instance_state_offset::()? }; + fn instance_state_getter() -> Option> { + let offset = unsafe { Self::instance_state_offset::()? }; Some(Getter { offset, _phantom: PhantomData, }) } - fn instance_state_getter2( - ) -> Option> { - let offset1 = unsafe { Self::instance_state_offset::()? }; - let offset2 = unsafe { Self::instance_state_offset::()? }; + fn instance_state_getter2( + ) -> Option> { + let offset1 = unsafe { Self::instance_state_offset::()? }; + let offset2 = unsafe { Self::instance_state_offset::()? }; assert_ne!( offset1, offset2, "instance_state_getter2 with same factor twice would alias" @@ -44,13 +44,13 @@ pub trait SpinFactors: Sized { }) } - fn instance_preparer_mut( + fn instance_preparer_mut( preparers: &mut Self::InstancePreparers, - ) -> crate::Result> { + ) -> crate::Result> { unsafe { - let offset = Self::instance_preparer_offset::().context("no such factor")?; + let offset = Self::instance_preparer_offset::().context("no such factor")?; let ptr = preparers as *mut Self::InstancePreparers; - let opt = &mut *ptr.add(offset).cast::>(); + let opt = &mut *ptr.add(offset).cast::>(); Ok(opt.as_mut()) } } diff --git a/crates/factors/tests/smoke.rs b/crates/factors/tests/smoke.rs index cf341e4662..c848be7467 100644 --- a/crates/factors/tests/smoke.rs +++ b/crates/factors/tests/smoke.rs @@ -2,9 +2,9 @@ use spin_app::App; use spin_factor_outbound_networking::OutboundNetworkingFactor; use spin_factor_variables::VariablesFactor; use spin_factor_wasi::{preview1::WasiPreview1Factor, DummyFilesMounter, WasiFactor}; -use spin_factors::SpinFactors; +use spin_factors::RuntimeFactors; -#[derive(SpinFactors)] +#[derive(RuntimeFactors)] struct Factors { wasi: WasiFactor, wasip1: WasiPreview1Factor, From b774ac0d4c5331554e4868ca9af8bdf657888dc1 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Thu, 6 Jun 2024 14:39:43 -0400 Subject: [PATCH 11/22] factors: Merge FactorInstancePreparer trait into Factor Signed-off-by: Lann Martin --- crates/factor-outbound-networking/src/lib.rs | 56 +++++++++------- crates/factor-variables/src/lib.rs | 44 ++++++------- crates/factor-wasi/src/lib.rs | 66 ++++++++++--------- crates/factor-wasi/src/preview1.rs | 24 +++---- crates/factors-derive/src/lib.rs | 5 +- crates/factors/src/factor.rs | 18 ++++- crates/factors/src/lib.rs | 4 +- .../src/{instance_preparer.rs => prepare.rs} | 27 -------- 8 files changed, 119 insertions(+), 125 deletions(-) rename crates/factors/src/{instance_preparer.rs => prepare.rs} (69%) diff --git a/crates/factor-outbound-networking/src/lib.rs b/crates/factor-outbound-networking/src/lib.rs index 5013725ec9..fb48ba1c82 100644 --- a/crates/factor-outbound-networking/src/lib.rs +++ b/crates/factor-outbound-networking/src/lib.rs @@ -8,8 +8,7 @@ use spin_factor_variables::VariablesFactor; use spin_factor_wasi::WasiFactor; use spin_factors::{ anyhow::{self, Context}, - ConfigureAppContext, Factor, FactorInstancePreparer, InstancePreparers, PrepareContext, - RuntimeConfig, RuntimeFactors, + ConfigureAppContext, Factor, InstancePreparers, PrepareContext, RuntimeConfig, RuntimeFactors, }; use spin_outbound_networking::{AllowedHostsConfig, ALLOWED_HOSTS_KEY}; @@ -44,24 +43,11 @@ impl Factor for OutboundNetworkingFactor { component_allowed_hosts, }) } -} - -#[derive(Default)] -pub struct AppState { - component_allowed_hosts: HashMap>, -} - -type SharedFutureResult = Shared>>>; - -pub struct InstancePreparer { - allowed_hosts_future: SharedFutureResult, -} -impl FactorInstancePreparer for InstancePreparer { - fn new( - ctx: PrepareContext, - mut preparers: InstancePreparers, - ) -> anyhow::Result { + fn create_preparer( + ctx: PrepareContext, + mut preparers: InstancePreparers, + ) -> anyhow::Result { let hosts = ctx .app_config() .component_allowed_hosts @@ -101,18 +87,40 @@ impl FactorInstancePreparer for InstancePreparer { } } }); - Ok(Self { - allowed_hosts_future, - }) + Ok(InstancePreparer::new(allowed_hosts_future)) } - fn prepare(self) -> anyhow::Result<::InstanceState> { + fn prepare( + &self, + _preparer: InstancePreparer, + ) -> anyhow::Result<::InstanceState> { Ok(()) } } +#[derive(Default)] +pub struct AppState { + component_allowed_hosts: HashMap>, +} + +type SharedFutureResult = Shared>>>; + +#[derive(Default)] +pub struct InstancePreparer { + allowed_hosts_future: Option>, +} + impl InstancePreparer { + fn new(allowed_hosts_future: SharedFutureResult) -> Self { + Self { + allowed_hosts_future: Some(allowed_hosts_future), + } + } + pub async fn resolve_allowed_hosts(&self) -> Arc> { - self.allowed_hosts_future.clone().await + self.allowed_hosts_future + .clone() + .expect("allowed_hosts_future not set") + .await } } diff --git a/crates/factor-variables/src/lib.rs b/crates/factor-variables/src/lib.rs index 68b1da814e..ca6eb4253d 100644 --- a/crates/factor-variables/src/lib.rs +++ b/crates/factor-variables/src/lib.rs @@ -2,8 +2,8 @@ use std::sync::Arc; use spin_expressions::ProviderResolver; use spin_factors::{ - anyhow, ConfigureAppContext, Factor, FactorInstancePreparer, InitContext, InstancePreparers, - PrepareContext, RuntimeConfig, RuntimeFactors, + anyhow, ConfigureAppContext, Factor, InitContext, InstancePreparers, PrepareContext, + RuntimeConfig, RuntimeFactors, }; use spin_world::{async_trait, v1::config as v1_config, v2::variables}; @@ -42,6 +42,24 @@ impl Factor for VariablesFactor { resolver: Arc::new(resolver), }) } + + fn create_preparer( + ctx: PrepareContext, + _preparers: InstancePreparers, + ) -> anyhow::Result { + let component_id = ctx.app_component().id().to_string(); + let resolver = ctx.app_config().resolver.clone(); + Ok(InstancePreparer { + state: InstanceState { + component_id, + resolver, + }, + }) + } + + fn prepare(&self, preparer: InstancePreparer) -> anyhow::Result { + Ok(preparer.state) + } } #[derive(Default)] @@ -49,6 +67,7 @@ pub struct AppState { resolver: Arc, } +#[derive(Default)] pub struct InstancePreparer { state: InstanceState, } @@ -59,26 +78,7 @@ impl InstancePreparer { } } -impl FactorInstancePreparer for InstancePreparer { - fn new( - ctx: PrepareContext, - _preparers: InstancePreparers, - ) -> anyhow::Result { - let component_id = ctx.app_component().id().to_string(); - let resolver = ctx.app_config().resolver.clone(); - Ok(Self { - state: InstanceState { - component_id, - resolver, - }, - }) - } - - fn prepare(self) -> anyhow::Result { - Ok(self.state) - } -} - +#[derive(Default)] pub struct InstanceState { component_id: String, resolver: Arc, diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs index 7af726ff78..1e97ab55ff 100644 --- a/crates/factor-wasi/src/lib.rs +++ b/crates/factor-wasi/src/lib.rs @@ -3,8 +3,7 @@ pub mod preview1; use std::{future::Future, net::SocketAddr, path::Path}; use spin_factors::{ - anyhow, AppComponent, Factor, FactorInstancePreparer, InitContext, InstancePreparers, - PrepareContext, RuntimeFactors, + anyhow, AppComponent, Factor, InitContext, InstancePreparers, PrepareContext, RuntimeFactors, }; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; @@ -69,6 +68,36 @@ impl Factor for WasiFactor { } Ok(()) } + + fn create_preparer( + ctx: PrepareContext, + _preparers: InstancePreparers, + ) -> anyhow::Result { + let mut wasi_ctx = WasiCtxBuilder::new(); + + // Apply environment variables + for (key, val) in ctx.app_component().environment() { + wasi_ctx.env(key, val); + } + + // Mount files + let mount_ctx = MountFilesContext { + wasi_ctx: &mut wasi_ctx, + }; + ctx.factor() + .files_mounter + .mount_files(ctx.app_component(), mount_ctx)?; + + Ok(InstancePreparer { wasi_ctx }) + } + + fn prepare(&self, preparer: InstancePreparer) -> anyhow::Result { + let InstancePreparer { mut wasi_ctx } = preparer; + Ok(InstanceState { + ctx: wasi_ctx.build(), + table: Default::default(), + }) + } } pub trait FilesMounter { @@ -122,36 +151,11 @@ pub struct InstancePreparer { wasi_ctx: WasiCtxBuilder, } -impl FactorInstancePreparer for InstancePreparer { - // NOTE: Replaces WASI parts of AppComponent::apply_store_config - fn new( - ctx: PrepareContext, - _preparers: InstancePreparers, - ) -> anyhow::Result { - let mut wasi_ctx = WasiCtxBuilder::new(); - - // Apply environment variables - for (key, val) in ctx.app_component().environment() { - wasi_ctx.env(key, val); +impl Default for InstancePreparer { + fn default() -> Self { + Self { + wasi_ctx: WasiCtxBuilder::new(), } - - // Mount files - let mount_ctx = MountFilesContext { - wasi_ctx: &mut wasi_ctx, - }; - ctx.factor() - .files_mounter - .mount_files(ctx.app_component(), mount_ctx)?; - - Ok(Self { wasi_ctx }) - } - - fn prepare(self) -> anyhow::Result { - let Self { mut wasi_ctx } = self; - Ok(InstanceState { - ctx: wasi_ctx.build(), - table: Default::default(), - }) } } diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs index 88a6bc5b68..ab3a7a76e3 100644 --- a/crates/factor-wasi/src/preview1.rs +++ b/crates/factor-wasi/src/preview1.rs @@ -1,7 +1,4 @@ -use spin_factors::{ - anyhow, Factor, FactorInstancePreparer, InitContext, InstancePreparers, PrepareContext, - RuntimeFactors, -}; +use spin_factors::{anyhow, Factor, InitContext, RuntimeFactors}; use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder}; pub struct WasiPreview1Factor; @@ -17,23 +14,20 @@ impl Factor for WasiPreview1Factor { ) -> anyhow::Result<()> { ctx.link_module_bindings(wasmtime_wasi::preview1::add_to_linker_async) } + + fn prepare(&self, mut preparer: InstancePreparer) -> anyhow::Result { + Ok(preparer.wasi_ctx.build_p1()) + } } pub struct InstancePreparer { wasi_ctx: WasiCtxBuilder, } -impl FactorInstancePreparer for InstancePreparer { - fn new( - _ctx: PrepareContext, - _preparers: InstancePreparers, - ) -> anyhow::Result { - Ok(Self { +impl Default for InstancePreparer { + fn default() -> Self { + Self { wasi_ctx: WasiCtxBuilder::new(), - }) - } - - fn prepare(mut self) -> anyhow::Result { - Ok(self.wasi_ctx.build_p1()) + } } } diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index 62439bcca8..f6dede3f2c 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -112,7 +112,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { }; #( preparers.#factor_names = Some( - #factors_path::FactorInstancePreparer::<#factor_types>::new::<#name>( + #Factor::create_preparer::( #factors_path::PrepareContext::new( &self.#factor_names, configured_app.app_config::<#factor_types>().unwrap(), @@ -124,7 +124,8 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { )* Ok(#state_name { #( - #factor_names: #factors_path::FactorInstancePreparer::<#factor_types>::prepare( + #factor_names: #Factor::prepare( + &self.#factor_names, preparers.#factor_names.unwrap(), )?, )* diff --git a/crates/factors/src/factor.rs b/crates/factors/src/factor.rs index 162ddb8d6a..79b1f86f16 100644 --- a/crates/factors/src/factor.rs +++ b/crates/factors/src/factor.rs @@ -2,7 +2,9 @@ use std::any::Any; use anyhow::Context; -use crate::{App, FactorInstancePreparer, Linker, ModuleLinker, RuntimeConfig, RuntimeFactors}; +use crate::{ + App, InstancePreparers, Linker, ModuleLinker, PrepareContext, RuntimeConfig, RuntimeFactors, +}; pub trait Factor: Any + Sized { /// Per-app state for this factor. @@ -11,7 +13,7 @@ pub trait Factor: Any + Sized { type AppState: Default; /// The [`FactorInstancePreparer`] for this factor. - type InstancePreparer: FactorInstancePreparer; + type InstancePreparer: Default; /// The per-instance state for this factor, constructed by a /// [`FactorInstancePreparer`] and available to any host-provided imports @@ -38,6 +40,18 @@ pub trait Factor: Any + Sized { _ = ctx; Ok(Default::default()) } + + /// Returns a new instance of this preparer for the given [`Factor`]. + fn create_preparer( + ctx: PrepareContext, + _preparers: InstancePreparers, + ) -> anyhow::Result { + _ = ctx; + Ok(Default::default()) + } + + /// Returns a new instance of the associated [`Factor::InstanceState`]. + fn prepare(&self, preparer: Self::InstancePreparer) -> anyhow::Result; } pub(crate) type GetDataFn = diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index a23644e2b3..eb34e6b371 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -1,5 +1,5 @@ mod factor; -mod instance_preparer; +mod prepare; mod runtime_config; mod spin_factors; @@ -10,7 +10,7 @@ pub use spin_factors_derive::RuntimeFactors; pub use crate::{ factor::{ConfigureAppContext, ConfiguredApp, Factor, InitContext}, - instance_preparer::{FactorInstancePreparer, InstancePreparers, PrepareContext}, + prepare::{InstancePreparers, PrepareContext}, runtime_config::{RuntimeConfig, RuntimeConfigSource}, spin_factors::RuntimeFactors, }; diff --git a/crates/factors/src/instance_preparer.rs b/crates/factors/src/prepare.rs similarity index 69% rename from crates/factors/src/instance_preparer.rs rename to crates/factors/src/prepare.rs index 8cec89f351..ccf975e8f3 100644 --- a/crates/factors/src/instance_preparer.rs +++ b/crates/factors/src/prepare.rs @@ -2,33 +2,6 @@ use anyhow::Context; use crate::{AppComponent, Factor, RuntimeFactors}; -pub trait FactorInstancePreparer: Sized { - /// Returns a new instance of this preparer for the given [`Factor`]. - fn new( - ctx: PrepareContext, - _preparers: InstancePreparers, - ) -> anyhow::Result; - - /// Returns a new instance of the associated [`Factor::InstanceState`]. - fn prepare(self) -> anyhow::Result; -} - -impl FactorInstancePreparer for () -where - F::InstanceState: Default, -{ - fn new( - _ctx: PrepareContext, - _preparers: InstancePreparers, - ) -> anyhow::Result { - Ok(()) - } - - fn prepare(self) -> anyhow::Result { - Ok(Default::default()) - } -} - /// A PrepareContext is passed to [`FactorInstancePreparer::new`], giving access /// to any already-initialized [`FactorInstancePreparer`]s, allowing for /// inter-[`Factor`] dependencies. From 14f32411b898a8091afa9fd44d3d9cd1b039e4bd Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Fri, 7 Jun 2024 10:06:55 -0400 Subject: [PATCH 12/22] factors: Add RuntimeConfig Also address a bunch of feedback. Signed-off-by: Lann Martin --- crates/factor-outbound-networking/src/lib.rs | 45 ++++---- crates/factor-variables/src/lib.rs | 40 +++---- crates/factor-wasi/src/lib.rs | 41 ++++--- crates/factor-wasi/src/preview1.rs | 28 +++-- crates/factors-derive/src/lib.rs | 55 +++++----- crates/factors/src/factor.rs | 87 ++++++++------- crates/factors/src/lib.rs | 8 +- crates/factors/src/prepare.rs | 54 +++++---- crates/factors/src/runtime_config.rs | 103 ++++++++---------- .../{spin_factors.rs => runtime_factors.rs} | 26 +++-- 10 files changed, 255 insertions(+), 232 deletions(-) rename crates/factors/src/{spin_factors.rs => runtime_factors.rs} (75%) diff --git a/crates/factor-outbound-networking/src/lib.rs b/crates/factor-outbound-networking/src/lib.rs index fb48ba1c82..e19f6d6078 100644 --- a/crates/factor-outbound-networking/src/lib.rs +++ b/crates/factor-outbound-networking/src/lib.rs @@ -8,21 +8,21 @@ use spin_factor_variables::VariablesFactor; use spin_factor_wasi::WasiFactor; use spin_factors::{ anyhow::{self, Context}, - ConfigureAppContext, Factor, InstancePreparers, PrepareContext, RuntimeConfig, RuntimeFactors, + ConfigureAppContext, Factor, FactorInstanceBuilder, InstanceBuilders, PrepareContext, + RuntimeFactors, }; use spin_outbound_networking::{AllowedHostsConfig, ALLOWED_HOSTS_KEY}; pub struct OutboundNetworkingFactor; impl Factor for OutboundNetworkingFactor { + type RuntimeConfig = (); type AppState = AppState; - type InstancePreparer = InstancePreparer; - type InstanceState = (); + type InstanceBuilder = InstanceBuilder; - fn configure_app( + fn configure_app( &self, - ctx: ConfigureAppContext, - _runtime_config: &mut impl RuntimeConfig, + ctx: ConfigureAppContext, ) -> anyhow::Result { // Extract allowed_outbound_hosts for all components let component_allowed_hosts = ctx @@ -44,17 +44,17 @@ impl Factor for OutboundNetworkingFactor { }) } - fn create_preparer( + fn prepare( ctx: PrepareContext, - mut preparers: InstancePreparers, - ) -> anyhow::Result { + builders: &mut InstanceBuilders, + ) -> anyhow::Result { let hosts = ctx - .app_config() + .app_state() .component_allowed_hosts .get(ctx.app_component().id()) .cloned() .context("missing component allowed hosts")?; - let resolver = preparers.get_mut::()?.resolver().clone(); + let resolver = builders.get_mut::()?.resolver().clone(); let allowed_hosts_future = async move { let prepared = resolver.prepare().await?; AllowedHostsConfig::parse(&hosts, &prepared) @@ -69,7 +69,7 @@ impl Factor for OutboundNetworkingFactor { // )?; // Update Wasi socket allowed ports - let wasi_preparer = preparers.get_mut::()?; + let wasi_preparer = builders.get_mut::()?; let hosts_future = allowed_hosts_future.clone(); wasi_preparer.outbound_socket_addr_check(move |addr| { let hosts_future = hosts_future.clone(); @@ -87,14 +87,7 @@ impl Factor for OutboundNetworkingFactor { } } }); - Ok(InstancePreparer::new(allowed_hosts_future)) - } - - fn prepare( - &self, - _preparer: InstancePreparer, - ) -> anyhow::Result<::InstanceState> { - Ok(()) + Ok(InstanceBuilder::new(allowed_hosts_future)) } } @@ -106,11 +99,11 @@ pub struct AppState { type SharedFutureResult = Shared>>>; #[derive(Default)] -pub struct InstancePreparer { +pub struct InstanceBuilder { allowed_hosts_future: Option>, } -impl InstancePreparer { +impl InstanceBuilder { fn new(allowed_hosts_future: SharedFutureResult) -> Self { Self { allowed_hosts_future: Some(allowed_hosts_future), @@ -124,3 +117,11 @@ impl InstancePreparer { .await } } + +impl FactorInstanceBuilder for InstanceBuilder { + type InstanceState = (); + + fn build(self) -> anyhow::Result { + Ok(()) + } +} diff --git a/crates/factor-variables/src/lib.rs b/crates/factor-variables/src/lib.rs index ca6eb4253d..438e016799 100644 --- a/crates/factor-variables/src/lib.rs +++ b/crates/factor-variables/src/lib.rs @@ -2,17 +2,17 @@ use std::sync::Arc; use spin_expressions::ProviderResolver; use spin_factors::{ - anyhow, ConfigureAppContext, Factor, InitContext, InstancePreparers, PrepareContext, - RuntimeConfig, RuntimeFactors, + anyhow, ConfigureAppContext, Factor, FactorInstanceBuilder, InitContext, InstanceBuilders, + PrepareContext, RuntimeFactors, }; use spin_world::{async_trait, v1::config as v1_config, v2::variables}; pub struct VariablesFactor; impl Factor for VariablesFactor { + type RuntimeConfig = (); type AppState = AppState; - type InstancePreparer = InstancePreparer; - type InstanceState = InstanceState; + type InstanceBuilder = InstanceBuilder; fn init( &mut self, @@ -23,10 +23,9 @@ impl Factor for VariablesFactor { Ok(()) } - fn configure_app( + fn configure_app( &self, - ctx: ConfigureAppContext, - _runtime_config: &mut impl RuntimeConfig, + ctx: ConfigureAppContext, ) -> anyhow::Result { let app = ctx.app(); let mut resolver = @@ -43,23 +42,19 @@ impl Factor for VariablesFactor { }) } - fn create_preparer( + fn prepare( ctx: PrepareContext, - _preparers: InstancePreparers, - ) -> anyhow::Result { + _builders: &mut InstanceBuilders, + ) -> anyhow::Result { let component_id = ctx.app_component().id().to_string(); - let resolver = ctx.app_config().resolver.clone(); - Ok(InstancePreparer { + let resolver = ctx.app_state().resolver.clone(); + Ok(InstanceBuilder { state: InstanceState { component_id, resolver, }, }) } - - fn prepare(&self, preparer: InstancePreparer) -> anyhow::Result { - Ok(preparer.state) - } } #[derive(Default)] @@ -67,17 +62,24 @@ pub struct AppState { resolver: Arc, } -#[derive(Default)] -pub struct InstancePreparer { +pub struct InstanceBuilder { state: InstanceState, } -impl InstancePreparer { +impl InstanceBuilder { pub fn resolver(&self) -> &Arc { &self.state.resolver } } +impl FactorInstanceBuilder for InstanceBuilder { + type InstanceState = InstanceState; + + fn build(self) -> anyhow::Result { + Ok(self.state) + } +} + #[derive(Default)] pub struct InstanceState { component_id: String, diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs index 1e97ab55ff..30597e1079 100644 --- a/crates/factor-wasi/src/lib.rs +++ b/crates/factor-wasi/src/lib.rs @@ -3,7 +3,8 @@ pub mod preview1; use std::{future::Future, net::SocketAddr, path::Path}; use spin_factors::{ - anyhow, AppComponent, Factor, InitContext, InstancePreparers, PrepareContext, RuntimeFactors, + anyhow, AppComponent, Factor, FactorInstanceBuilder, InitContext, InstanceBuilders, + PrepareContext, RuntimeFactors, }; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; @@ -20,9 +21,9 @@ impl WasiFactor { } impl Factor for WasiFactor { + type RuntimeConfig = (); type AppState = (); - type InstancePreparer = InstancePreparer; - type InstanceState = InstanceState; + type InstanceBuilder = InstanceBuilder; fn init( &mut self, @@ -69,10 +70,10 @@ impl Factor for WasiFactor { Ok(()) } - fn create_preparer( + fn prepare( ctx: PrepareContext, - _preparers: InstancePreparers, - ) -> anyhow::Result { + _builders: &mut InstanceBuilders, + ) -> anyhow::Result { let mut wasi_ctx = WasiCtxBuilder::new(); // Apply environment variables @@ -88,15 +89,7 @@ impl Factor for WasiFactor { .files_mounter .mount_files(ctx.app_component(), mount_ctx)?; - Ok(InstancePreparer { wasi_ctx }) - } - - fn prepare(&self, preparer: InstancePreparer) -> anyhow::Result { - let InstancePreparer { mut wasi_ctx } = preparer; - Ok(InstanceState { - ctx: wasi_ctx.build(), - table: Default::default(), - }) + Ok(InstanceBuilder { wasi_ctx }) } } @@ -147,19 +140,23 @@ impl<'a> MountFilesContext<'a> { } } -pub struct InstancePreparer { +pub struct InstanceBuilder { wasi_ctx: WasiCtxBuilder, } -impl Default for InstancePreparer { - fn default() -> Self { - Self { - wasi_ctx: WasiCtxBuilder::new(), - } +impl FactorInstanceBuilder for InstanceBuilder { + type InstanceState = InstanceState; + + fn build(self) -> anyhow::Result { + let InstanceBuilder { mut wasi_ctx } = self; + Ok(InstanceState { + ctx: wasi_ctx.build(), + table: Default::default(), + }) } } -impl InstancePreparer { +impl InstanceBuilder { pub fn outbound_socket_addr_check(&mut self, check: F) where F: Fn(SocketAddr) -> Fut + Send + Sync + Clone + 'static, diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs index ab3a7a76e3..1a25260bec 100644 --- a/crates/factor-wasi/src/preview1.rs +++ b/crates/factor-wasi/src/preview1.rs @@ -1,12 +1,12 @@ -use spin_factors::{anyhow, Factor, InitContext, RuntimeFactors}; +use spin_factors::{anyhow, Factor, FactorInstanceBuilder, InitContext, RuntimeFactors}; use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder}; pub struct WasiPreview1Factor; impl Factor for WasiPreview1Factor { + type RuntimeConfig = (); type AppState = (); - type InstancePreparer = InstancePreparer; - type InstanceState = WasiP1Ctx; + type InstanceBuilder = InstanceBuilder; fn init( &mut self, @@ -15,19 +15,25 @@ impl Factor for WasiPreview1Factor { ctx.link_module_bindings(wasmtime_wasi::preview1::add_to_linker_async) } - fn prepare(&self, mut preparer: InstancePreparer) -> anyhow::Result { - Ok(preparer.wasi_ctx.build_p1()) + fn prepare( + _ctx: spin_factors::PrepareContext, + _builders: &mut spin_factors::InstanceBuilders, + ) -> anyhow::Result { + Ok(InstanceBuilder { + wasi_ctx: WasiCtxBuilder::new(), + }) } } -pub struct InstancePreparer { +pub struct InstanceBuilder { wasi_ctx: WasiCtxBuilder, } -impl Default for InstancePreparer { - fn default() -> Self { - Self { - wasi_ctx: WasiCtxBuilder::new(), - } +impl FactorInstanceBuilder for InstanceBuilder { + type InstanceState = WasiP1Ctx; + + fn build(self) -> anyhow::Result { + let Self { mut wasi_ctx } = self; + Ok(wasi_ctx.build_p1()) } } diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index f6dede3f2c..deb03e7560 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -20,8 +20,8 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { let name = &input.ident; let vis = &input.vis; - let app_configs_name = format_ident!("{name}AppState"); - let preparers_name = format_ident!("{name}InstancePreparers"); + let app_state_name = format_ident!("{name}AppState"); + let builders_name = format_ident!("{name}InstanceBuilders"); let state_name = format_ident!("{name}InstanceState"); if !input.generics.params.is_empty() { @@ -61,6 +61,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { let Factor = quote!(#factors_path::Factor); let ConfiguredApp = quote!(#factors_path::ConfiguredApp); let RuntimeConfigTracker = quote!(#factors_path::__internal::RuntimeConfigTracker); + let FactorInstanceBuilder = quote!(#factors_path::FactorInstanceBuilder); Ok(quote! { impl #name { @@ -87,46 +88,48 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { app: #factors_path::App, runtime_config: impl #factors_path::RuntimeConfigSource ) -> #Result<#ConfiguredApp> { - let mut app_configs = #app_configs_name { + let mut app_state = #app_state_name { #( #factor_names: None, )* }; - let mut runtime_config = #RuntimeConfigTracker::new(runtime_config); + let mut runtime_config_tracker = #RuntimeConfigTracker::new(runtime_config); #( - app_configs.#factor_names = Some( + app_state.#factor_names = Some( #Factor::configure_app( &self.#factor_names, - #factors_path::ConfigureAppContext::::new(&app, &app_configs), - &mut runtime_config, + #factors_path::ConfigureAppContext::::new( + &app, + &app_state, + &mut runtime_config_tracker, + )?, )? ); )* - Ok(#ConfiguredApp::new(app, app_configs)) + Ok(#ConfiguredApp::new(app, app_state)) } pub fn build_store_data(&self, configured_app: &#ConfiguredApp, component_id: &str) -> #Result<#state_name> { let app_component = configured_app.app().get_component(component_id).ok_or_else(|| { #wasmtime::Error::msg("unknown component") })?; - let mut preparers = #preparers_name { + let mut builders = #builders_name { #( #factor_names: None, )* }; #( - preparers.#factor_names = Some( - #Factor::create_preparer::( + builders.#factor_names = Some( + #Factor::prepare::( #factors_path::PrepareContext::new( &self.#factor_names, - configured_app.app_config::<#factor_types>().unwrap(), + configured_app.app_state::<#factor_types>().unwrap(), &app_component, ), - #factors_path::InstancePreparers::new(&mut preparers), + &mut #factors_path::InstanceBuilders::new(&mut builders), )? ); )* Ok(#state_name { #( - #factor_names: #Factor::prepare( - &self.#factor_names, - preparers.#factor_names.unwrap(), + #factor_names: #FactorInstanceBuilder::build( + builders.#factor_names.unwrap(), )?, )* }) @@ -135,15 +138,15 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { } impl #factors_path::RuntimeFactors for #name { - type AppState = #app_configs_name; - type InstancePreparers = #preparers_name; + type AppState = #app_state_name; + type InstanceBuilders = #builders_name; type InstanceState = #state_name; - unsafe fn instance_preparer_offset() -> Option { + unsafe fn instance_builder_offset() -> Option { let type_id = #TypeId::of::(); #( if type_id == #TypeId::of::<#factor_types>() { - return Some(std::mem::offset_of!(Self::InstancePreparers, #factor_names)); + return Some(std::mem::offset_of!(Self::InstanceBuilders, #factor_names)); } )* None @@ -160,32 +163,32 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { } - fn app_config(app_configs: &Self::AppState) -> Option<&T::AppState> { + fn app_state(app_state: &Self::AppState) -> Option<&T::AppState> { let type_id = #TypeId::of::(); #( if type_id == #TypeId::of::<#factor_types>() { - return Some(unsafe { std::mem::transmute(&app_configs.#factor_names) }); + return Some(unsafe { std::mem::transmute(&app_state.#factor_names) }); } )* None } } - #vis struct #app_configs_name { + #vis struct #app_state_name { #( pub #factor_names: Option<<#factor_types as #Factor>::AppState>, )* } - #vis struct #preparers_name { + #vis struct #builders_name { #( - pub #factor_names: Option<<#factor_types as #Factor>::InstancePreparer>, + pub #factor_names: Option<<#factor_types as #Factor>::InstanceBuilder>, )* } #vis struct #state_name { #( - pub #factor_names: <#factor_types as #Factor>::InstanceState, + pub #factor_names: <<#factor_types as #Factor>::InstanceBuilder as #FactorInstanceBuilder>::InstanceState, )* } }) diff --git a/crates/factors/src/factor.rs b/crates/factors/src/factor.rs index 79b1f86f16..c567bd9744 100644 --- a/crates/factors/src/factor.rs +++ b/crates/factors/src/factor.rs @@ -3,22 +3,16 @@ use std::any::Any; use anyhow::Context; use crate::{ - App, InstancePreparers, Linker, ModuleLinker, PrepareContext, RuntimeConfig, RuntimeFactors, + prepare::FactorInstanceBuilder, runtime_config::RuntimeConfigTracker, App, FactorRuntimeConfig, + InstanceBuilders, Linker, ModuleLinker, PrepareContext, RuntimeConfigSource, RuntimeFactors, }; pub trait Factor: Any + Sized { - /// Per-app state for this factor. - /// - /// See [`Factor::configure_app`]. - type AppState: Default; + type RuntimeConfig: FactorRuntimeConfig; - /// The [`FactorInstancePreparer`] for this factor. - type InstancePreparer: Default; + type AppState: Default; - /// The per-instance state for this factor, constructed by a - /// [`FactorInstancePreparer`] and available to any host-provided imports - /// defined by this factor. - type InstanceState; + type InstanceBuilder: FactorInstanceBuilder; /// Initializes this Factor for a runtime. This will be called at most once, /// before any call to [`FactorInstancePreparer::new`] @@ -34,28 +28,23 @@ pub trait Factor: Any + Sized { /// any call to `init` in cases where only validation is needed. fn configure_app( &self, - ctx: ConfigureAppContext, - _runtime_config: &mut impl RuntimeConfig, + ctx: ConfigureAppContext, ) -> anyhow::Result { _ = ctx; Ok(Default::default()) } - /// Returns a new instance of this preparer for the given [`Factor`]. - fn create_preparer( + fn prepare( ctx: PrepareContext, - _preparers: InstancePreparers, - ) -> anyhow::Result { - _ = ctx; - Ok(Default::default()) - } - - /// Returns a new instance of the associated [`Factor::InstanceState`]. - fn prepare(&self, preparer: Self::InstancePreparer) -> anyhow::Result; + _builders: &mut InstanceBuilders, + ) -> anyhow::Result; } -pub(crate) type GetDataFn = - fn(&mut ::InstanceState) -> &mut ::InstanceState; +pub(crate) type FactorInstanceState = + <::InstanceBuilder as FactorInstanceBuilder>::InstanceState; + +pub(crate) type GetDataFn = + fn(&mut ::InstanceState) -> &mut FactorInstanceState; /// An InitContext is passed to [`Factor::init`], giving access to the global /// common [`wasmtime::component::Linker`]. @@ -95,7 +84,7 @@ impl<'a, T: RuntimeFactors, F: Factor> InitContext<'a, T, F> { &mut self, add_to_linker: impl Fn( &mut Linker, - fn(&mut T::InstanceState) -> &mut F::InstanceState, + fn(&mut T::InstanceState) -> &mut FactorInstanceState, ) -> anyhow::Result<()>, ) -> anyhow::Result<()> where { @@ -110,7 +99,7 @@ where { &mut self, add_to_linker: impl Fn( &mut ModuleLinker, - fn(&mut T::InstanceState) -> &mut F::InstanceState, + fn(&mut T::InstanceState) -> &mut FactorInstanceState, ) -> anyhow::Result<()>, ) -> anyhow::Result<()> where { @@ -122,42 +111,60 @@ where { } } -pub struct ConfigureAppContext<'a, T: RuntimeFactors> { - pub(crate) app: &'a App, - pub(crate) app_configs: &'a T::AppState, +pub struct ConfigureAppContext<'a, T: RuntimeFactors, F: Factor> { + app: &'a App, + app_state: &'a T::AppState, + runtime_config: Option, } -impl<'a, T: RuntimeFactors> ConfigureAppContext<'a, T> { +impl<'a, T: RuntimeFactors, F: Factor> ConfigureAppContext<'a, T, F> { #[doc(hidden)] - pub fn new(app: &'a App, app_configs: &'a T::AppState) -> Self { - Self { app, app_configs } + pub fn new( + app: &'a App, + app_state: &'a T::AppState, + runtime_config_tracker: &mut RuntimeConfigTracker, + ) -> anyhow::Result { + let runtime_config = runtime_config_tracker.get_config::()?; + Ok(Self { + app, + app_state, + runtime_config, + }) } pub fn app(&self) -> &App { self.app } - pub fn app_config(&self) -> crate::Result<&F::AppState> { - T::app_config::(self.app_configs).context("no such factor") + pub fn app_state(&self) -> crate::Result<&U::AppState> { + T::app_state::(self.app_state).context("no such factor") + } + + pub fn runtime_config(&self) -> Option<&F::RuntimeConfig> { + self.runtime_config.as_ref() + } + + pub fn take_runtime_config(&mut self) -> Option { + self.runtime_config.take() } } pub struct ConfiguredApp { app: App, - app_configs: T::AppState, + app_state: T::AppState, } impl ConfiguredApp { #[doc(hidden)] - pub fn new(app: App, app_configs: T::AppState) -> Self { - Self { app, app_configs } + pub fn new(app: App, app_state: T::AppState) -> Self { + Self { app, app_state } } pub fn app(&self) -> &App { &self.app } - pub fn app_config(&self) -> crate::Result<&F::AppState> { - T::app_config::(&self.app_configs).context("no such factor") + pub fn app_state(&self) -> crate::Result<&F::AppState> { + T::app_state::(&self.app_state).context("no such factor") } } diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index eb34e6b371..e728927607 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -1,7 +1,7 @@ mod factor; mod prepare; mod runtime_config; -mod spin_factors; +mod runtime_factors; pub use anyhow; pub use wasmtime; @@ -10,9 +10,9 @@ pub use spin_factors_derive::RuntimeFactors; pub use crate::{ factor::{ConfigureAppContext, ConfiguredApp, Factor, InitContext}, - prepare::{InstancePreparers, PrepareContext}, - runtime_config::{RuntimeConfig, RuntimeConfigSource}, - spin_factors::RuntimeFactors, + prepare::{FactorInstanceBuilder, InstanceBuilders, PrepareContext}, + runtime_config::{FactorRuntimeConfig, RuntimeConfigSource}, + runtime_factors::RuntimeFactors, }; pub type Linker = wasmtime::component::Linker<::InstanceState>; diff --git a/crates/factors/src/prepare.rs b/crates/factors/src/prepare.rs index ccf975e8f3..2c17f8be29 100644 --- a/crates/factors/src/prepare.rs +++ b/crates/factors/src/prepare.rs @@ -1,26 +1,40 @@ +use std::marker::PhantomData; + use anyhow::Context; use crate::{AppComponent, Factor, RuntimeFactors}; -/// A PrepareContext is passed to [`FactorInstancePreparer::new`], giving access -/// to any already-initialized [`FactorInstancePreparer`]s, allowing for +pub trait FactorInstanceBuilder { + type InstanceState; + + fn build(self) -> anyhow::Result; +} + +pub struct DefaultInstanceBuilder(PhantomData T>); + +impl FactorInstanceBuilder for DefaultInstanceBuilder { + type InstanceState = T; + + fn build(self) -> anyhow::Result { + Ok(Default::default()) + } +} + +/// A PrepareContext is passed to [`Factor::prepare`], giving access to any +/// already-initialized [`FactorInstanceBuilder`]s, allowing for /// inter-[`Factor`] dependencies. pub struct PrepareContext<'a, F: Factor> { pub(crate) factor: &'a F, - pub(crate) app_config: &'a F::AppState, + pub(crate) app_state: &'a F::AppState, pub(crate) app_component: &'a AppComponent<'a>, } impl<'a, F: Factor> PrepareContext<'a, F> { #[doc(hidden)] - pub fn new( - factor: &'a F, - app_config: &'a F::AppState, - app_component: &'a AppComponent, - ) -> Self { + pub fn new(factor: &'a F, app_state: &'a F::AppState, app_component: &'a AppComponent) -> Self { Self { factor, - app_config, + app_state, app_component, } } @@ -29,8 +43,8 @@ impl<'a, F: Factor> PrepareContext<'a, F> { self.factor } - pub fn app_config(&self) -> &F::AppState { - self.app_config + pub fn app_state(&self) -> &F::AppState { + self.app_state } pub fn app_component(&self) -> &AppComponent { @@ -38,22 +52,22 @@ impl<'a, F: Factor> PrepareContext<'a, F> { } } -pub struct InstancePreparers<'a, T: RuntimeFactors> { - pub(crate) inner: &'a mut T::InstancePreparers, +pub struct InstanceBuilders<'a, T: RuntimeFactors> { + pub(crate) inner: &'a mut T::InstanceBuilders, } -impl<'a, T: RuntimeFactors> InstancePreparers<'a, T> { +impl<'a, T: RuntimeFactors> InstanceBuilders<'a, T> { #[doc(hidden)] - pub fn new(inner: &'a mut T::InstancePreparers) -> Self { + pub fn new(inner: &'a mut T::InstanceBuilders) -> Self { Self { inner } } - /// Returns a already-initialized preparer for the given [`Factor`]. + /// Returns the prepared [`FactorInstanceBuilder`] for the given [`Factor`]. /// /// Fails if the current [`RuntimeFactors`] does not include the given - /// [`Factor`] or if the given [`Factor`]'s preparer has not been - /// initialized yet (because it is sequenced after this factor). - pub fn get_mut(&mut self) -> crate::Result<&mut F::InstancePreparer> { - T::instance_preparer_mut::(self.inner)?.context("preparer not initialized") + /// [`Factor`] or if the given [`Factor`]'s builder has not been prepared + /// yet (because it is sequenced after this factor). + pub fn get_mut(&mut self) -> crate::Result<&mut F::InstanceBuilder> { + T::instance_builder_mut::(self.inner)?.context("builder not prepared") } } diff --git a/crates/factors/src/runtime_config.rs b/crates/factors/src/runtime_config.rs index 020707395d..6b161d2179 100644 --- a/crates/factors/src/runtime_config.rs +++ b/crates/factors/src/runtime_config.rs @@ -3,34 +3,60 @@ use std::collections::HashSet; use anyhow::bail; use serde::de::DeserializeOwned; -/// RuntimeConfig represents an application's runtime configuration. +use crate::Factor; + +/// FactorRuntimeConfig represents an application's runtime configuration. /// /// Runtime configuration is partitioned, with each partition being the -/// responsibility of exactly one [`crate::Factor`]. If configuration needs to -/// be shared between Factors, one Factor can be selected as the owner and the -/// others will have a dependency relationship with that owner. -pub trait RuntimeConfig { +/// responsibility of exactly one [`crate::Factor`]. If configuration needs +/// to be shared between Factors, one Factor can be selected as the owner +/// and the others will have a dependency relationship with that owner. +pub trait FactorRuntimeConfig: DeserializeOwned { + const KEY: &'static str; +} + +impl FactorRuntimeConfig for () { + const KEY: &'static str = ""; +} + +pub trait RuntimeConfigSource { + /// Returns an iterator of factor config keys available in this source. + /// + /// Should only include keys that have been positively provided. A runtime + /// may treat unrecognized keys as a warning or error. + fn factor_config_keys(&self) -> impl Iterator; + /// Returns deserialized runtime config of the given type for the given /// factor config key. /// /// Returns Ok(None) if no configuration is available for the given key. - /// Returns Err if configuration is available but deserialization fails, - /// or if the given config key has already been retrieved. + /// Returns Err if configuration is available but deserialization fails. + fn get_config(&self, factor_config_key: &str) + -> anyhow::Result>; +} + +impl RuntimeConfigSource for () { fn get_config( - &mut self, - factor_config_key: &str, - ) -> anyhow::Result>; + &self, + _factor_config_key: &str, + ) -> anyhow::Result> { + Ok(None) + } + + fn factor_config_keys(&self) -> impl Iterator { + std::iter::empty() + } } -pub struct RuntimeConfigTracker { - source: Source, - used_keys: HashSet, +pub struct RuntimeConfigTracker { + source: S, + used_keys: HashSet<&'static str>, unused_keys: HashSet, } -impl RuntimeConfigTracker { +impl RuntimeConfigTracker { #[doc(hidden)] - pub fn new(source: Source) -> Self { + pub fn new(source: S) -> Self { let unused_keys = source.factor_config_keys().map(ToOwned::to_owned).collect(); Self { source, @@ -47,48 +73,13 @@ impl RuntimeConfigTracker { Err(self.unused_keys) } } -} -impl RuntimeConfig for RuntimeConfigTracker { - fn get_config( - &mut self, - factor_config_key: &str, - ) -> anyhow::Result> { - if !self.used_keys.insert(factor_config_key.to_owned()) { - bail!("already got runtime config key {factor_config_key:?}"); + pub fn get_config(&mut self) -> anyhow::Result> { + let key = F::RuntimeConfig::KEY; + if !self.used_keys.insert(key) { + bail!("already got runtime config key {key:?}"); } - self.unused_keys.remove(factor_config_key); - self.source.get_config::(factor_config_key) - } -} - -pub trait RuntimeConfigSource { - /// Returns deserialized runtime config of the given type for the given - /// factor config key. - /// - /// Returns Ok(None) if no configuration is available for the given key. - /// Returns Err if configuration is available but deserialization fails. - fn get_config( - &self, - factor_config_key: &str, - ) -> anyhow::Result>; - - /// Returns an iterator of factor config keys available in this source. - /// - /// Should only include keys that have been positively provided. A runtime - /// may treat unrecognized keys as a warning or error. - fn factor_config_keys(&self) -> impl Iterator; -} - -impl RuntimeConfigSource for () { - fn get_config( - &self, - _factor_config_key: &str, - ) -> anyhow::Result> { - Ok(None) - } - - fn factor_config_keys(&self) -> impl Iterator { - std::iter::empty() + self.unused_keys.remove(key); + self.source.get_config::(key) } } diff --git a/crates/factors/src/spin_factors.rs b/crates/factors/src/runtime_factors.rs similarity index 75% rename from crates/factors/src/spin_factors.rs rename to crates/factors/src/runtime_factors.rs index fd961e6791..8d11e9c30d 100644 --- a/crates/factors/src/spin_factors.rs +++ b/crates/factors/src/runtime_factors.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use anyhow::Context; -use crate::Factor; +use crate::{factor::FactorInstanceState, Factor}; // TODO(lann): Most of the unsafe shenanigans here probably aren't worth it; // consider replacing with e.g. `Any::downcast`. @@ -10,18 +10,19 @@ use crate::Factor; /// Implemented by `#[derive(RuntimeFactors)]` pub trait RuntimeFactors: Sized { type AppState; - type InstancePreparers; + type InstanceBuilders; type InstanceState: Send + 'static; #[doc(hidden)] - unsafe fn instance_preparer_offset() -> Option; + unsafe fn instance_builder_offset() -> Option; #[doc(hidden)] unsafe fn instance_state_offset() -> Option; - fn app_config(app_configs: &Self::AppState) -> Option<&F::AppState>; + fn app_state(app_state: &Self::AppState) -> Option<&F::AppState>; - fn instance_state_getter() -> Option> { + fn instance_state_getter( + ) -> Option>> { let offset = unsafe { Self::instance_state_offset::()? }; Some(Getter { offset, @@ -30,7 +31,8 @@ pub trait RuntimeFactors: Sized { } fn instance_state_getter2( - ) -> Option> { + ) -> Option, FactorInstanceState>> + { let offset1 = unsafe { Self::instance_state_offset::()? }; let offset2 = unsafe { Self::instance_state_offset::()? }; assert_ne!( @@ -44,13 +46,13 @@ pub trait RuntimeFactors: Sized { }) } - fn instance_preparer_mut( - preparers: &mut Self::InstancePreparers, - ) -> crate::Result> { + fn instance_builder_mut( + builders: &mut Self::InstanceBuilders, + ) -> crate::Result> { unsafe { - let offset = Self::instance_preparer_offset::().context("no such factor")?; - let ptr = preparers as *mut Self::InstancePreparers; - let opt = &mut *ptr.add(offset).cast::>(); + let offset = Self::instance_builder_offset::().context("no such factor")?; + let ptr = builders as *mut Self::InstanceBuilders; + let opt = &mut *ptr.add(offset).cast::>(); Ok(opt.as_mut()) } } From 9014ec6769aae36967dc4b6e80a11d65abddf2a3 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Fri, 7 Jun 2024 10:15:10 -0400 Subject: [PATCH 13/22] factors: Make Factor::configure_app required Signed-off-by: Lann Martin --- crates/factor-wasi/src/lib.rs | 7 +++++++ crates/factor-wasi/src/preview1.rs | 7 +++++++ crates/factors/src/factor.rs | 7 ++----- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs index 30597e1079..c6fdb1279b 100644 --- a/crates/factor-wasi/src/lib.rs +++ b/crates/factor-wasi/src/lib.rs @@ -70,6 +70,13 @@ impl Factor for WasiFactor { Ok(()) } + fn configure_app( + &self, + _ctx: spin_factors::ConfigureAppContext, + ) -> anyhow::Result { + Ok(()) + } + fn prepare( ctx: PrepareContext, _builders: &mut InstanceBuilders, diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs index 1a25260bec..7437086683 100644 --- a/crates/factor-wasi/src/preview1.rs +++ b/crates/factor-wasi/src/preview1.rs @@ -15,6 +15,13 @@ impl Factor for WasiPreview1Factor { ctx.link_module_bindings(wasmtime_wasi::preview1::add_to_linker_async) } + fn configure_app( + &self, + _ctx: spin_factors::ConfigureAppContext, + ) -> anyhow::Result { + Ok(()) + } + fn prepare( _ctx: spin_factors::PrepareContext, _builders: &mut spin_factors::InstanceBuilders, diff --git a/crates/factors/src/factor.rs b/crates/factors/src/factor.rs index c567bd9744..a8c308b833 100644 --- a/crates/factors/src/factor.rs +++ b/crates/factors/src/factor.rs @@ -10,7 +10,7 @@ use crate::{ pub trait Factor: Any + Sized { type RuntimeConfig: FactorRuntimeConfig; - type AppState: Default; + type AppState; type InstanceBuilder: FactorInstanceBuilder; @@ -29,10 +29,7 @@ pub trait Factor: Any + Sized { fn configure_app( &self, ctx: ConfigureAppContext, - ) -> anyhow::Result { - _ = ctx; - Ok(Default::default()) - } + ) -> anyhow::Result; fn prepare( ctx: PrepareContext, From f43b2f4f1ae9ae36c9cd6dd1f2b580a82e1b673d Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Fri, 7 Jun 2024 17:00:51 -0400 Subject: [PATCH 14/22] factors: Add VariablesFactor runtime config Signed-off-by: Lann Martin --- Cargo.lock | 4 + crates/expressions/src/lib.rs | 10 ++ crates/factor-outbound-networking/src/lib.rs | 2 - crates/factor-variables/Cargo.toml | 2 + crates/factor-variables/src/lib.rs | 102 +++++++++++++------ crates/factor-variables/src/provider_type.rs | 49 +++++++++ crates/factors-derive/src/lib.rs | 32 +++--- crates/factors/Cargo.toml | 2 + crates/factors/src/lib.rs | 2 +- crates/factors/src/prepare.rs | 20 +++- crates/factors/src/runtime_config.rs | 39 ++++--- crates/factors/src/runtime_factors.rs | 98 +----------------- crates/factors/tests/smoke.rs | 69 ++++++++++--- 13 files changed, 255 insertions(+), 176 deletions(-) create mode 100644 crates/factor-variables/src/provider_type.rs diff --git a/Cargo.lock b/Cargo.lock index 9c575470f5..4eaa2e597a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7575,9 +7575,11 @@ dependencies = [ name = "spin-factor-variables" version = "2.6.0-pre0" dependencies = [ + "serde 1.0.197", "spin-expressions", "spin-factors", "spin-world", + "toml 0.8.12", ] [[package]] @@ -7602,6 +7604,8 @@ dependencies = [ "spin-factor-wasi", "spin-factors-derive", "thiserror", + "tokio", + "toml 0.8.12", "tracing", "wasmtime", ] diff --git a/crates/expressions/src/lib.rs b/crates/expressions/src/lib.rs index 612c0696bb..811c0e864d 100644 --- a/crates/expressions/src/lib.rs +++ b/crates/expressions/src/lib.rs @@ -5,6 +5,8 @@ use std::{borrow::Cow, collections::HashMap, fmt::Debug}; use spin_locked_app::Variable; +pub use async_trait; + pub use provider::Provider; use template::Part; pub use template::Template; @@ -251,6 +253,14 @@ impl<'a> Key<'a> { } } +impl<'a> TryFrom<&'a str> for Key<'a> { + type Error = Error; + + fn try_from(value: &'a str) -> std::prelude::v1::Result { + Self::new(value) + } +} + impl<'a> AsRef for Key<'a> { fn as_ref(&self) -> &str { self.0 diff --git a/crates/factor-outbound-networking/src/lib.rs b/crates/factor-outbound-networking/src/lib.rs index e19f6d6078..89a6d1e8c7 100644 --- a/crates/factor-outbound-networking/src/lib.rs +++ b/crates/factor-outbound-networking/src/lib.rs @@ -91,14 +91,12 @@ impl Factor for OutboundNetworkingFactor { } } -#[derive(Default)] pub struct AppState { component_allowed_hosts: HashMap>, } type SharedFutureResult = Shared>>>; -#[derive(Default)] pub struct InstanceBuilder { allowed_hosts_future: Option>, } diff --git a/crates/factor-variables/Cargo.toml b/crates/factor-variables/Cargo.toml index 7621fb266b..5e5cf55438 100644 --- a/crates/factor-variables/Cargo.toml +++ b/crates/factor-variables/Cargo.toml @@ -5,9 +5,11 @@ authors = { workspace = true } edition = { workspace = true } [dependencies] +serde = { version = "1.0", features = ["rc"] } spin-expressions = { path = "../expressions" } spin-factors = { path = "../factors" } spin-world = { path = "../world" } +toml = "0.8" [lints] workspace = true diff --git a/crates/factor-variables/src/lib.rs b/crates/factor-variables/src/lib.rs index 438e016799..6f0c310e71 100644 --- a/crates/factor-variables/src/lib.rs +++ b/crates/factor-variables/src/lib.rs @@ -1,18 +1,44 @@ -use std::sync::Arc; +mod provider_type; +use std::{collections::HashMap, sync::Arc}; + +use provider_type::{provider_maker, ProviderMaker}; +use serde::Deserialize; use spin_expressions::ProviderResolver; use spin_factors::{ - anyhow, ConfigureAppContext, Factor, FactorInstanceBuilder, InitContext, InstanceBuilders, - PrepareContext, RuntimeFactors, + anyhow::{self, bail, Context}, + ConfigureAppContext, Factor, FactorRuntimeConfig, InitContext, InstanceBuilders, + PrepareContext, RuntimeFactors, SelfInstanceBuilder, }; use spin_world::{async_trait, v1::config as v1_config, v2::variables}; -pub struct VariablesFactor; +pub use provider_type::{StaticVariables, VariablesProviderType}; + +#[derive(Default)] +pub struct VariablesFactor { + provider_types: HashMap<&'static str, ProviderMaker>, +} + +impl VariablesFactor { + pub fn add_provider_type( + &mut self, + provider_type: T, + ) -> anyhow::Result<()> { + if self + .provider_types + .insert(T::TYPE, provider_maker(provider_type)) + .is_some() + { + bail!("duplicate provider type {:?}", T::TYPE); + } + Ok(()) + } +} impl Factor for VariablesFactor { - type RuntimeConfig = (); + type RuntimeConfig = RuntimeConfig; type AppState = AppState; - type InstanceBuilder = InstanceBuilder; + type InstanceBuilder = InstanceState; fn init( &mut self, @@ -25,18 +51,30 @@ impl Factor for VariablesFactor { fn configure_app( &self, - ctx: ConfigureAppContext, + mut ctx: ConfigureAppContext, ) -> anyhow::Result { let app = ctx.app(); let mut resolver = ProviderResolver::new(app.variables().map(|(key, val)| (key.clone(), val.clone())))?; + for component in app.components() { resolver.add_component_variables( component.id(), component.config().map(|(k, v)| (k.into(), v.into())), )?; } - // TODO: add providers from runtime config + + if let Some(runtime_config) = ctx.take_runtime_config() { + for ProviderConfig { type_, config } in runtime_config.provider_configs { + let provider_maker = self + .provider_types + .get(type_.as_str()) + .with_context(|| format!("unknown variables provider type {type_}"))?; + let provider = provider_maker(config)?; + resolver.add_provider(provider); + } + } + Ok(AppState { resolver: Arc::new(resolver), }) @@ -45,47 +83,51 @@ impl Factor for VariablesFactor { fn prepare( ctx: PrepareContext, _builders: &mut InstanceBuilders, - ) -> anyhow::Result { + ) -> anyhow::Result { let component_id = ctx.app_component().id().to_string(); let resolver = ctx.app_state().resolver.clone(); - Ok(InstanceBuilder { - state: InstanceState { - component_id, - resolver, - }, + Ok(InstanceState { + component_id, + resolver, }) } } -#[derive(Default)] -pub struct AppState { - resolver: Arc, +#[derive(Deserialize)] +#[serde(transparent)] +pub struct RuntimeConfig { + provider_configs: Vec, } -pub struct InstanceBuilder { - state: InstanceState, +impl FactorRuntimeConfig for RuntimeConfig { + const KEY: &'static str = "variable_provider"; } -impl InstanceBuilder { - pub fn resolver(&self) -> &Arc { - &self.state.resolver - } +#[derive(Deserialize)] +struct ProviderConfig { + #[serde(rename = "type")] + type_: String, + #[serde(flatten)] + config: toml::Table, } -impl FactorInstanceBuilder for InstanceBuilder { - type InstanceState = InstanceState; - - fn build(self) -> anyhow::Result { - Ok(self.state) - } +pub struct AppState { + resolver: Arc, } -#[derive(Default)] pub struct InstanceState { component_id: String, resolver: Arc, } +impl InstanceState { + pub fn resolver(&self) -> &Arc { + &self.resolver + } +} + +impl SelfInstanceBuilder for InstanceState {} + #[async_trait] impl variables::Host for InstanceState { async fn get(&mut self, key: String) -> Result { diff --git a/crates/factor-variables/src/provider_type.rs b/crates/factor-variables/src/provider_type.rs new file mode 100644 index 0000000000..bd6a07c074 --- /dev/null +++ b/crates/factor-variables/src/provider_type.rs @@ -0,0 +1,49 @@ +use std::{collections::HashMap, sync::Arc}; + +use serde::{de::DeserializeOwned, Deserialize}; +use spin_expressions::{async_trait::async_trait, Key, Provider}; +use spin_factors::anyhow; + +pub trait VariablesProviderType: 'static { + const TYPE: &'static str; + + type RuntimeConfig: DeserializeOwned; + type Provider: Provider; + + fn make_provider(&self, runtime_config: Self::RuntimeConfig) -> anyhow::Result; +} + +pub(crate) type ProviderMaker = Box anyhow::Result>>; + +pub(crate) fn provider_maker(provider_type: T) -> ProviderMaker { + Box::new(move |table| { + let runtime_config: T::RuntimeConfig = table.try_into()?; + let provider = provider_type.make_provider(runtime_config)?; + Ok(Box::new(provider)) + }) +} + +pub struct StaticVariables; + +impl VariablesProviderType for StaticVariables { + const TYPE: &'static str = "static"; + + type RuntimeConfig = StaticVariablesProvider; + type Provider = StaticVariablesProvider; + + fn make_provider(&self, runtime_config: Self::RuntimeConfig) -> anyhow::Result { + Ok(runtime_config) + } +} + +#[derive(Debug, Deserialize)] +pub struct StaticVariablesProvider { + values: Arc>, +} + +#[async_trait] +impl Provider for StaticVariablesProvider { + async fn get(&self, key: &Key) -> anyhow::Result> { + Ok(self.values.get(key.as_str()).cloned()) + } +} diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index deb03e7560..d787797b88 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -104,6 +104,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { )? ); )* + runtime_config_tracker.validate_all_keys_used()?; Ok(#ConfiguredApp::new(app, app_state)) } @@ -142,32 +143,29 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { type InstanceBuilders = #builders_name; type InstanceState = #state_name; - unsafe fn instance_builder_offset() -> Option { - let type_id = #TypeId::of::(); + fn app_state(app_state: &Self::AppState) -> Option<&F::AppState> { + let type_id = #TypeId::of::(); #( if type_id == #TypeId::of::<#factor_types>() { - return Some(std::mem::offset_of!(Self::InstanceBuilders, #factor_names)); + unsafe { + return Some(::std::mem::transmute(&app_state.#factor_names)); + } } )* None } - unsafe fn instance_state_offset() -> Option { - let type_id = #TypeId::of::(); + fn instance_builder_mut( + builders: &mut Self::InstanceBuilders, + ) -> Option> { + let type_id = #TypeId::of::(); #( if type_id == #TypeId::of::<#factor_types>() { - return Some(std::mem::offset_of!(Self::InstanceState, #factor_names)); - } - )* - None - - } - - fn app_state(app_state: &Self::AppState) -> Option<&T::AppState> { - let type_id = #TypeId::of::(); - #( - if type_id == #TypeId::of::<#factor_types>() { - return Some(unsafe { std::mem::transmute(&app_state.#factor_names) }); + return Some( + builders.#factor_names.as_mut().map(|builder| { + unsafe { ::std::mem::transmute(builder) } + }) + ); } )* None diff --git a/crates/factors/Cargo.toml b/crates/factors/Cargo.toml index 2d9347dbef..62255f7473 100644 --- a/crates/factors/Cargo.toml +++ b/crates/factors/Cargo.toml @@ -19,6 +19,8 @@ spin-factors-derive = { path = "../factors-derive", features = ["expander"] } spin-factor-outbound-networking = { path = "../factor-outbound-networking" } spin-factor-variables = { path = "../factor-variables" } spin-factor-wasi = { path = "../factor-wasi" } +tokio = { version = "1", features = ["macros", "rt"] } +toml = "0.8" [lints] workspace = true diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index e728927607..208adb84fc 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -10,7 +10,7 @@ pub use spin_factors_derive::RuntimeFactors; pub use crate::{ factor::{ConfigureAppContext, ConfiguredApp, Factor, InitContext}, - prepare::{FactorInstanceBuilder, InstanceBuilders, PrepareContext}, + prepare::{FactorInstanceBuilder, InstanceBuilders, PrepareContext, SelfInstanceBuilder}, runtime_config::{FactorRuntimeConfig, RuntimeConfigSource}, runtime_factors::RuntimeFactors, }; diff --git a/crates/factors/src/prepare.rs b/crates/factors/src/prepare.rs index 2c17f8be29..81359c5c5b 100644 --- a/crates/factors/src/prepare.rs +++ b/crates/factors/src/prepare.rs @@ -1,10 +1,10 @@ -use std::marker::PhantomData; +use std::{any::Any, marker::PhantomData}; use anyhow::Context; use crate::{AppComponent, Factor, RuntimeFactors}; -pub trait FactorInstanceBuilder { +pub trait FactorInstanceBuilder: Any { type InstanceState; fn build(self) -> anyhow::Result; @@ -12,7 +12,7 @@ pub trait FactorInstanceBuilder { pub struct DefaultInstanceBuilder(PhantomData T>); -impl FactorInstanceBuilder for DefaultInstanceBuilder { +impl FactorInstanceBuilder for DefaultInstanceBuilder { type InstanceState = T; fn build(self) -> anyhow::Result { @@ -20,6 +20,16 @@ impl FactorInstanceBuilder for DefaultInstanceBuilder { } } +pub trait SelfInstanceBuilder: 'static {} + +impl FactorInstanceBuilder for T { + type InstanceState = Self; + + fn build(self) -> anyhow::Result { + Ok(self) + } +} + /// A PrepareContext is passed to [`Factor::prepare`], giving access to any /// already-initialized [`FactorInstanceBuilder`]s, allowing for /// inter-[`Factor`] dependencies. @@ -68,6 +78,8 @@ impl<'a, T: RuntimeFactors> InstanceBuilders<'a, T> { /// [`Factor`] or if the given [`Factor`]'s builder has not been prepared /// yet (because it is sequenced after this factor). pub fn get_mut(&mut self) -> crate::Result<&mut F::InstanceBuilder> { - T::instance_builder_mut::(self.inner)?.context("builder not prepared") + T::instance_builder_mut::(self.inner) + .context("no such factor")? + .context("builder not prepared") } } diff --git a/crates/factors/src/runtime_config.rs b/crates/factors/src/runtime_config.rs index 6b161d2179..3f313f0f13 100644 --- a/crates/factors/src/runtime_config.rs +++ b/crates/factors/src/runtime_config.rs @@ -5,6 +5,8 @@ use serde::de::DeserializeOwned; use crate::Factor; +pub const NO_RUNTIME_CONFIG: &str = ""; + /// FactorRuntimeConfig represents an application's runtime configuration. /// /// Runtime configuration is partitioned, with each partition being the @@ -16,7 +18,7 @@ pub trait FactorRuntimeConfig: DeserializeOwned { } impl FactorRuntimeConfig for () { - const KEY: &'static str = ""; + const KEY: &'static str = NO_RUNTIME_CONFIG; } pub trait RuntimeConfigSource { @@ -24,15 +26,14 @@ pub trait RuntimeConfigSource { /// /// Should only include keys that have been positively provided. A runtime /// may treat unrecognized keys as a warning or error. - fn factor_config_keys(&self) -> impl Iterator; + fn config_keys(&self) -> impl IntoIterator; /// Returns deserialized runtime config of the given type for the given /// factor config key. /// /// Returns Ok(None) if no configuration is available for the given key. /// Returns Err if configuration is available but deserialization fails. - fn get_config(&self, factor_config_key: &str) - -> anyhow::Result>; + fn get_config(&self, key: &str) -> anyhow::Result>; } impl RuntimeConfigSource for () { @@ -43,7 +44,7 @@ impl RuntimeConfigSource for () { Ok(None) } - fn factor_config_keys(&self) -> impl Iterator { + fn config_keys(&self) -> impl IntoIterator { std::iter::empty() } } @@ -57,7 +58,11 @@ pub struct RuntimeConfigTracker { impl RuntimeConfigTracker { #[doc(hidden)] pub fn new(source: S) -> Self { - let unused_keys = source.factor_config_keys().map(ToOwned::to_owned).collect(); + let unused_keys = source + .config_keys() + .into_iter() + .map(ToOwned::to_owned) + .collect(); Self { source, used_keys: Default::default(), @@ -66,18 +71,28 @@ impl RuntimeConfigTracker { } #[doc(hidden)] - pub fn validate_all_keys_used(self) -> Result<(), impl IntoIterator> { - if self.unused_keys.is_empty() { - Ok(()) - } else { - Err(self.unused_keys) + pub fn validate_all_keys_used(self) -> anyhow::Result<()> { + if !self.unused_keys.is_empty() { + bail!( + "unused runtime config key(s): {keys}", + keys = self + .unused_keys + .iter() + .map(|key| format!("{key:?}")) + .collect::>() + .join(", ") + ); } + Ok(()) } pub fn get_config(&mut self) -> anyhow::Result> { let key = F::RuntimeConfig::KEY; + if key == NO_RUNTIME_CONFIG { + return Ok(None); + } if !self.used_keys.insert(key) { - bail!("already got runtime config key {key:?}"); + bail!("already used runtime config key {key:?}"); } self.unused_keys.remove(key); self.source.get_config::(key) diff --git a/crates/factors/src/runtime_factors.rs b/crates/factors/src/runtime_factors.rs index 8d11e9c30d..8e44b78ef3 100644 --- a/crates/factors/src/runtime_factors.rs +++ b/crates/factors/src/runtime_factors.rs @@ -1,8 +1,4 @@ -use std::marker::PhantomData; - -use anyhow::Context; - -use crate::{factor::FactorInstanceState, Factor}; +use crate::Factor; // TODO(lann): Most of the unsafe shenanigans here probably aren't worth it; // consider replacing with e.g. `Any::downcast`. @@ -13,99 +9,9 @@ pub trait RuntimeFactors: Sized { type InstanceBuilders; type InstanceState: Send + 'static; - #[doc(hidden)] - unsafe fn instance_builder_offset() -> Option; - - #[doc(hidden)] - unsafe fn instance_state_offset() -> Option; - fn app_state(app_state: &Self::AppState) -> Option<&F::AppState>; - fn instance_state_getter( - ) -> Option>> { - let offset = unsafe { Self::instance_state_offset::()? }; - Some(Getter { - offset, - _phantom: PhantomData, - }) - } - - fn instance_state_getter2( - ) -> Option, FactorInstanceState>> - { - let offset1 = unsafe { Self::instance_state_offset::()? }; - let offset2 = unsafe { Self::instance_state_offset::()? }; - assert_ne!( - offset1, offset2, - "instance_state_getter2 with same factor twice would alias" - ); - Some(Getter2 { - offset1, - offset2, - _phantom: PhantomData, - }) - } - fn instance_builder_mut( builders: &mut Self::InstanceBuilders, - ) -> crate::Result> { - unsafe { - let offset = Self::instance_builder_offset::().context("no such factor")?; - let ptr = builders as *mut Self::InstanceBuilders; - let opt = &mut *ptr.add(offset).cast::>(); - Ok(opt.as_mut()) - } - } -} - -pub struct Getter { - pub(crate) offset: usize, - pub(crate) _phantom: PhantomData &mut U>, -} - -impl Getter { - pub fn get_mut<'a>(&self, container: &'a mut T) -> &'a mut U { - let ptr = container as *mut T; - unsafe { &mut *ptr.add(self.offset).cast::() } - } + ) -> Option>; } - -impl Clone for Getter { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Getter {} - -pub struct Getter2 { - pub(crate) offset1: usize, - pub(crate) offset2: usize, - #[allow(clippy::type_complexity)] - pub(crate) _phantom: PhantomData (&mut U, &mut V)>, -} - -impl Getter2 { - pub fn get_mut<'a>(&self, container: &'a mut T) -> (&'a mut U, &'a mut V) - where - T: 'static, - U: 'static, - V: 'static, - { - let ptr = container as *mut T; - unsafe { - ( - &mut *ptr.add(self.offset1).cast::(), - &mut *ptr.add(self.offset2).cast::(), - ) - } - } -} - -impl Clone for Getter2 { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Getter2 {} diff --git a/crates/factors/tests/smoke.rs b/crates/factors/tests/smoke.rs index c848be7467..a88f9ff5c2 100644 --- a/crates/factors/tests/smoke.rs +++ b/crates/factors/tests/smoke.rs @@ -1,8 +1,8 @@ use spin_app::App; use spin_factor_outbound_networking::OutboundNetworkingFactor; -use spin_factor_variables::VariablesFactor; +use spin_factor_variables::{StaticVariables, VariablesFactor}; use spin_factor_wasi::{preview1::WasiPreview1Factor, DummyFilesMounter, WasiFactor}; -use spin_factors::RuntimeFactors; +use spin_factors::{FactorRuntimeConfig, RuntimeConfigSource, RuntimeFactors}; #[derive(RuntimeFactors)] struct Factors { @@ -12,30 +12,40 @@ struct Factors { outbound_networking_factor: OutboundNetworkingFactor, } -fn main() -> anyhow::Result<()> { +#[tokio::test(flavor = "multi_thread")] +async fn main() -> anyhow::Result<()> { let mut factors = Factors { wasi: WasiFactor::new(DummyFilesMounter), wasip1: WasiPreview1Factor, - variables: VariablesFactor, + variables: VariablesFactor::default(), outbound_networking_factor: OutboundNetworkingFactor, // outbound_http_factor: OutboundHttpFactor, }; + factors.variables.add_provider_type(StaticVariables)?; let locked = serde_json::from_value(serde_json::json!({ - "spin_locked_version": 1, + "spin_lock_version": 1, + "variables": { + "foo": {} + }, "triggers": [], "components": [{ "id": "test", + "metadata": { + "allowed_outbound_hosts": ["http://{{ foo }}"] + }, "source": { "content_type": "application/wasm", "content": {"inline": "KGNvbXBvbmVudCk="} + }, + "config": { + "test_var": "{{foo}}" } }] - })) - .unwrap(); + }))?; let app = App::inert(locked); - let engine = wasmtime::Engine::default(); + let engine = wasmtime::Engine::new(wasmtime::Config::new().async_support(true))?; let mut linker = wasmtime::component::Linker::new(&engine); let mut module_linker = wasmtime::Linker::new(&engine); @@ -43,16 +53,47 @@ fn main() -> anyhow::Result<()> { .init(Some(&mut linker), Some(&mut module_linker)) .unwrap(); - let configured_app = factors.configure_app(app, ()).unwrap(); - let data = factors.build_store_data(&configured_app, "test").unwrap(); + let configured_app = factors.configure_app(app, TestSource)?; + let data = factors.build_store_data(&configured_app, "test")?; + + assert_eq!( + data.variables + .resolver() + .resolve("test", "test_var".try_into().unwrap()) + .await + .unwrap(), + "bar" + ); let mut store = wasmtime::Store::new(&engine, data); - let component = wasmtime::component::Component::new(&engine, b"(component)").unwrap(); - let _instance = linker.instantiate(&mut store, &component).unwrap(); + let component = wasmtime::component::Component::new(&engine, b"(component)")?; + let _instance = linker.instantiate_async(&mut store, &component).await?; - let module = wasmtime::Module::new(&engine, b"(module)").unwrap(); - let _module_instance = module_linker.instantiate(&mut store, &module).unwrap(); + let module = wasmtime::Module::new(&engine, b"(module)")?; + let _module_instance = module_linker.instantiate_async(&mut store, &module).await?; Ok(()) } + +struct TestSource; + +impl RuntimeConfigSource for TestSource { + fn config_keys(&self) -> impl IntoIterator { + [spin_factor_variables::RuntimeConfig::KEY] + } + + fn get_config(&self, key: &str) -> anyhow::Result> { + let Some(table) = toml::toml! { + [[variable_provider]] + type = "static" + [variable_provider.values] + foo = "bar" + } + .remove(key) else { + return Ok(None); + }; + let config = table.try_into()?; + Ok(Some(config)) + } +} From b5ec51cb3ff1df5b6ad8c95fdb6a061393a5b794 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Mon, 10 Jun 2024 10:33:49 -0400 Subject: [PATCH 15/22] factors: Rename VariablesProviderType -> MakeVariablesProvider Also sneak in Factor::runtime_config_json_schema Signed-off-by: Lann Martin --- crates/factor-variables/src/lib.rs | 8 +++--- .../src/{provider_type.rs => provider.rs} | 6 ++--- crates/factors/src/factor.rs | 26 ++++++++++++++++--- crates/factors/src/runtime_factors.rs | 3 --- 4 files changed, 29 insertions(+), 14 deletions(-) rename crates/factor-variables/src/{provider_type.rs => provider.rs} (89%) diff --git a/crates/factor-variables/src/lib.rs b/crates/factor-variables/src/lib.rs index 6f0c310e71..c5c4ec0990 100644 --- a/crates/factor-variables/src/lib.rs +++ b/crates/factor-variables/src/lib.rs @@ -1,8 +1,8 @@ -mod provider_type; +mod provider; use std::{collections::HashMap, sync::Arc}; -use provider_type::{provider_maker, ProviderMaker}; +use provider::{provider_maker, ProviderMaker}; use serde::Deserialize; use spin_expressions::ProviderResolver; use spin_factors::{ @@ -12,7 +12,7 @@ use spin_factors::{ }; use spin_world::{async_trait, v1::config as v1_config, v2::variables}; -pub use provider_type::{StaticVariables, VariablesProviderType}; +pub use provider::{MakeVariablesProvider, StaticVariables}; #[derive(Default)] pub struct VariablesFactor { @@ -20,7 +20,7 @@ pub struct VariablesFactor { } impl VariablesFactor { - pub fn add_provider_type( + pub fn add_provider_type( &mut self, provider_type: T, ) -> anyhow::Result<()> { diff --git a/crates/factor-variables/src/provider_type.rs b/crates/factor-variables/src/provider.rs similarity index 89% rename from crates/factor-variables/src/provider_type.rs rename to crates/factor-variables/src/provider.rs index bd6a07c074..a8ceeccaa8 100644 --- a/crates/factor-variables/src/provider_type.rs +++ b/crates/factor-variables/src/provider.rs @@ -4,7 +4,7 @@ use serde::{de::DeserializeOwned, Deserialize}; use spin_expressions::{async_trait::async_trait, Key, Provider}; use spin_factors::anyhow; -pub trait VariablesProviderType: 'static { +pub trait MakeVariablesProvider: 'static { const TYPE: &'static str; type RuntimeConfig: DeserializeOwned; @@ -15,7 +15,7 @@ pub trait VariablesProviderType: 'static { pub(crate) type ProviderMaker = Box anyhow::Result>>; -pub(crate) fn provider_maker(provider_type: T) -> ProviderMaker { +pub(crate) fn provider_maker(provider_type: T) -> ProviderMaker { Box::new(move |table| { let runtime_config: T::RuntimeConfig = table.try_into()?; let provider = provider_type.make_provider(runtime_config)?; @@ -25,7 +25,7 @@ pub(crate) fn provider_maker(provider_type: T) -> Prov pub struct StaticVariables; -impl VariablesProviderType for StaticVariables { +impl MakeVariablesProvider for StaticVariables { const TYPE: &'static str = "static"; type RuntimeConfig = StaticVariablesProvider; diff --git a/crates/factors/src/factor.rs b/crates/factors/src/factor.rs index a8c308b833..2f3b258387 100644 --- a/crates/factors/src/factor.rs +++ b/crates/factors/src/factor.rs @@ -1,4 +1,4 @@ -use std::any::Any; +use std::{any::Any, collections::HashMap}; use anyhow::Context; @@ -23,18 +23,36 @@ pub trait Factor: Any + Sized { } /// Performs factor-specific validation and configuration for the given - /// [`App`]. A runtime may - but is not required to - reuse the returned - /// config across multiple instances. Note that this may be called without - /// any call to `init` in cases where only validation is needed. + /// [`App`]. + /// + /// A runtime may - but is not required to - reuse the returned config + /// across multiple instances. Note that this may be called without any call + /// to `init` in cases where only validation is needed. fn configure_app( &self, ctx: ConfigureAppContext, ) -> anyhow::Result; + /// Prepares an instance builder for this factor. + /// + /// This method is given access to the app component being instantiated and + /// to any other factors' instance builders that have already been prepared. fn prepare( ctx: PrepareContext, _builders: &mut InstanceBuilders, ) -> anyhow::Result; + + /// Returns [JSON Schema](https://json-schema.org/) for this factor's + /// runtime config. + /// + /// Note that this represents only a fragment of an entire JSON document (a + /// "child instance" in JSON Schema terms), so `$schema` isn't needed. + /// + /// The default implementation returns an empty schema, which accepts any + /// configuration. + fn runtime_config_json_schema(&self) -> impl serde::Serialize { + HashMap::<(), ()>::new() + } } pub(crate) type FactorInstanceState = diff --git a/crates/factors/src/runtime_factors.rs b/crates/factors/src/runtime_factors.rs index 8e44b78ef3..3d61638474 100644 --- a/crates/factors/src/runtime_factors.rs +++ b/crates/factors/src/runtime_factors.rs @@ -1,8 +1,5 @@ use crate::Factor; -// TODO(lann): Most of the unsafe shenanigans here probably aren't worth it; -// consider replacing with e.g. `Any::downcast`. - /// Implemented by `#[derive(RuntimeFactors)]` pub trait RuntimeFactors: Sized { type AppState; From a33c964314c36677b3bb7e0e1c90f1756fee8418 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Mon, 10 Jun 2024 11:15:10 -0400 Subject: [PATCH 16/22] factors: Rename MakeVariablesProvider::TYPE -> RUNTIME_CONFIG_TYPE Signed-off-by: Lann Martin --- crates/factor-variables/src/lib.rs | 4 ++-- crates/factor-variables/src/provider.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/factor-variables/src/lib.rs b/crates/factor-variables/src/lib.rs index c5c4ec0990..82903787d1 100644 --- a/crates/factor-variables/src/lib.rs +++ b/crates/factor-variables/src/lib.rs @@ -26,10 +26,10 @@ impl VariablesFactor { ) -> anyhow::Result<()> { if self .provider_types - .insert(T::TYPE, provider_maker(provider_type)) + .insert(T::RUNTIME_CONFIG_TYPE, provider_maker(provider_type)) .is_some() { - bail!("duplicate provider type {:?}", T::TYPE); + bail!("duplicate provider type {:?}", T::RUNTIME_CONFIG_TYPE); } Ok(()) } diff --git a/crates/factor-variables/src/provider.rs b/crates/factor-variables/src/provider.rs index a8ceeccaa8..5b8530b514 100644 --- a/crates/factor-variables/src/provider.rs +++ b/crates/factor-variables/src/provider.rs @@ -5,7 +5,7 @@ use spin_expressions::{async_trait::async_trait, Key, Provider}; use spin_factors::anyhow; pub trait MakeVariablesProvider: 'static { - const TYPE: &'static str; + const RUNTIME_CONFIG_TYPE: &'static str; type RuntimeConfig: DeserializeOwned; type Provider: Provider; @@ -26,7 +26,7 @@ pub(crate) fn provider_maker(provider_type: T) -> Prov pub struct StaticVariables; impl MakeVariablesProvider for StaticVariables { - const TYPE: &'static str = "static"; + const RUNTIME_CONFIG_TYPE: &'static str = "static"; type RuntimeConfig = StaticVariablesProvider; type Provider = StaticVariablesProvider; From a3c48ae98fa015dcaef8ec5fa44180b913c86d96 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Mon, 10 Jun 2024 11:17:39 -0400 Subject: [PATCH 17/22] factors: ProviderMaker -> ProviderFromToml Signed-off-by: Lann Martin --- crates/factor-variables/src/lib.rs | 6 +++--- crates/factor-variables/src/provider.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/factor-variables/src/lib.rs b/crates/factor-variables/src/lib.rs index 82903787d1..29c36a9750 100644 --- a/crates/factor-variables/src/lib.rs +++ b/crates/factor-variables/src/lib.rs @@ -2,7 +2,7 @@ mod provider; use std::{collections::HashMap, sync::Arc}; -use provider::{provider_maker, ProviderMaker}; +use provider::{provider_from_toml, ProviderFromToml}; use serde::Deserialize; use spin_expressions::ProviderResolver; use spin_factors::{ @@ -16,7 +16,7 @@ pub use provider::{MakeVariablesProvider, StaticVariables}; #[derive(Default)] pub struct VariablesFactor { - provider_types: HashMap<&'static str, ProviderMaker>, + provider_types: HashMap<&'static str, ProviderFromToml>, } impl VariablesFactor { @@ -26,7 +26,7 @@ impl VariablesFactor { ) -> anyhow::Result<()> { if self .provider_types - .insert(T::RUNTIME_CONFIG_TYPE, provider_maker(provider_type)) + .insert(T::RUNTIME_CONFIG_TYPE, provider_from_toml(provider_type)) .is_some() { bail!("duplicate provider type {:?}", T::RUNTIME_CONFIG_TYPE); diff --git a/crates/factor-variables/src/provider.rs b/crates/factor-variables/src/provider.rs index 5b8530b514..83d634bf9b 100644 --- a/crates/factor-variables/src/provider.rs +++ b/crates/factor-variables/src/provider.rs @@ -13,9 +13,9 @@ pub trait MakeVariablesProvider: 'static { fn make_provider(&self, runtime_config: Self::RuntimeConfig) -> anyhow::Result; } -pub(crate) type ProviderMaker = Box anyhow::Result>>; +pub(crate) type ProviderFromToml = Box anyhow::Result>>; -pub(crate) fn provider_maker(provider_type: T) -> ProviderMaker { +pub(crate) fn provider_from_toml(provider_type: T) -> ProviderFromToml { Box::new(move |table| { let runtime_config: T::RuntimeConfig = table.try_into()?; let provider = provider_type.make_provider(runtime_config)?; From 2cd9a67a2fee7e1085165d14125f02f8d557389d Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Tue, 11 Jun 2024 12:48:59 -0400 Subject: [PATCH 18/22] factors: Add OutboundHttpFactor Signed-off-by: Lann Martin --- Cargo.lock | 111 ++++++++----------- crates/factor-outbound-http/Cargo.toml | 18 +++ crates/factor-outbound-http/src/lib.rs | 54 +++++++++ crates/factor-outbound-http/src/spin.rs | 31 ++++++ crates/factor-outbound-http/src/wasi.rs | 40 +++++++ crates/factor-outbound-networking/src/lib.rs | 58 +++++++--- crates/factor-wasi/src/lib.rs | 14 ++- crates/factors-derive/src/lib.rs | 12 ++ crates/factors/Cargo.toml | 1 + crates/factors/src/prepare.rs | 10 +- crates/factors/src/runtime_config.rs | 17 +-- crates/factors/src/runtime_factors.rs | 91 ++++++++++++++- crates/factors/tests/smoke.rs | 17 ++- 13 files changed, 367 insertions(+), 107 deletions(-) create mode 100644 crates/factor-outbound-http/Cargo.toml create mode 100644 crates/factor-outbound-http/src/lib.rs create mode 100644 crates/factor-outbound-http/src/spin.rs create mode 100644 crates/factor-outbound-http/src/wasi.rs diff --git a/Cargo.lock b/Cargo.lock index 4eaa2e597a..2801b1fed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1566,8 +1566,7 @@ dependencies = [ [[package]] name = "cranelift-bforest" version = "0.109.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6b33d7e757a887989eb18b35712b2a67d96171ec3149d1bfb657b29b7b367c" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "cranelift-entity", ] @@ -1575,8 +1574,7 @@ dependencies = [ [[package]] name = "cranelift-codegen" version = "0.109.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9acf15cb22be42d07c3b57d7856329cb228b7315d385346149df2566ad5e4aa" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "bumpalo", "cranelift-bforest", @@ -1597,8 +1595,7 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" version = "0.109.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e934d301392b73b3f8b0540391fb82465a0f179a3cee7c726482ac4727efcc97" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "cranelift-codegen-shared", ] @@ -1606,14 +1603,12 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" version = "0.109.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb2a2566b3d54b854dfb288b3b187f6d3d17d6f762c92898207eba302931da" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" [[package]] name = "cranelift-control" version = "0.109.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0100f33b704cdacd01ad66ff41f8c5030d57cbff078e2a4e49ab1822591299fa" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "arbitrary", ] @@ -1621,8 +1616,7 @@ dependencies = [ [[package]] name = "cranelift-entity" version = "0.109.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8cfdc315e5d18997093e040a8d234bea1ac1e118a716d3e30f40d449e78207b" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "serde 1.0.197", "serde_derive", @@ -1631,8 +1625,7 @@ dependencies = [ [[package]] name = "cranelift-frontend" version = "0.109.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f74b84f16af2e982b0c0c72233503d9d55cbfe3865dbe807ca28dc6642a28b5" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "cranelift-codegen", "log", @@ -1643,14 +1636,12 @@ dependencies = [ [[package]] name = "cranelift-isle" version = "0.109.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adf306d3dde705fb94bd48082f01d38c4ededc74293a4c007805f610bf08bc6e" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" [[package]] name = "cranelift-native" version = "0.109.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea0ebdef7aff4a79bcbc8b6495f31315f16b3bf311152f472eaa8d679352581" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "cranelift-codegen", "libc", @@ -1660,8 +1651,7 @@ dependencies = [ [[package]] name = "cranelift-wasm" version = "0.109.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d549108a1942065cdbac3bb96c2952afa0e1b9a3beff4b08c4308ac72257576d" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -7558,6 +7548,20 @@ dependencies = [ "toml 0.5.11", ] +[[package]] +name = "spin-factor-outbound-http" +version = "2.6.0-pre0" +dependencies = [ + "anyhow", + "http 1.1.0", + "spin-factor-outbound-networking", + "spin-factor-wasi", + "spin-factors", + "spin-world", + "tracing", + "wasmtime-wasi-http", +] + [[package]] name = "spin-factor-outbound-networking" version = "2.6.0-pre0" @@ -7599,6 +7603,7 @@ dependencies = [ "serde 1.0.197", "serde_json", "spin-app", + "spin-factor-outbound-http", "spin-factor-outbound-networking", "spin-factor-variables", "spin-factor-wasi", @@ -9488,8 +9493,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi-common" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86fd41e1e26ff6af9451c6a332a5ce5f5283ca51e87d875cdd9a05305598ee3" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "bitflags 2.5.0", @@ -9822,8 +9826,7 @@ dependencies = [ [[package]] name = "wasmtime" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d8b5e7a4d54917c5ebe555b9667337e5f93383f49bddaaeec2eba68093b45" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "addr2line", "anyhow", @@ -9878,8 +9881,7 @@ dependencies = [ [[package]] name = "wasmtime-asm-macros" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d697d99c341d4a9ffb72f3af7a02124d233eeb59aee010f36d88e97cca553d5e" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "cfg-if", ] @@ -9887,8 +9889,7 @@ dependencies = [ [[package]] name = "wasmtime-cache" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916610f9ae9a6c22deb25bba2e6247ba9f00b093d30620875203b91328a1adfa" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "base64 0.21.7", @@ -9907,8 +9908,7 @@ dependencies = [ [[package]] name = "wasmtime-component-macro" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b29b462b068e73b5b27fae092a27f47e5937cabf6b26be2779c978698a52feca" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "proc-macro2", @@ -9922,14 +9922,12 @@ dependencies = [ [[package]] name = "wasmtime-component-util" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d2912c53d9054984b380dfbd7579f9c3681b2a73b903a56bd71a1c4f175f1e" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" [[package]] name = "wasmtime-cranelift" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3975deafea000457ba84355c7c0fce0372937204f77026510b7b454f28a3a65" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "cfg-if", @@ -9952,8 +9950,7 @@ dependencies = [ [[package]] name = "wasmtime-environ" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f444e900e848b884d8a8a2949b6f5b92af642a3e663ff8fbe78731143a55be61" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "cpp_demangle", @@ -9977,8 +9974,7 @@ dependencies = [ [[package]] name = "wasmtime-fiber" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ded58eb2d1bf0dcd2182d0ccd7055c4b10b50d711514f1d73f61515d0fa829d" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "cc", @@ -9992,8 +9988,7 @@ dependencies = [ [[package]] name = "wasmtime-jit-debug" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bc54198c6720f098210a85efb3ba8c078d1de4d373cdb6778850a66ae088d11" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "object 0.36.0", "once_cell", @@ -10004,8 +9999,7 @@ dependencies = [ [[package]] name = "wasmtime-jit-icache-coherence" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5afe2f0499542f9a4bcfa1b55bfdda803b6ade4e7c93c6b99e0f39dba44b0a91" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "cfg-if", @@ -10016,14 +10010,12 @@ dependencies = [ [[package]] name = "wasmtime-slab" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a7de1f2bec5bbb35d532e61c85c049dc84ae671df60492f90b954ecf21169e7" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" [[package]] name = "wasmtime-types" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "412463e9000e14cf6856be48628d2213c20c153e29ffc22b036980c892ea6964" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "cranelift-entity", "serde 1.0.197", @@ -10035,8 +10027,7 @@ dependencies = [ [[package]] name = "wasmtime-versioned-export-macros" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de5a9bc4f44ceeb168e9e8e3be4e0b4beb9095b468479663a9e24c667e36826f" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "proc-macro2", "quote", @@ -10046,8 +10037,7 @@ dependencies = [ [[package]] name = "wasmtime-wasi" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8abb1301089ed8e0b4840f539cba316a73ac382090f1b25d22d8c8eed8df49c7" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "async-trait", @@ -10077,8 +10067,7 @@ dependencies = [ [[package]] name = "wasmtime-wasi-http" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "315cadc284b808cfbd6be9295da4009144c106723f09b421ce6c6d89275cfdb7" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "async-trait", @@ -10100,8 +10089,7 @@ dependencies = [ [[package]] name = "wasmtime-winch" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed4db238a0241df2d15f79ad17b3a37a27f2ea6cb885894d81b42ae107544466" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "cranelift-codegen", @@ -10117,8 +10105,7 @@ dependencies = [ [[package]] name = "wasmtime-wit-bindgen" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc077306b38288262e5ba01d4b21532a6987416cdc0aedf04bb06c22a68fdc" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "heck 0.4.1", @@ -10297,8 +10284,7 @@ dependencies = [ [[package]] name = "wiggle" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29830e5d01c182d24b94092c697aa7ab0ee97d22e78a2bf40ca91eae6ebca5c2" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "async-trait", @@ -10312,8 +10298,7 @@ dependencies = [ [[package]] name = "wiggle-generate" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "557567f2793508760cd855f7659b7a0b9dc4dbc451f53f1415d6943a15311ade" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "heck 0.4.1", @@ -10327,8 +10312,7 @@ dependencies = [ [[package]] name = "wiggle-macro" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc26129a8aea20b62c961d1b9ab4a3c3b56b10042ed85d004f8678af0f21ba6e" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "proc-macro2", "quote", @@ -10370,8 +10354,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c6915884e731b2db0d8cf08cb64474cb69221a161675fd3c135f91febc3daa" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=69a78fc1359769f320b5968042cb9d988336dd54#69a78fc1359769f320b5968042cb9d988336dd54" dependencies = [ "anyhow", "cranelift-codegen", diff --git a/crates/factor-outbound-http/Cargo.toml b/crates/factor-outbound-http/Cargo.toml new file mode 100644 index 0000000000..e922255e35 --- /dev/null +++ b/crates/factor-outbound-http/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "spin-factor-outbound-http" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } + +[dependencies] +anyhow = "1.0" +http = "1.1.0" +spin-factor-outbound-networking = { path = "../factor-outbound-networking" } +spin-factor-wasi = { path = "../factor-wasi" } +spin-factors = { path = "../factors" } +spin-world = { path = "../world" } +tracing = { workspace = true } +wasmtime-wasi-http = { workspace = true } + +[lints] +workspace = true diff --git a/crates/factor-outbound-http/src/lib.rs b/crates/factor-outbound-http/src/lib.rs new file mode 100644 index 0000000000..026e524137 --- /dev/null +++ b/crates/factor-outbound-http/src/lib.rs @@ -0,0 +1,54 @@ +mod spin; +mod wasi; + +use spin_factor_outbound_networking::{OutboundAllowedHosts, OutboundNetworkingFactor}; +use spin_factors::{ + anyhow, ConfigureAppContext, Factor, InstanceBuilders, PrepareContext, RuntimeFactors, + SelfInstanceBuilder, +}; +use wasmtime_wasi_http::WasiHttpCtx; +pub struct OutboundHttpFactor; + +impl Factor for OutboundHttpFactor { + type RuntimeConfig = (); + type AppState = (); + type InstanceBuilder = InstanceState; + + fn init( + &mut self, + mut ctx: spin_factors::InitContext, + ) -> anyhow::Result<()> { + ctx.link_bindings(spin_world::v1::http::add_to_linker)?; + if let Some(linker) = ctx.linker() { + wasi::add_to_linker::(linker)?; + } + Ok(()) + } + + fn configure_app( + &self, + _ctx: ConfigureAppContext, + ) -> anyhow::Result { + Ok(()) + } + + fn prepare( + _ctx: PrepareContext, + builders: &mut InstanceBuilders, + ) -> anyhow::Result { + let allowed_hosts = builders + .get_mut::()? + .allowed_hosts(); + Ok(InstanceState { + allowed_hosts, + wasi_http_ctx: WasiHttpCtx::new(), + }) + } +} + +pub struct InstanceState { + allowed_hosts: OutboundAllowedHosts, + wasi_http_ctx: WasiHttpCtx, +} + +impl SelfInstanceBuilder for InstanceState {} diff --git a/crates/factor-outbound-http/src/spin.rs b/crates/factor-outbound-http/src/spin.rs new file mode 100644 index 0000000000..22ea723f2f --- /dev/null +++ b/crates/factor-outbound-http/src/spin.rs @@ -0,0 +1,31 @@ +use spin_factor_outbound_networking::OutboundUrl; +use spin_world::{ + async_trait, + v1::http, + v1::http_types::{self, HttpError, Request, Response}, +}; + +#[async_trait] +impl http::Host for crate::InstanceState { + async fn send_request(&mut self, req: Request) -> Result { + // FIXME(lann): This is all just a stub to test allowed_outbound_hosts + let outbound_url = OutboundUrl::parse(&req.uri, "https").or(Err(HttpError::InvalidUrl))?; + match self.allowed_hosts.allows(&outbound_url).await { + Ok(true) => (), + _ => { + return Err(HttpError::DestinationNotAllowed); + } + } + Ok(Response { + status: 200, + headers: None, + body: Some(b"test response".into()), + }) + } +} + +impl http_types::Host for crate::InstanceState { + fn convert_http_error(&mut self, err: HttpError) -> anyhow::Result { + Ok(err) + } +} diff --git a/crates/factor-outbound-http/src/wasi.rs b/crates/factor-outbound-http/src/wasi.rs new file mode 100644 index 0000000000..3de0d8437b --- /dev/null +++ b/crates/factor-outbound-http/src/wasi.rs @@ -0,0 +1,40 @@ +use anyhow::Context; +use spin_factors::{Linker, RuntimeFactors}; +use wasmtime_wasi_http::{WasiHttpImpl, WasiHttpView}; + +pub(crate) fn add_to_linker(linker: &mut Linker) -> anyhow::Result<()> { + fn type_annotate(f: F) -> F + where + F: Fn(&mut T) -> WasiHttpImpl, + { + f + } + let wasi_and_http_getter = + T::instance_state_getter2::() + .context("failed to get WasiFactor")?; + let host_getter = type_annotate(move |data| { + let (wasi, http) = wasi_and_http_getter.get_states(data); + WasiHttpImpl(MutStates { http, wasi }) + }); + wasmtime_wasi_http::bindings::http::outgoing_handler::add_to_linker_get_host( + linker, + host_getter, + )?; + wasmtime_wasi_http::bindings::http::types::add_to_linker_get_host(linker, host_getter)?; + Ok(()) +} + +struct MutStates<'a> { + http: &'a mut crate::InstanceState, + wasi: &'a mut spin_factor_wasi::InstanceState, +} + +impl<'a> WasiHttpView for MutStates<'a> { + fn ctx(&mut self) -> &mut wasmtime_wasi_http::WasiHttpCtx { + &mut self.http.wasi_http_ctx + } + + fn table(&mut self) -> &mut spin_factors::wasmtime::component::ResourceTable { + self.wasi.table() + } +} diff --git a/crates/factor-outbound-networking/src/lib.rs b/crates/factor-outbound-networking/src/lib.rs index 89a6d1e8c7..b4f88c7146 100644 --- a/crates/factor-outbound-networking/src/lib.rs +++ b/crates/factor-outbound-networking/src/lib.rs @@ -13,6 +13,10 @@ use spin_factors::{ }; use spin_outbound_networking::{AllowedHostsConfig, ALLOWED_HOSTS_KEY}; +pub use spin_outbound_networking::OutboundUrl; + +pub type SharedFutureResult = Shared, Arc>>>; + pub struct OutboundNetworkingFactor; impl Factor for OutboundNetworkingFactor { @@ -59,7 +63,7 @@ impl Factor for OutboundNetworkingFactor { let prepared = resolver.prepare().await?; AllowedHostsConfig::parse(&hosts, &prepared) } - .map(Arc::new) + .map(|res| res.map(Arc::new).map_err(Arc::new)) .boxed() .shared(); // let prepared_resolver = resolver.prepare().await?; @@ -74,10 +78,10 @@ impl Factor for OutboundNetworkingFactor { wasi_preparer.outbound_socket_addr_check(move |addr| { let hosts_future = hosts_future.clone(); async move { - match &*hosts_future.await { + match hosts_future.await { Ok(allowed_hosts) => { // TODO: verify this actually works... - spin_outbound_networking::check_url(&addr.to_string(), "*", allowed_hosts) + spin_outbound_networking::check_url(&addr.to_string(), "*", &allowed_hosts) } Err(err) => { // TODO: should this trap (somehow)? @@ -87,7 +91,9 @@ impl Factor for OutboundNetworkingFactor { } } }); - Ok(InstanceBuilder::new(allowed_hosts_future)) + Ok(InstanceBuilder { + allowed_hosts_future, + }) } } @@ -95,25 +101,16 @@ pub struct AppState { component_allowed_hosts: HashMap>, } -type SharedFutureResult = Shared>>>; - pub struct InstanceBuilder { - allowed_hosts_future: Option>, + allowed_hosts_future: SharedFutureResult, } impl InstanceBuilder { - fn new(allowed_hosts_future: SharedFutureResult) -> Self { - Self { - allowed_hosts_future: Some(allowed_hosts_future), + pub fn allowed_hosts(&self) -> OutboundAllowedHosts { + OutboundAllowedHosts { + allowed_hosts_future: self.allowed_hosts_future.clone(), } } - - pub async fn resolve_allowed_hosts(&self) -> Arc> { - self.allowed_hosts_future - .clone() - .expect("allowed_hosts_future not set") - .await - } } impl FactorInstanceBuilder for InstanceBuilder { @@ -123,3 +120,30 @@ impl FactorInstanceBuilder for InstanceBuilder { Ok(()) } } + +// TODO: Refactor w/ spin-outbound-networking crate to simplify +pub struct OutboundAllowedHosts { + allowed_hosts_future: SharedFutureResult, +} + +impl OutboundAllowedHosts { + pub async fn allows(&self, url: &OutboundUrl) -> anyhow::Result { + Ok(self.resolve().await?.allows(url)) + } + + pub async fn check_url(&self, url: &str, scheme: &str) -> anyhow::Result { + let allowed_hosts = self.resolve().await?; + Ok(spin_outbound_networking::check_url( + url, + scheme, + &allowed_hosts, + )) + } + + async fn resolve(&self) -> anyhow::Result> { + self.allowed_hosts_future.clone().await.map_err(|err| { + // TODO: better way to handle this? + anyhow::Error::msg(err) + }) + } +} diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs index c6fdb1279b..226477bb32 100644 --- a/crates/factor-wasi/src/lib.rs +++ b/crates/factor-wasi/src/lib.rs @@ -6,7 +6,7 @@ use spin_factors::{ anyhow, AppComponent, Factor, FactorInstanceBuilder, InitContext, InstanceBuilders, PrepareContext, RuntimeFactors, }; -use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; +use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiImpl, WasiView}; pub struct WasiFactor { files_mounter: Box, @@ -29,14 +29,14 @@ impl Factor for WasiFactor { &mut self, mut ctx: InitContext, ) -> anyhow::Result<()> { - fn type_annotate(f: F) -> F + fn type_annotate(f: F) -> F where - F: Fn(&mut T) -> &mut dyn WasiView, + F: Fn(&mut T) -> WasiImpl<&mut U>, { f } let get_data = ctx.get_data_fn(); - let closure = type_annotate(move |data| get_data(data) as &mut dyn WasiView); + let closure = type_annotate(move |data| WasiImpl(get_data(data))); if let Some(linker) = ctx.linker() { use wasmtime_wasi::bindings; bindings::clocks::wall_clock::add_to_linker_get_host(linker, closure)?; @@ -189,6 +189,12 @@ pub struct InstanceState { table: ResourceTable, } +impl InstanceState { + pub fn table(&mut self) -> &mut ResourceTable { + &mut self.table + } +} + impl WasiView for InstanceState { fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index d787797b88..b7e30734c4 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -65,6 +65,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { Ok(quote! { impl #name { + #[allow(clippy::needless_option_as_deref)] pub fn init( &mut self, mut linker: Option<&mut #wasmtime::component::Linker<#state_name>>, @@ -170,6 +171,17 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { )* None } + + fn instance_state_offset() -> Option { + let type_id = #TypeId::of::(); + #( + if type_id == #TypeId::of::<#factor_types>() { + return Some(std::mem::offset_of!(Self::InstanceState, #factor_names)); + } + )* + None + } + } #vis struct #app_state_name { diff --git a/crates/factors/Cargo.toml b/crates/factors/Cargo.toml index 62255f7473..117e894723 100644 --- a/crates/factors/Cargo.toml +++ b/crates/factors/Cargo.toml @@ -16,6 +16,7 @@ wasmtime = { workspace = true } [dev-dependencies] serde_json = "1.0" spin-factors-derive = { path = "../factors-derive", features = ["expander"] } +spin-factor-outbound-http = { path = "../factor-outbound-http" } spin-factor-outbound-networking = { path = "../factor-outbound-networking" } spin-factor-variables = { path = "../factor-variables" } spin-factor-wasi = { path = "../factor-wasi" } diff --git a/crates/factors/src/prepare.rs b/crates/factors/src/prepare.rs index 81359c5c5b..525f774044 100644 --- a/crates/factors/src/prepare.rs +++ b/crates/factors/src/prepare.rs @@ -1,4 +1,4 @@ -use std::{any::Any, marker::PhantomData}; +use std::any::Any; use anyhow::Context; @@ -10,13 +10,11 @@ pub trait FactorInstanceBuilder: Any { fn build(self) -> anyhow::Result; } -pub struct DefaultInstanceBuilder(PhantomData T>); - -impl FactorInstanceBuilder for DefaultInstanceBuilder { - type InstanceState = T; +impl FactorInstanceBuilder for () { + type InstanceState = (); fn build(self) -> anyhow::Result { - Ok(Default::default()) + Ok(()) } } diff --git a/crates/factors/src/runtime_config.rs b/crates/factors/src/runtime_config.rs index 3f313f0f13..372c9ffe69 100644 --- a/crates/factors/src/runtime_config.rs +++ b/crates/factors/src/runtime_config.rs @@ -24,27 +24,28 @@ impl FactorRuntimeConfig for () { pub trait RuntimeConfigSource { /// Returns an iterator of factor config keys available in this source. /// - /// Should only include keys that have been positively provided. A runtime - /// may treat unrecognized keys as a warning or error. - fn config_keys(&self) -> impl IntoIterator; + /// Should only include keys that have been positively provided and that + /// haven't already been parsed by the runtime. A runtime may treat + /// unrecognized keys as a warning or error. + fn factor_config_keys(&self) -> impl IntoIterator; /// Returns deserialized runtime config of the given type for the given /// factor config key. /// /// Returns Ok(None) if no configuration is available for the given key. /// Returns Err if configuration is available but deserialization fails. - fn get_config(&self, key: &str) -> anyhow::Result>; + fn get_factor_config(&self, key: &str) -> anyhow::Result>; } impl RuntimeConfigSource for () { - fn get_config( + fn get_factor_config( &self, _factor_config_key: &str, ) -> anyhow::Result> { Ok(None) } - fn config_keys(&self) -> impl IntoIterator { + fn factor_config_keys(&self) -> impl IntoIterator { std::iter::empty() } } @@ -59,7 +60,7 @@ impl RuntimeConfigTracker { #[doc(hidden)] pub fn new(source: S) -> Self { let unused_keys = source - .config_keys() + .factor_config_keys() .into_iter() .map(ToOwned::to_owned) .collect(); @@ -95,6 +96,6 @@ impl RuntimeConfigTracker { bail!("already used runtime config key {key:?}"); } self.unused_keys.remove(key); - self.source.get_config::(key) + self.source.get_factor_config::(key) } } diff --git a/crates/factors/src/runtime_factors.rs b/crates/factors/src/runtime_factors.rs index 3d61638474..4df65d4757 100644 --- a/crates/factors/src/runtime_factors.rs +++ b/crates/factors/src/runtime_factors.rs @@ -1,7 +1,9 @@ -use crate::Factor; +use std::{any::TypeId, marker::PhantomData}; + +use crate::{factor::FactorInstanceState, Factor}; /// Implemented by `#[derive(RuntimeFactors)]` -pub trait RuntimeFactors: Sized { +pub trait RuntimeFactors: Sized + 'static { type AppState; type InstanceBuilders; type InstanceState: Send + 'static; @@ -11,4 +13,89 @@ pub trait RuntimeFactors: Sized { fn instance_builder_mut( builders: &mut Self::InstanceBuilders, ) -> Option>; + + #[doc(hidden)] + fn instance_state_offset() -> Option; + + fn instance_state_getter() -> Option> { + StateGetter::new() + } + + fn instance_state_getter2() -> Option> { + StateGetter2::new() + } +} + +pub struct StateGetter { + offset: isize, + _phantom: PhantomData F>, +} + +impl StateGetter { + fn new() -> Option { + Some(Self { + offset: T::instance_state_offset::()?.try_into().unwrap(), + _phantom: PhantomData, + }) + } + + pub fn get_state<'a>( + &self, + instance_state: &'a mut T::InstanceState, + ) -> &'a mut FactorInstanceState { + let ptr = instance_state as *mut T::InstanceState; + unsafe { &mut *(ptr.offset(self.offset) as *mut FactorInstanceState) } + } +} + +impl Clone for StateGetter { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for StateGetter {} + +pub struct StateGetter2 { + offset1: isize, + offset2: isize, + _phantom: PhantomData (F1, F2)>, +} + +impl StateGetter2 { + fn new() -> Option { + // Only safe if F1 and F2 are different (and so do not alias) + if TypeId::of::() == TypeId::of::() { + return None; + } + Some(StateGetter2 { + offset1: T::instance_state_offset::()?.try_into().unwrap(), + offset2: T::instance_state_offset::()?.try_into().unwrap(), + _phantom: PhantomData, + }) + } + + pub fn get_states<'a>( + &self, + instance_state: &'a mut T::InstanceState, + ) -> ( + &'a mut FactorInstanceState, + &'a mut FactorInstanceState, + ) { + let ptr = instance_state as *mut T::InstanceState; + unsafe { + ( + &mut *(ptr.offset(self.offset1) as *mut FactorInstanceState), + &mut *(ptr.offset(self.offset2) as *mut FactorInstanceState), + ) + } + } } + +impl Clone for StateGetter2 { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for StateGetter2 {} diff --git a/crates/factors/tests/smoke.rs b/crates/factors/tests/smoke.rs index a88f9ff5c2..6dfdf48999 100644 --- a/crates/factors/tests/smoke.rs +++ b/crates/factors/tests/smoke.rs @@ -1,4 +1,5 @@ use spin_app::App; +use spin_factor_outbound_http::OutboundHttpFactor; use spin_factor_outbound_networking::OutboundNetworkingFactor; use spin_factor_variables::{StaticVariables, VariablesFactor}; use spin_factor_wasi::{preview1::WasiPreview1Factor, DummyFilesMounter, WasiFactor}; @@ -7,19 +8,20 @@ use spin_factors::{FactorRuntimeConfig, RuntimeConfigSource, RuntimeFactors}; #[derive(RuntimeFactors)] struct Factors { wasi: WasiFactor, - wasip1: WasiPreview1Factor, + wasi_p1: WasiPreview1Factor, variables: VariablesFactor, outbound_networking_factor: OutboundNetworkingFactor, + outbound_http_factor: OutboundHttpFactor, } -#[tokio::test(flavor = "multi_thread")] +#[tokio::test] async fn main() -> anyhow::Result<()> { let mut factors = Factors { wasi: WasiFactor::new(DummyFilesMounter), - wasip1: WasiPreview1Factor, + wasi_p1: WasiPreview1Factor, variables: VariablesFactor::default(), outbound_networking_factor: OutboundNetworkingFactor, - // outbound_http_factor: OutboundHttpFactor, + outbound_http_factor: OutboundHttpFactor, }; factors.variables.add_provider_type(StaticVariables)?; @@ -79,11 +81,14 @@ async fn main() -> anyhow::Result<()> { struct TestSource; impl RuntimeConfigSource for TestSource { - fn config_keys(&self) -> impl IntoIterator { + fn factor_config_keys(&self) -> impl IntoIterator { [spin_factor_variables::RuntimeConfig::KEY] } - fn get_config(&self, key: &str) -> anyhow::Result> { + fn get_factor_config( + &self, + key: &str, + ) -> anyhow::Result> { let Some(table) = toml::toml! { [[variable_provider]] type = "static" From 647b1f5e9eb020c9a41ef628401528e3b6d05a20 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Wed, 12 Jun 2024 11:49:12 -0400 Subject: [PATCH 19/22] factors: Add KeyValueFactor Signed-off-by: Lann Martin --- Cargo.lock | 36 ++++- crates/factor-key-value/Cargo.toml | 17 +++ crates/factor-key-value/src/lib.rs | 163 ++++++++++++++++++++++ crates/factor-key-value/src/store.rs | 24 ++++ crates/factor-outbound-http/src/lib.rs | 3 + crates/factor-outbound-http/src/wasi.rs | 11 ++ crates/factor-variables/src/lib.rs | 24 ++-- crates/factor-variables/src/provider.rs | 4 +- crates/factors-derive/src/lib.rs | 14 +- crates/factors/Cargo.toml | 10 +- crates/factors/src/lib.rs | 1 + crates/factors/src/prepare.rs | 4 +- crates/factors/src/runtime_factors.rs | 44 +++--- crates/factors/tests/smoke-app/.gitignore | 2 + crates/factors/tests/smoke-app/Cargo.toml | 15 ++ crates/factors/tests/smoke-app/spin.toml | 25 ++++ crates/factors/tests/smoke-app/src/lib.rs | 19 +++ crates/factors/tests/smoke.rs | 126 +++++++++++++---- 18 files changed, 468 insertions(+), 74 deletions(-) create mode 100644 crates/factor-key-value/Cargo.toml create mode 100644 crates/factor-key-value/src/lib.rs create mode 100644 crates/factor-key-value/src/store.rs create mode 100644 crates/factors/tests/smoke-app/.gitignore create mode 100644 crates/factors/tests/smoke-app/Cargo.toml create mode 100644 crates/factors/tests/smoke-app/spin.toml create mode 100644 crates/factors/tests/smoke-app/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 2801b1fed8..a530a00dab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2476,6 +2476,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset 0.9.1", + "rustc_version", +] + [[package]] name = "filetime" version = "0.2.23" @@ -3292,12 +3302,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http 1.1.0", "http-body 1.0.0", "pin-project-lite", @@ -7548,6 +7558,18 @@ dependencies = [ "toml 0.5.11", ] +[[package]] +name = "spin-factor-key-value" +version = "2.6.0-pre0" +dependencies = [ + "anyhow", + "serde 1.0.197", + "spin-factors", + "spin-key-value", + "spin-world", + "toml 0.8.12", +] + [[package]] name = "spin-factor-outbound-http" version = "2.6.0-pre0" @@ -7600,19 +7622,27 @@ name = "spin-factors" version = "2.6.0-pre0" dependencies = [ "anyhow", + "field-offset", + "http 1.1.0", + "http-body-util", "serde 1.0.197", "serde_json", "spin-app", + "spin-componentize", + "spin-factor-key-value", "spin-factor-outbound-http", "spin-factor-outbound-networking", "spin-factor-variables", "spin-factor-wasi", "spin-factors-derive", + "spin-key-value-sqlite", + "spin-loader", "thiserror", "tokio", "toml 0.8.12", "tracing", "wasmtime", + "wasmtime-wasi-http", ] [[package]] diff --git a/crates/factor-key-value/Cargo.toml b/crates/factor-key-value/Cargo.toml new file mode 100644 index 0000000000..89e060e463 --- /dev/null +++ b/crates/factor-key-value/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "spin-factor-key-value" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } + +[dependencies] +anyhow = "1.0" +serde = { version = "1.0", features = ["rc"] } +spin-factors = { path = "../factors" } +# TODO: merge with this crate +spin-key-value = { path = "../key-value" } +spin-world = { path = "../world" } +toml = "0.8" + +[lints] +workspace = true diff --git a/crates/factor-key-value/src/lib.rs b/crates/factor-key-value/src/lib.rs new file mode 100644 index 0000000000..55932e06fb --- /dev/null +++ b/crates/factor-key-value/src/lib.rs @@ -0,0 +1,163 @@ +mod store; + +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; + +use anyhow::{bail, ensure}; +use serde::Deserialize; +use spin_factors::{ + anyhow::{self, Context}, + ConfigureAppContext, Factor, FactorInstanceBuilder, FactorRuntimeConfig, InitContext, + InstanceBuilders, PrepareContext, RuntimeFactors, +}; +use spin_key_value::{ + CachingStoreManager, DelegatingStoreManager, KeyValueDispatch, StoreManager, + KEY_VALUE_STORES_KEY, +}; +use store::{store_from_toml_fn, StoreFromToml}; + +pub use store::MakeKeyValueStore; + +#[derive(Default)] +pub struct KeyValueFactor { + store_types: HashMap<&'static str, StoreFromToml>, +} + +impl KeyValueFactor { + pub fn add_store_type(&mut self, store_type: T) -> anyhow::Result<()> { + if self + .store_types + .insert(T::RUNTIME_CONFIG_TYPE, store_from_toml_fn(store_type)) + .is_some() + { + bail!( + "duplicate key value store type {:?}", + T::RUNTIME_CONFIG_TYPE + ); + } + Ok(()) + } +} + +impl Factor for KeyValueFactor { + type RuntimeConfig = RuntimeConfig; + type AppState = AppState; + type InstanceBuilder = InstanceBuilder; + + fn init( + &mut self, + mut ctx: InitContext, + ) -> anyhow::Result<()> { + ctx.link_bindings(spin_world::v1::key_value::add_to_linker)?; + ctx.link_bindings(spin_world::v2::key_value::add_to_linker)?; + Ok(()) + } + + fn configure_app( + &self, + mut ctx: ConfigureAppContext, + ) -> anyhow::Result { + // Build StoreManager from runtime config + let mut stores = HashMap::new(); + if let Some(runtime_config) = ctx.take_runtime_config() { + for (label, StoreConfig { type_, config }) in runtime_config.store_configs { + let store_maker = self + .store_types + .get(type_.as_str()) + .with_context(|| format!("unknown key value store type {type_:?}"))?; + let store = store_maker(config)?; + stores.insert(label, store); + } + } + let delegating_manager = DelegatingStoreManager::new(stores); + let caching_manager = CachingStoreManager::new(delegating_manager); + let store_manager = Arc::new(caching_manager); + + // Build component -> allowed stores map + let mut component_allowed_stores = HashMap::new(); + for component in ctx.app().components() { + let component_id = component.id().to_string(); + let key_value_stores = component + .get_metadata(KEY_VALUE_STORES_KEY)? + .unwrap_or_default() + .into_iter() + .collect::>(); + for label in &key_value_stores { + // TODO: port nicer errors from KeyValueComponent (via error type?) + ensure!( + store_manager.is_defined(label), + "unknown key_value_stores label {label:?} for component {component_id:?}" + ); + } + component_allowed_stores.insert(component_id, key_value_stores); + // TODO: warn (?) on unused store? + } + + Ok(AppState { + store_manager, + component_allowed_stores, + }) + } + + fn prepare( + ctx: PrepareContext, + _builders: &mut InstanceBuilders, + ) -> anyhow::Result { + let app_state = ctx.app_state(); + let allowed_stores = app_state + .component_allowed_stores + .get(ctx.app_component().id()) + .expect("component should be in component_stores") + .clone(); + Ok(InstanceBuilder { + store_manager: app_state.store_manager.clone(), + allowed_stores, + }) + } +} + +#[derive(Deserialize)] +#[serde(transparent)] +pub struct RuntimeConfig { + store_configs: HashMap, +} + +impl FactorRuntimeConfig for RuntimeConfig { + const KEY: &'static str = "key_value_store"; +} + +#[derive(Deserialize)] +struct StoreConfig { + #[serde(rename = "type")] + type_: String, + #[serde(flatten)] + config: toml::Table, +} + +type AppStoreManager = CachingStoreManager; + +pub struct AppState { + store_manager: Arc, + component_allowed_stores: HashMap>, +} + +pub struct InstanceBuilder { + store_manager: Arc, + allowed_stores: HashSet, +} + +impl FactorInstanceBuilder for InstanceBuilder { + type InstanceState = KeyValueDispatch; + + fn build(self) -> anyhow::Result { + let Self { + store_manager, + allowed_stores, + } = self; + let mut dispatch = KeyValueDispatch::new_with_capacity(u32::MAX); + dispatch.init(allowed_stores, store_manager); + Ok(dispatch) + } +} diff --git a/crates/factor-key-value/src/store.rs b/crates/factor-key-value/src/store.rs new file mode 100644 index 0000000000..5b0c956584 --- /dev/null +++ b/crates/factor-key-value/src/store.rs @@ -0,0 +1,24 @@ +use std::sync::Arc; + +use serde::de::DeserializeOwned; +use spin_key_value::StoreManager; + +pub trait MakeKeyValueStore: 'static { + const RUNTIME_CONFIG_TYPE: &'static str; + + type RuntimeConfig: DeserializeOwned; + type StoreManager: StoreManager; + + fn make_store(&self, runtime_config: Self::RuntimeConfig) + -> anyhow::Result; +} + +pub(crate) type StoreFromToml = Box anyhow::Result>>; + +pub(crate) fn store_from_toml_fn(provider_type: T) -> StoreFromToml { + Box::new(move |table| { + let runtime_config: T::RuntimeConfig = table.try_into()?; + let provider = provider_type.make_store(runtime_config)?; + Ok(Arc::new(provider)) + }) +} diff --git a/crates/factor-outbound-http/src/lib.rs b/crates/factor-outbound-http/src/lib.rs index 026e524137..3507b5b957 100644 --- a/crates/factor-outbound-http/src/lib.rs +++ b/crates/factor-outbound-http/src/lib.rs @@ -7,6 +7,9 @@ use spin_factors::{ SelfInstanceBuilder, }; use wasmtime_wasi_http::WasiHttpCtx; + +pub use wasi::get_wasi_http_view; + pub struct OutboundHttpFactor; impl Factor for OutboundHttpFactor { diff --git a/crates/factor-outbound-http/src/wasi.rs b/crates/factor-outbound-http/src/wasi.rs index 3de0d8437b..daec51fd0b 100644 --- a/crates/factor-outbound-http/src/wasi.rs +++ b/crates/factor-outbound-http/src/wasi.rs @@ -38,3 +38,14 @@ impl<'a> WasiHttpView for MutStates<'a> { self.wasi.table() } } + +// TODO: This is a little weird, organizationally +pub fn get_wasi_http_view( + instance_state: &mut T::InstanceState, +) -> anyhow::Result { + let wasi_and_http_getter = + T::instance_state_getter2::() + .context("failed to get WasiFactor")?; + let (wasi, http) = wasi_and_http_getter.get_states(instance_state); + Ok(MutStates { http, wasi }) +} diff --git a/crates/factor-variables/src/lib.rs b/crates/factor-variables/src/lib.rs index 29c36a9750..730d1d1dfc 100644 --- a/crates/factor-variables/src/lib.rs +++ b/crates/factor-variables/src/lib.rs @@ -2,7 +2,7 @@ mod provider; use std::{collections::HashMap, sync::Arc}; -use provider::{provider_from_toml, ProviderFromToml}; +use provider::{provider_from_toml_fn, ProviderFromToml}; use serde::Deserialize; use spin_expressions::ProviderResolver; use spin_factors::{ @@ -10,7 +10,7 @@ use spin_factors::{ ConfigureAppContext, Factor, FactorRuntimeConfig, InitContext, InstanceBuilders, PrepareContext, RuntimeFactors, SelfInstanceBuilder, }; -use spin_world::{async_trait, v1::config as v1_config, v2::variables}; +use spin_world::{async_trait, v1, v2::variables}; pub use provider::{MakeVariablesProvider, StaticVariables}; @@ -26,7 +26,7 @@ impl VariablesFactor { ) -> anyhow::Result<()> { if self .provider_types - .insert(T::RUNTIME_CONFIG_TYPE, provider_from_toml(provider_type)) + .insert(T::RUNTIME_CONFIG_TYPE, provider_from_toml_fn(provider_type)) .is_some() { bail!("duplicate provider type {:?}", T::RUNTIME_CONFIG_TYPE); @@ -44,8 +44,8 @@ impl Factor for VariablesFactor { &mut self, mut ctx: InitContext, ) -> anyhow::Result<()> { - ctx.link_bindings(v1_config::add_to_linker)?; - ctx.link_bindings(variables::add_to_linker)?; + ctx.link_bindings(spin_world::v1::config::add_to_linker)?; + ctx.link_bindings(spin_world::v2::variables::add_to_linker)?; Ok(()) } @@ -69,7 +69,7 @@ impl Factor for VariablesFactor { let provider_maker = self .provider_types .get(type_.as_str()) - .with_context(|| format!("unknown variables provider type {type_}"))?; + .with_context(|| format!("unknown variables provider type {type_:?}"))?; let provider = provider_maker(config)?; resolver.add_provider(provider); } @@ -144,18 +144,18 @@ impl variables::Host for InstanceState { } #[async_trait] -impl v1_config::Host for InstanceState { - async fn get_config(&mut self, key: String) -> Result { +impl v1::config::Host for InstanceState { + async fn get_config(&mut self, key: String) -> Result { ::get(self, key) .await .map_err(|err| match err { - variables::Error::InvalidName(msg) => v1_config::Error::InvalidKey(msg), - variables::Error::Undefined(msg) => v1_config::Error::Provider(msg), - other => v1_config::Error::Other(format!("{other}")), + variables::Error::InvalidName(msg) => v1::config::Error::InvalidKey(msg), + variables::Error::Undefined(msg) => v1::config::Error::Provider(msg), + other => v1::config::Error::Other(format!("{other}")), }) } - fn convert_error(&mut self, err: v1_config::Error) -> anyhow::Result { + fn convert_error(&mut self, err: v1::config::Error) -> anyhow::Result { Ok(err) } } diff --git a/crates/factor-variables/src/provider.rs b/crates/factor-variables/src/provider.rs index 83d634bf9b..f3ee948118 100644 --- a/crates/factor-variables/src/provider.rs +++ b/crates/factor-variables/src/provider.rs @@ -15,7 +15,9 @@ pub trait MakeVariablesProvider: 'static { pub(crate) type ProviderFromToml = Box anyhow::Result>>; -pub(crate) fn provider_from_toml(provider_type: T) -> ProviderFromToml { +pub(crate) fn provider_from_toml_fn( + provider_type: T, +) -> ProviderFromToml { Box::new(move |table| { let runtime_config: T::RuntimeConfig = table.try_into()?; let provider = provider_type.make_provider(runtime_config)?; diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index b7e30734c4..d64bc88b0d 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -56,6 +56,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { let TypeId = quote!(::std::any::TypeId); let factors_crate = format_ident!("spin_factors"); let factors_path = quote!(::#factors_crate); + let field_offset = quote!(#factors_path::__internal::field_offset); let wasmtime = quote!(#factors_path::wasmtime); let Result = quote!(#factors_path::Result); let Factor = quote!(#factors_path::Factor); @@ -172,16 +173,23 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { None } - fn instance_state_offset() -> Option { + fn instance_state_offset() -> Option< + #field_offset::FieldOffset< + Self::InstanceState, + <::InstanceBuilder as #FactorInstanceBuilder>::InstanceState, + > + > { let type_id = #TypeId::of::(); #( if type_id == #TypeId::of::<#factor_types>() { - return Some(std::mem::offset_of!(Self::InstanceState, #factor_names)); + let offset = #field_offset::offset_of!(Self::InstanceState => #factor_names); + return Some( + unsafe { ::std::mem::transmute(offset) } + ); } )* None } - } #vis struct #app_state_name { diff --git a/crates/factors/Cargo.toml b/crates/factors/Cargo.toml index 117e894723..7a2e5f1d7f 100644 --- a/crates/factors/Cargo.toml +++ b/crates/factors/Cargo.toml @@ -6,6 +6,7 @@ edition = { workspace = true } [dependencies] anyhow = "1.0" +field-offset = "0.3.6" serde = "1.0" spin-app = { path = "../app" } spin-factors-derive = { path = "../factors-derive" } @@ -14,14 +15,21 @@ tracing = { workspace = true } wasmtime = { workspace = true } [dev-dependencies] +http = "1.1.0" +http-body-util = "0.1.2" serde_json = "1.0" +spin-componentize = { path = "../componentize" } spin-factors-derive = { path = "../factors-derive", features = ["expander"] } +spin-factor-key-value = { path = "../factor-key-value" } spin-factor-outbound-http = { path = "../factor-outbound-http" } spin-factor-outbound-networking = { path = "../factor-outbound-networking" } spin-factor-variables = { path = "../factor-variables" } spin-factor-wasi = { path = "../factor-wasi" } -tokio = { version = "1", features = ["macros", "rt"] } +spin-key-value-sqlite = { path = "../key-value-sqlite" } +spin-loader = { path = "../loader" } +tokio = { version = "1", features = ["macros", "rt", "sync"] } toml = "0.8" +wasmtime-wasi-http = { workspace = true } [lints] workspace = true diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index 208adb84fc..b0203d6d55 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -28,4 +28,5 @@ pub type Result = wasmtime::Result; #[doc(hidden)] pub mod __internal { pub use crate::runtime_config::RuntimeConfigTracker; + pub use field_offset; } diff --git a/crates/factors/src/prepare.rs b/crates/factors/src/prepare.rs index 525f774044..851cce1f5e 100644 --- a/crates/factors/src/prepare.rs +++ b/crates/factors/src/prepare.rs @@ -5,7 +5,7 @@ use anyhow::Context; use crate::{AppComponent, Factor, RuntimeFactors}; pub trait FactorInstanceBuilder: Any { - type InstanceState; + type InstanceState: Send + 'static; fn build(self) -> anyhow::Result; } @@ -18,7 +18,7 @@ impl FactorInstanceBuilder for () { } } -pub trait SelfInstanceBuilder: 'static {} +pub trait SelfInstanceBuilder: Send + 'static {} impl FactorInstanceBuilder for T { type InstanceState = Self; diff --git a/crates/factors/src/runtime_factors.rs b/crates/factors/src/runtime_factors.rs index 4df65d4757..73f746039b 100644 --- a/crates/factors/src/runtime_factors.rs +++ b/crates/factors/src/runtime_factors.rs @@ -1,4 +1,4 @@ -use std::{any::TypeId, marker::PhantomData}; +use field_offset::FieldOffset; use crate::{factor::FactorInstanceState, Factor}; @@ -15,7 +15,8 @@ pub trait RuntimeFactors: Sized + 'static { ) -> Option>; #[doc(hidden)] - fn instance_state_offset() -> Option; + fn instance_state_offset( + ) -> Option>>; fn instance_state_getter() -> Option> { StateGetter::new() @@ -26,16 +27,14 @@ pub trait RuntimeFactors: Sized + 'static { } } -pub struct StateGetter { - offset: isize, - _phantom: PhantomData F>, +pub struct StateGetter { + offset: FieldOffset>, } impl StateGetter { fn new() -> Option { Some(Self { - offset: T::instance_state_offset::()?.try_into().unwrap(), - _phantom: PhantomData, + offset: T::instance_state_offset::()?, }) } @@ -43,8 +42,7 @@ impl StateGetter { &self, instance_state: &'a mut T::InstanceState, ) -> &'a mut FactorInstanceState { - let ptr = instance_state as *mut T::InstanceState; - unsafe { &mut *(ptr.offset(self.offset) as *mut FactorInstanceState) } + self.offset.apply_mut(instance_state) } } @@ -56,23 +54,21 @@ impl Clone for StateGetter { impl Copy for StateGetter {} -pub struct StateGetter2 { - offset1: isize, - offset2: isize, - _phantom: PhantomData (F1, F2)>, +pub struct StateGetter2 { + // Invariant: offsets must point at non-overlapping objects + offset1: FieldOffset>, + offset2: FieldOffset>, } impl StateGetter2 { fn new() -> Option { - // Only safe if F1 and F2 are different (and so do not alias) - if TypeId::of::() == TypeId::of::() { + let offset1 = T::instance_state_offset::()?; + let offset2 = T::instance_state_offset::()?; + // Make sure the two states don't point to the same field + if offset1.get_byte_offset() == offset2.get_byte_offset() { return None; } - Some(StateGetter2 { - offset1: T::instance_state_offset::()?.try_into().unwrap(), - offset2: T::instance_state_offset::()?.try_into().unwrap(), - _phantom: PhantomData, - }) + Some(StateGetter2 { offset1, offset2 }) } pub fn get_states<'a>( @@ -85,8 +81,8 @@ impl StateGetter2 { let ptr = instance_state as *mut T::InstanceState; unsafe { ( - &mut *(ptr.offset(self.offset1) as *mut FactorInstanceState), - &mut *(ptr.offset(self.offset2) as *mut FactorInstanceState), + &mut *(self.offset1.apply_ptr_mut(ptr) as *mut FactorInstanceState), + &mut *(self.offset2.apply_ptr_mut(ptr) as *mut FactorInstanceState), ) } } @@ -99,3 +95,7 @@ impl Clone for StateGetter2 Copy for StateGetter2 {} + +// TODO: This seems fine, but then again I don't understand why `FieldOffset`'s +// own `Sync`ness depends on `U`... +unsafe impl Sync for StateGetter2 {} diff --git a/crates/factors/tests/smoke-app/.gitignore b/crates/factors/tests/smoke-app/.gitignore new file mode 100644 index 0000000000..386474fa59 --- /dev/null +++ b/crates/factors/tests/smoke-app/.gitignore @@ -0,0 +1,2 @@ +target/ +.spin/ diff --git a/crates/factors/tests/smoke-app/Cargo.toml b/crates/factors/tests/smoke-app/Cargo.toml new file mode 100644 index 0000000000..d4fc36af1b --- /dev/null +++ b/crates/factors/tests/smoke-app/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "smoke-app" +authors = ["Lann Martin "] +description = "" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1" +spin-sdk = "3.0.1" + +[workspace] diff --git a/crates/factors/tests/smoke-app/spin.toml b/crates/factors/tests/smoke-app/spin.toml new file mode 100644 index 0000000000..a800b49fe9 --- /dev/null +++ b/crates/factors/tests/smoke-app/spin.toml @@ -0,0 +1,25 @@ +spin_manifest_version = 2 + +[application] +name = "smoke-app" +version = "0.1.0" +authors = ["Lann Martin "] +description = "" + +[variables] +host = { required = true } +other = { default = "other value" } + +[[trigger.http]] +route = "/..." +component = "smoke-app" + +[component.smoke-app] +source = "target/wasm32-wasi/release/smoke_app.wasm" +allowed_outbound_hosts = ["https://{{ host }}"] +key_value_stores = ["default"] +variables = { "other" = "<{{ other }}>"} + +[component.smoke-app.build] +command = "cargo build --target wasm32-wasi --release" +watch = ["src/**/*.rs", "Cargo.toml"] diff --git a/crates/factors/tests/smoke-app/src/lib.rs b/crates/factors/tests/smoke-app/src/lib.rs new file mode 100644 index 0000000000..214373d293 --- /dev/null +++ b/crates/factors/tests/smoke-app/src/lib.rs @@ -0,0 +1,19 @@ +use spin_sdk::http::{IntoResponse, Request, Response}; +use spin_sdk::http_component; + +/// A simple Spin HTTP component. +#[http_component] +fn handle_smoke_app(_req: Request) -> anyhow::Result { + let var_val = spin_sdk::variables::get("other")?; + let kv_val = { + let store = spin_sdk::key_value::Store::open_default()?; + store.set("k", b"v")?; + store.get("k")? + }; + let body = format!("Test response\nVariable: {var_val}\nKV: {kv_val:?}"); + Ok(Response::builder() + .status(200) + .header("content-type", "text/plain") + .body(body) + .build()) +} diff --git a/crates/factors/tests/smoke.rs b/crates/factors/tests/smoke.rs index 6dfdf48999..6a749c8061 100644 --- a/crates/factors/tests/smoke.rs +++ b/crates/factors/tests/smoke.rs @@ -1,50 +1,49 @@ +use std::path::PathBuf; + +use anyhow::bail; +use http_body_util::BodyExt; +use serde::Deserialize; use spin_app::App; +use spin_factor_key_value::{KeyValueFactor, MakeKeyValueStore}; use spin_factor_outbound_http::OutboundHttpFactor; use spin_factor_outbound_networking::OutboundNetworkingFactor; use spin_factor_variables::{StaticVariables, VariablesFactor}; use spin_factor_wasi::{preview1::WasiPreview1Factor, DummyFilesMounter, WasiFactor}; use spin_factors::{FactorRuntimeConfig, RuntimeConfigSource, RuntimeFactors}; +use spin_key_value_sqlite::{DatabaseLocation, KeyValueSqlite}; +use wasmtime_wasi_http::WasiHttpView; #[derive(RuntimeFactors)] struct Factors { wasi: WasiFactor, wasi_p1: WasiPreview1Factor, variables: VariablesFactor, - outbound_networking_factor: OutboundNetworkingFactor, - outbound_http_factor: OutboundHttpFactor, + outbound_networking: OutboundNetworkingFactor, + outbound_http: OutboundHttpFactor, + key_value: KeyValueFactor, } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn main() -> anyhow::Result<()> { let mut factors = Factors { wasi: WasiFactor::new(DummyFilesMounter), wasi_p1: WasiPreview1Factor, variables: VariablesFactor::default(), - outbound_networking_factor: OutboundNetworkingFactor, - outbound_http_factor: OutboundHttpFactor, + outbound_networking: OutboundNetworkingFactor, + outbound_http: OutboundHttpFactor, + key_value: KeyValueFactor::default(), }; + factors.variables.add_provider_type(StaticVariables)?; - let locked = serde_json::from_value(serde_json::json!({ - "spin_lock_version": 1, - "variables": { - "foo": {} - }, - "triggers": [], - "components": [{ - "id": "test", - "metadata": { - "allowed_outbound_hosts": ["http://{{ foo }}"] - }, - "source": { - "content_type": "application/wasm", - "content": {"inline": "KGNvbXBvbmVudCk="} - }, - "config": { - "test_var": "{{foo}}" - } - }] - }))?; + factors.key_value.add_store_type(TestSpinKeyValueStore)?; + + let locked = spin_loader::from_file( + "tests/smoke-app/spin.toml", + spin_loader::FilesMountStrategy::Direct, + None, + ) + .await?; let app = App::inert(locked); let engine = wasmtime::Engine::new(wasmtime::Config::new().async_support(true))?; @@ -56,25 +55,60 @@ async fn main() -> anyhow::Result<()> { .unwrap(); let configured_app = factors.configure_app(app, TestSource)?; - let data = factors.build_store_data(&configured_app, "test")?; + let data = factors.build_store_data(&configured_app, "smoke-app")?; assert_eq!( data.variables .resolver() - .resolve("test", "test_var".try_into().unwrap()) + .resolve("smoke-app", "other".try_into().unwrap()) .await .unwrap(), - "bar" + "" ); let mut store = wasmtime::Store::new(&engine, data); - let component = wasmtime::component::Component::new(&engine, b"(component)")?; - let _instance = linker.instantiate_async(&mut store, &component).await?; + let component = configured_app.app().components().next().unwrap(); + let wasm_path = component + .source() + .content + .source + .as_deref() + .unwrap() + .strip_prefix("file://") + .unwrap(); + let wasm_bytes = std::fs::read(wasm_path)?; + let component_bytes = spin_componentize::componentize_if_necessary(&wasm_bytes)?; + let component = wasmtime::component::Component::new(&engine, component_bytes)?; + let instance = linker.instantiate_async(&mut store, &component).await?; let module = wasmtime::Module::new(&engine, b"(module)")?; let _module_instance = module_linker.instantiate_async(&mut store, &module).await?; + // Invoke handler + let req = http::Request::get("/").body(Default::default()).unwrap(); + let mut wasi_http_view = + spin_factor_outbound_http::get_wasi_http_view::(store.data_mut())?; + let request = wasi_http_view.new_incoming_request(req)?; + let (response_tx, response_rx) = tokio::sync::oneshot::channel(); + let response = wasi_http_view.new_response_outparam(response_tx)?; + drop(wasi_http_view); + + let guest = wasmtime_wasi_http::proxy::Proxy::new(&mut store, &instance)?; + let call_task = tokio::spawn(async move { + guest + .wasi_http_incoming_handler() + .call_handle(&mut store, request, response) + .await + }); + let resp_task = tokio::spawn(async { + let resp = response_rx.await.unwrap().unwrap(); + let body = resp.into_body().collect().await.unwrap().to_bytes(); + eprintln!("Response: {body:?}"); + }); + let (call_res, resp_res) = tokio::join!(call_task, resp_task); + let _ = call_res?; + resp_res?; Ok(()) } @@ -94,6 +128,9 @@ impl RuntimeConfigSource for TestSource { type = "static" [variable_provider.values] foo = "bar" + + [key_value_store.default] + type = "spin" } .remove(key) else { return Ok(None); @@ -102,3 +139,32 @@ impl RuntimeConfigSource for TestSource { Ok(Some(config)) } } + +struct TestSpinKeyValueStore; + +impl MakeKeyValueStore for TestSpinKeyValueStore { + const RUNTIME_CONFIG_TYPE: &'static str = "spin"; + + type RuntimeConfig = TestSpinKeyValueRuntimeConfig; + + type StoreManager = KeyValueSqlite; + + fn make_store( + &self, + runtime_config: Self::RuntimeConfig, + ) -> anyhow::Result { + let location = match runtime_config.path { + Some(_) => { + // TODO(lann): need state_dir to derive default store path + bail!("spin key value runtime config not implemented") + } + None => DatabaseLocation::InMemory, + }; + Ok(KeyValueSqlite::new(location)) + } +} + +#[derive(Deserialize)] +struct TestSpinKeyValueRuntimeConfig { + path: Option, +} From 6668cb8b70a8fdbd65acca061d176aeeb240e234 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Thu, 13 Jun 2024 09:21:37 -0400 Subject: [PATCH 20/22] factors: Replace some transmute with Any::downcast_* Signed-off-by: Lann Martin --- crates/factors-derive/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index d64bc88b0d..39b730034c 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -53,6 +53,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { factor_types.push(&field.ty); } + let Any = quote!(::std::any::Any); let TypeId = quote!(::std::any::TypeId); let factors_crate = format_ident!("spin_factors"); let factors_path = quote!(::#factors_crate); @@ -146,11 +147,10 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { type InstanceState = #state_name; fn app_state(app_state: &Self::AppState) -> Option<&F::AppState> { - let type_id = #TypeId::of::(); #( - if type_id == #TypeId::of::<#factor_types>() { - unsafe { - return Some(::std::mem::transmute(&app_state.#factor_names)); + if let Some(state) = &app_state.#factor_names { + if let Some(state) = ::downcast_ref(state) { + return Some(state) } } )* @@ -165,7 +165,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { if type_id == #TypeId::of::<#factor_types>() { return Some( builders.#factor_names.as_mut().map(|builder| { - unsafe { ::std::mem::transmute(builder) } + ::downcast_mut(builder).unwrap() }) ); } From d281f4a5d94b7100747c27340fa3feec8750afa8 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Thu, 13 Jun 2024 17:20:20 -0400 Subject: [PATCH 21/22] factors: Tweak Factor::prepare signature Signed-off-by: Lann Martin --- crates/factor-key-value/src/lib.rs | 1 + crates/factor-outbound-http/src/lib.rs | 1 + crates/factor-outbound-networking/src/lib.rs | 1 + crates/factor-variables/src/lib.rs | 1 + crates/factor-wasi/src/lib.rs | 4 ++-- crates/factor-wasi/src/preview1.rs | 1 + crates/factors-derive/src/lib.rs | 2 +- crates/factors/src/factor.rs | 1 + crates/factors/src/prepare.rs | 8 +------- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/factor-key-value/src/lib.rs b/crates/factor-key-value/src/lib.rs index 55932e06fb..efca63a162 100644 --- a/crates/factor-key-value/src/lib.rs +++ b/crates/factor-key-value/src/lib.rs @@ -102,6 +102,7 @@ impl Factor for KeyValueFactor { } fn prepare( + &self, ctx: PrepareContext, _builders: &mut InstanceBuilders, ) -> anyhow::Result { diff --git a/crates/factor-outbound-http/src/lib.rs b/crates/factor-outbound-http/src/lib.rs index 3507b5b957..15ec5f8edf 100644 --- a/crates/factor-outbound-http/src/lib.rs +++ b/crates/factor-outbound-http/src/lib.rs @@ -36,6 +36,7 @@ impl Factor for OutboundHttpFactor { } fn prepare( + &self, _ctx: PrepareContext, builders: &mut InstanceBuilders, ) -> anyhow::Result { diff --git a/crates/factor-outbound-networking/src/lib.rs b/crates/factor-outbound-networking/src/lib.rs index b4f88c7146..66b5e3732d 100644 --- a/crates/factor-outbound-networking/src/lib.rs +++ b/crates/factor-outbound-networking/src/lib.rs @@ -49,6 +49,7 @@ impl Factor for OutboundNetworkingFactor { } fn prepare( + &self, ctx: PrepareContext, builders: &mut InstanceBuilders, ) -> anyhow::Result { diff --git a/crates/factor-variables/src/lib.rs b/crates/factor-variables/src/lib.rs index 730d1d1dfc..498e96c890 100644 --- a/crates/factor-variables/src/lib.rs +++ b/crates/factor-variables/src/lib.rs @@ -81,6 +81,7 @@ impl Factor for VariablesFactor { } fn prepare( + &self, ctx: PrepareContext, _builders: &mut InstanceBuilders, ) -> anyhow::Result { diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs index 226477bb32..2789ac2a84 100644 --- a/crates/factor-wasi/src/lib.rs +++ b/crates/factor-wasi/src/lib.rs @@ -78,6 +78,7 @@ impl Factor for WasiFactor { } fn prepare( + &self, ctx: PrepareContext, _builders: &mut InstanceBuilders, ) -> anyhow::Result { @@ -92,8 +93,7 @@ impl Factor for WasiFactor { let mount_ctx = MountFilesContext { wasi_ctx: &mut wasi_ctx, }; - ctx.factor() - .files_mounter + self.files_mounter .mount_files(ctx.app_component(), mount_ctx)?; Ok(InstanceBuilder { wasi_ctx }) diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs index 7437086683..3839e3c68c 100644 --- a/crates/factor-wasi/src/preview1.rs +++ b/crates/factor-wasi/src/preview1.rs @@ -23,6 +23,7 @@ impl Factor for WasiPreview1Factor { } fn prepare( + &self, _ctx: spin_factors::PrepareContext, _builders: &mut spin_factors::InstanceBuilders, ) -> anyhow::Result { diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index 39b730034c..d184153303 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -121,8 +121,8 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { #( builders.#factor_names = Some( #Factor::prepare::( + &self.#factor_names, #factors_path::PrepareContext::new( - &self.#factor_names, configured_app.app_state::<#factor_types>().unwrap(), &app_component, ), diff --git a/crates/factors/src/factor.rs b/crates/factors/src/factor.rs index 2f3b258387..a62d6e6695 100644 --- a/crates/factors/src/factor.rs +++ b/crates/factors/src/factor.rs @@ -38,6 +38,7 @@ pub trait Factor: Any + Sized { /// This method is given access to the app component being instantiated and /// to any other factors' instance builders that have already been prepared. fn prepare( + &self, ctx: PrepareContext, _builders: &mut InstanceBuilders, ) -> anyhow::Result; diff --git a/crates/factors/src/prepare.rs b/crates/factors/src/prepare.rs index 851cce1f5e..7e3c7f6d6e 100644 --- a/crates/factors/src/prepare.rs +++ b/crates/factors/src/prepare.rs @@ -32,25 +32,19 @@ impl FactorInstanceBuilder for T { /// already-initialized [`FactorInstanceBuilder`]s, allowing for /// inter-[`Factor`] dependencies. pub struct PrepareContext<'a, F: Factor> { - pub(crate) factor: &'a F, pub(crate) app_state: &'a F::AppState, pub(crate) app_component: &'a AppComponent<'a>, } impl<'a, F: Factor> PrepareContext<'a, F> { #[doc(hidden)] - pub fn new(factor: &'a F, app_state: &'a F::AppState, app_component: &'a AppComponent) -> Self { + pub fn new(app_state: &'a F::AppState, app_component: &'a AppComponent) -> Self { Self { - factor, app_state, app_component, } } - pub fn factor(&self) -> &F { - self.factor - } - pub fn app_state(&self) -> &F::AppState { self.app_state } From 87d8f4c41d4750390fad278990e4cf7109caa569 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Tue, 18 Jun 2024 13:48:41 -0400 Subject: [PATCH 22/22] factors: Remove module code Signed-off-by: Lann Martin --- crates/factor-outbound-http/src/lib.rs | 4 +- crates/factor-wasi/src/lib.rs | 61 ++++++++++++-------------- crates/factor-wasi/src/preview1.rs | 47 -------------------- crates/factors-derive/src/lib.rs | 6 +-- crates/factors/src/factor.rs | 46 +++---------------- crates/factors/src/lib.rs | 1 - crates/factors/tests/smoke.rs | 12 +---- 7 files changed, 41 insertions(+), 136 deletions(-) delete mode 100644 crates/factor-wasi/src/preview1.rs diff --git a/crates/factor-outbound-http/src/lib.rs b/crates/factor-outbound-http/src/lib.rs index 15ec5f8edf..36000239b9 100644 --- a/crates/factor-outbound-http/src/lib.rs +++ b/crates/factor-outbound-http/src/lib.rs @@ -22,9 +22,7 @@ impl Factor for OutboundHttpFactor { mut ctx: spin_factors::InitContext, ) -> anyhow::Result<()> { ctx.link_bindings(spin_world::v1::http::add_to_linker)?; - if let Some(linker) = ctx.linker() { - wasi::add_to_linker::(linker)?; - } + wasi::add_to_linker::(ctx.linker())?; Ok(()) } diff --git a/crates/factor-wasi/src/lib.rs b/crates/factor-wasi/src/lib.rs index 2789ac2a84..9d80131b60 100644 --- a/crates/factor-wasi/src/lib.rs +++ b/crates/factor-wasi/src/lib.rs @@ -1,5 +1,3 @@ -pub mod preview1; - use std::{future::Future, net::SocketAddr, path::Path}; use spin_factors::{ @@ -37,36 +35,35 @@ impl Factor for WasiFactor { } let get_data = ctx.get_data_fn(); let closure = type_annotate(move |data| WasiImpl(get_data(data))); - if let Some(linker) = ctx.linker() { - use wasmtime_wasi::bindings; - bindings::clocks::wall_clock::add_to_linker_get_host(linker, closure)?; - bindings::clocks::monotonic_clock::add_to_linker_get_host(linker, closure)?; - bindings::filesystem::types::add_to_linker_get_host(linker, closure)?; - bindings::filesystem::preopens::add_to_linker_get_host(linker, closure)?; - bindings::io::error::add_to_linker_get_host(linker, closure)?; - bindings::io::poll::add_to_linker_get_host(linker, closure)?; - bindings::io::streams::add_to_linker_get_host(linker, closure)?; - bindings::random::random::add_to_linker_get_host(linker, closure)?; - bindings::random::insecure::add_to_linker_get_host(linker, closure)?; - bindings::random::insecure_seed::add_to_linker_get_host(linker, closure)?; - bindings::cli::exit::add_to_linker_get_host(linker, closure)?; - bindings::cli::environment::add_to_linker_get_host(linker, closure)?; - bindings::cli::stdin::add_to_linker_get_host(linker, closure)?; - bindings::cli::stdout::add_to_linker_get_host(linker, closure)?; - bindings::cli::stderr::add_to_linker_get_host(linker, closure)?; - bindings::cli::terminal_input::add_to_linker_get_host(linker, closure)?; - bindings::cli::terminal_output::add_to_linker_get_host(linker, closure)?; - bindings::cli::terminal_stdin::add_to_linker_get_host(linker, closure)?; - bindings::cli::terminal_stdout::add_to_linker_get_host(linker, closure)?; - bindings::cli::terminal_stderr::add_to_linker_get_host(linker, closure)?; - bindings::sockets::tcp::add_to_linker_get_host(linker, closure)?; - bindings::sockets::tcp_create_socket::add_to_linker_get_host(linker, closure)?; - bindings::sockets::udp::add_to_linker_get_host(linker, closure)?; - bindings::sockets::udp_create_socket::add_to_linker_get_host(linker, closure)?; - bindings::sockets::instance_network::add_to_linker_get_host(linker, closure)?; - bindings::sockets::network::add_to_linker_get_host(linker, closure)?; - bindings::sockets::ip_name_lookup::add_to_linker_get_host(linker, closure)?; - } + let linker = ctx.linker(); + use wasmtime_wasi::bindings; + bindings::clocks::wall_clock::add_to_linker_get_host(linker, closure)?; + bindings::clocks::monotonic_clock::add_to_linker_get_host(linker, closure)?; + bindings::filesystem::types::add_to_linker_get_host(linker, closure)?; + bindings::filesystem::preopens::add_to_linker_get_host(linker, closure)?; + bindings::io::error::add_to_linker_get_host(linker, closure)?; + bindings::io::poll::add_to_linker_get_host(linker, closure)?; + bindings::io::streams::add_to_linker_get_host(linker, closure)?; + bindings::random::random::add_to_linker_get_host(linker, closure)?; + bindings::random::insecure::add_to_linker_get_host(linker, closure)?; + bindings::random::insecure_seed::add_to_linker_get_host(linker, closure)?; + bindings::cli::exit::add_to_linker_get_host(linker, closure)?; + bindings::cli::environment::add_to_linker_get_host(linker, closure)?; + bindings::cli::stdin::add_to_linker_get_host(linker, closure)?; + bindings::cli::stdout::add_to_linker_get_host(linker, closure)?; + bindings::cli::stderr::add_to_linker_get_host(linker, closure)?; + bindings::cli::terminal_input::add_to_linker_get_host(linker, closure)?; + bindings::cli::terminal_output::add_to_linker_get_host(linker, closure)?; + bindings::cli::terminal_stdin::add_to_linker_get_host(linker, closure)?; + bindings::cli::terminal_stdout::add_to_linker_get_host(linker, closure)?; + bindings::cli::terminal_stderr::add_to_linker_get_host(linker, closure)?; + bindings::sockets::tcp::add_to_linker_get_host(linker, closure)?; + bindings::sockets::tcp_create_socket::add_to_linker_get_host(linker, closure)?; + bindings::sockets::udp::add_to_linker_get_host(linker, closure)?; + bindings::sockets::udp_create_socket::add_to_linker_get_host(linker, closure)?; + bindings::sockets::instance_network::add_to_linker_get_host(linker, closure)?; + bindings::sockets::network::add_to_linker_get_host(linker, closure)?; + bindings::sockets::ip_name_lookup::add_to_linker_get_host(linker, closure)?; Ok(()) } diff --git a/crates/factor-wasi/src/preview1.rs b/crates/factor-wasi/src/preview1.rs deleted file mode 100644 index 3839e3c68c..0000000000 --- a/crates/factor-wasi/src/preview1.rs +++ /dev/null @@ -1,47 +0,0 @@ -use spin_factors::{anyhow, Factor, FactorInstanceBuilder, InitContext, RuntimeFactors}; -use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder}; - -pub struct WasiPreview1Factor; - -impl Factor for WasiPreview1Factor { - type RuntimeConfig = (); - type AppState = (); - type InstanceBuilder = InstanceBuilder; - - fn init( - &mut self, - mut ctx: InitContext, - ) -> anyhow::Result<()> { - ctx.link_module_bindings(wasmtime_wasi::preview1::add_to_linker_async) - } - - fn configure_app( - &self, - _ctx: spin_factors::ConfigureAppContext, - ) -> anyhow::Result { - Ok(()) - } - - fn prepare( - &self, - _ctx: spin_factors::PrepareContext, - _builders: &mut spin_factors::InstanceBuilders, - ) -> anyhow::Result { - Ok(InstanceBuilder { - wasi_ctx: WasiCtxBuilder::new(), - }) - } -} - -pub struct InstanceBuilder { - wasi_ctx: WasiCtxBuilder, -} - -impl FactorInstanceBuilder for InstanceBuilder { - type InstanceState = WasiP1Ctx; - - fn build(self) -> anyhow::Result { - let Self { mut wasi_ctx } = self; - Ok(wasi_ctx.build_p1()) - } -} diff --git a/crates/factors-derive/src/lib.rs b/crates/factors-derive/src/lib.rs index d184153303..0b980570f6 100644 --- a/crates/factors-derive/src/lib.rs +++ b/crates/factors-derive/src/lib.rs @@ -70,15 +70,13 @@ fn expand_factors(input: &DeriveInput) -> syn::Result { #[allow(clippy::needless_option_as_deref)] pub fn init( &mut self, - mut linker: Option<&mut #wasmtime::component::Linker<#state_name>>, - mut module_linker: Option<&mut #wasmtime::Linker<#state_name>>, + linker: &mut #wasmtime::component::Linker<#state_name>, ) -> #Result<()> { #( #Factor::init::( &mut self.#factor_names, #factors_path::InitContext::::new( - linker.as_deref_mut(), - module_linker.as_deref_mut(), + linker, |state| &mut state.#factor_names, ) )?; diff --git a/crates/factors/src/factor.rs b/crates/factors/src/factor.rs index a62d6e6695..a3d0747951 100644 --- a/crates/factors/src/factor.rs +++ b/crates/factors/src/factor.rs @@ -4,7 +4,7 @@ use anyhow::Context; use crate::{ prepare::FactorInstanceBuilder, runtime_config::RuntimeConfigTracker, App, FactorRuntimeConfig, - InstanceBuilders, Linker, ModuleLinker, PrepareContext, RuntimeConfigSource, RuntimeFactors, + InstanceBuilders, Linker, PrepareContext, RuntimeConfigSource, RuntimeFactors, }; pub trait Factor: Any + Sized { @@ -65,31 +65,18 @@ pub(crate) type GetDataFn = /// An InitContext is passed to [`Factor::init`], giving access to the global /// common [`wasmtime::component::Linker`]. pub struct InitContext<'a, T: RuntimeFactors, F: Factor> { - pub(crate) linker: Option<&'a mut Linker>, - pub(crate) module_linker: Option<&'a mut ModuleLinker>, + pub(crate) linker: &'a mut Linker, pub(crate) get_data: GetDataFn, } impl<'a, T: RuntimeFactors, F: Factor> InitContext<'a, T, F> { #[doc(hidden)] - pub fn new( - linker: Option<&'a mut Linker>, - module_linker: Option<&'a mut ModuleLinker>, - get_data: GetDataFn, - ) -> Self { - Self { - linker, - module_linker, - get_data, - } + pub fn new(linker: &'a mut Linker, get_data: GetDataFn) -> Self { + Self { linker, get_data } } - pub fn linker(&mut self) -> Option<&mut Linker> { - self.linker.as_deref_mut() - } - - pub fn module_linker(&mut self) -> Option<&mut ModuleLinker> { - self.module_linker.as_deref_mut() + pub fn linker(&mut self) -> &mut Linker { + self.linker } pub fn get_data_fn(&self) -> GetDataFn { @@ -104,26 +91,7 @@ impl<'a, T: RuntimeFactors, F: Factor> InitContext<'a, T, F> { ) -> anyhow::Result<()>, ) -> anyhow::Result<()> where { - if let Some(linker) = self.linker.as_deref_mut() { - add_to_linker(linker, self.get_data) - } else { - Ok(()) - } - } - - pub fn link_module_bindings( - &mut self, - add_to_linker: impl Fn( - &mut ModuleLinker, - fn(&mut T::InstanceState) -> &mut FactorInstanceState, - ) -> anyhow::Result<()>, - ) -> anyhow::Result<()> -where { - if let Some(linker) = self.module_linker.as_deref_mut() { - add_to_linker(linker, self.get_data) - } else { - Ok(()) - } + add_to_linker(self.linker, self.get_data) } } diff --git a/crates/factors/src/lib.rs b/crates/factors/src/lib.rs index b0203d6d55..bfdbf0f54e 100644 --- a/crates/factors/src/lib.rs +++ b/crates/factors/src/lib.rs @@ -16,7 +16,6 @@ pub use crate::{ }; pub type Linker = wasmtime::component::Linker<::InstanceState>; -pub type ModuleLinker = wasmtime::Linker<::InstanceState>; // Temporary wrappers while refactoring pub type App = spin_app::App<'static, spin_app::InertLoader>; diff --git a/crates/factors/tests/smoke.rs b/crates/factors/tests/smoke.rs index 6a749c8061..dcbc043f20 100644 --- a/crates/factors/tests/smoke.rs +++ b/crates/factors/tests/smoke.rs @@ -8,7 +8,7 @@ use spin_factor_key_value::{KeyValueFactor, MakeKeyValueStore}; use spin_factor_outbound_http::OutboundHttpFactor; use spin_factor_outbound_networking::OutboundNetworkingFactor; use spin_factor_variables::{StaticVariables, VariablesFactor}; -use spin_factor_wasi::{preview1::WasiPreview1Factor, DummyFilesMounter, WasiFactor}; +use spin_factor_wasi::{DummyFilesMounter, WasiFactor}; use spin_factors::{FactorRuntimeConfig, RuntimeConfigSource, RuntimeFactors}; use spin_key_value_sqlite::{DatabaseLocation, KeyValueSqlite}; use wasmtime_wasi_http::WasiHttpView; @@ -16,7 +16,6 @@ use wasmtime_wasi_http::WasiHttpView; #[derive(RuntimeFactors)] struct Factors { wasi: WasiFactor, - wasi_p1: WasiPreview1Factor, variables: VariablesFactor, outbound_networking: OutboundNetworkingFactor, outbound_http: OutboundHttpFactor, @@ -27,7 +26,6 @@ struct Factors { async fn main() -> anyhow::Result<()> { let mut factors = Factors { wasi: WasiFactor::new(DummyFilesMounter), - wasi_p1: WasiPreview1Factor, variables: VariablesFactor::default(), outbound_networking: OutboundNetworkingFactor, outbound_http: OutboundHttpFactor, @@ -48,11 +46,8 @@ async fn main() -> anyhow::Result<()> { let engine = wasmtime::Engine::new(wasmtime::Config::new().async_support(true))?; let mut linker = wasmtime::component::Linker::new(&engine); - let mut module_linker = wasmtime::Linker::new(&engine); - factors - .init(Some(&mut linker), Some(&mut module_linker)) - .unwrap(); + factors.init(&mut linker).unwrap(); let configured_app = factors.configure_app(app, TestSource)?; let data = factors.build_store_data(&configured_app, "smoke-app")?; @@ -82,9 +77,6 @@ async fn main() -> anyhow::Result<()> { let component = wasmtime::component::Component::new(&engine, component_bytes)?; let instance = linker.instantiate_async(&mut store, &component).await?; - let module = wasmtime::Module::new(&engine, b"(module)")?; - let _module_instance = module_linker.instantiate_async(&mut store, &module).await?; - // Invoke handler let req = http::Request::get("/").body(Default::default()).unwrap(); let mut wasi_http_view =