Skip to content

Commit b84f77f

Browse files
committed
VariablesFactor wip
Signed-off-by: Lann Martin <lann.martin@fermyon.com>
1 parent 3268e6c commit b84f77f

File tree

9 files changed

+204
-31
lines changed

9 files changed

+204
-31
lines changed

crates/factor-outbound-networking/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ edition = { workspace = true }
66

77
[dependencies]
88
anyhow = "1"
9+
futures-util = "0.3"
910
ipnet = "2.9.0"
11+
spin-factor-variables = { path = "../factor-variables" }
1012
spin-factor-wasi = { path = "../factor-wasi" }
1113
spin-factors = { path = "../factors" }
1214
# TODO: merge with this crate
Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::{collections::HashMap, sync::Arc};
22

33
use anyhow::Context;
4+
use futures_util::{future::{BoxFuture, Shared}, FutureExt};
5+
use spin_factor_variables::VariablesFactor;
46
use spin_factor_wasi::WasiFactor;
57
use spin_factors::{Factor, FactorInstancePreparer, Result, SpinFactors};
68
use spin_outbound_networking::{AllowedHostsConfig, HostConfig, PortConfig, ALLOWED_HOSTS_KEY};
@@ -17,33 +19,34 @@ impl Factor for OutboundNetworkingFactor {
1719
app: &spin_factors::App,
1820
_ctx: spin_factors::ConfigureAppContext<Factors>,
1921
) -> Result<Self::AppConfig> {
20-
let mut cfg = AppConfig::default();
21-
// TODO: resolve resolver resolution
22-
let resolver = Default::default();
23-
for component in app.components() {
24-
if let Some(hosts) = component.get_metadata(ALLOWED_HOSTS_KEY)? {
25-
let allowed_hosts = AllowedHostsConfig::parse(&hosts, &resolver)?;
26-
cfg.component_allowed_hosts
27-
.insert(component.id().to_string(), Arc::new(allowed_hosts));
28-
}
29-
}
30-
Ok(cfg)
22+
// Extract allowed_outbound_hosts for all components
23+
let component_allowed_hosts = app
24+
.components()
25+
.map(|component| {
26+
Ok((
27+
component.id().to_string(),
28+
component
29+
.get_metadata(ALLOWED_HOSTS_KEY)?
30+
.unwrap_or_default(),
31+
))
32+
})
33+
.collect::<Result<_>>()?;
34+
35+
Ok(AppConfig {
36+
component_allowed_hosts,
37+
})
3138
}
3239
}
3340

3441
#[derive(Default)]
3542
pub struct AppConfig {
36-
component_allowed_hosts: HashMap<String, Arc<AllowedHostsConfig>>,
43+
component_allowed_hosts: HashMap<String, Vec<String>>,
3744
}
3845

39-
pub struct InstancePreparer {
40-
allowed_hosts: Arc<AllowedHostsConfig>,
41-
}
46+
type AllowedHostsFuture = Shared<BoxFuture<'static, Arc<anyhow::Result<AllowedHostsConfig>>>>;
4247

43-
impl InstancePreparer {
44-
pub fn allowed_hosts(&self) -> &Arc<AllowedHostsConfig> {
45-
&self.allowed_hosts
46-
}
48+
pub struct InstancePreparer {
49+
allowed_hosts_future: AllowedHostsFuture,
4750
}
4851

4952
impl FactorInstancePreparer<OutboundNetworkingFactor> for InstancePreparer {
@@ -52,16 +55,26 @@ impl FactorInstancePreparer<OutboundNetworkingFactor> for InstancePreparer {
5255
app_component: &spin_factors::AppComponent,
5356
mut ctx: spin_factors::PrepareContext<Factors>,
5457
) -> Result<Self> {
55-
let allowed_hosts = ctx
56-
.app_config::<OutboundNetworkingFactor>()?
57-
.component_allowed_hosts
58-
.get(app_component.id())
59-
.context("missing component")?
60-
.clone();
58+
let hosts =
59+
ctx.app_config::<OutboundNetworkingFactor>()
60+
.unwrap()
61+
.component_allowed_hosts
62+
.get(app_component.id())
63+
.context("missing component allowed hosts")?;
64+
let resolver = ctx.instance_preparer_mut::<VariablesFactor>()?.resolver().clone();
65+
let allowed_hosts_future = async move {
66+
let prepared = resolver.prepare().await?;
67+
Ok(AllowedHostsConfig::parse(&hosts, &prepared))
68+
}.boxed().shared();
69+
let prepared_resolver = resolver.prepare().await?;
70+
let allowed_hosts = AllowedHostsConfig::parse(
71+
.context("missing component allowed hosts")?,
72+
&prepared_resolver,
73+
)?;
6174

6275
// Update Wasi socket allowed ports
6376
let wasi_preparer = ctx.instance_preparer_mut::<WasiFactor>()?;
64-
match &*allowed_hosts {
77+
match &allowed_hosts {
6578
AllowedHostsConfig::All => wasi_preparer.inherit_network(),
6679
AllowedHostsConfig::SpecificHosts(configs) => {
6780
for config in configs {
@@ -77,10 +90,18 @@ impl FactorInstancePreparer<OutboundNetworkingFactor> for InstancePreparer {
7790
}
7891
}
7992

80-
Ok(Self { allowed_hosts })
93+
Ok(Self {
94+
allowed_hosts: Arc::new(allowed_hosts),
95+
})
8196
}
8297

8398
fn prepare(self) -> Result<<OutboundNetworkingFactor as Factor>::InstanceState> {
8499
Ok(())
85100
}
86101
}
102+
103+
impl InstancePreparer {
104+
pub async fn resolve_allowed_hosts(&self) -> Arc<anyhow::Result<AllowedHostsConfig>> {
105+
self.allowed_hosts_future.clone().await
106+
}
107+
}

crates/factor-variables/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "spin-factor-variables"
3+
version = { workspace = true }
4+
authors = { workspace = true }
5+
edition = { workspace = true }
6+
7+
[dependencies]
8+
anyhow = "1"
9+
spin-expressions = { path = "../expressions" }
10+
spin-factors = { path = "../factors" }
11+
spin-world = { path = "../world" }
12+
13+
[lints]
14+
workspace = true

crates/factor-variables/src/lib.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use std::sync::Arc;
2+
3+
use spin_expressions::ProviderResolver;
4+
use spin_factors::{Factor, FactorInstancePreparer, Result, SpinFactors};
5+
use spin_world::{async_trait, v1::config as v1_config, v2::variables};
6+
7+
pub struct VariablesFactor;
8+
9+
impl Factor for VariablesFactor {
10+
type AppConfig = AppConfig;
11+
type InstancePreparer = InstancePreparer;
12+
type InstanceState = InstanceState;
13+
14+
fn init<Factors: SpinFactors>(
15+
&mut self,
16+
mut ctx: spin_factors::InitContext<Factors, Self>,
17+
) -> Result<()> {
18+
ctx.link_bindings(v1_config::add_to_linker)?;
19+
ctx.link_bindings(variables::add_to_linker)?;
20+
Ok(())
21+
}
22+
23+
fn configure_app<Factors: SpinFactors>(
24+
&self,
25+
app: &spin_factors::App,
26+
_ctx: spin_factors::ConfigureAppContext<Factors>,
27+
) -> Result<Self::AppConfig> {
28+
let mut resolver =
29+
ProviderResolver::new(app.variables().map(|(key, val)| (key.clone(), val.clone())))?;
30+
for component in app.components() {
31+
resolver.add_component_variables(
32+
component.id(),
33+
component.config().map(|(k, v)| (k.into(), v.into())),
34+
)?;
35+
}
36+
// TODO: add providers from runtime config
37+
Ok(AppConfig {
38+
resolver: Arc::new(resolver),
39+
})
40+
}
41+
}
42+
43+
#[derive(Default)]
44+
pub struct AppConfig {
45+
resolver: Arc<ProviderResolver>,
46+
}
47+
48+
pub struct InstancePreparer {
49+
state: InstanceState,
50+
}
51+
52+
impl InstancePreparer {
53+
pub fn resolver(&self) -> &Arc<ProviderResolver> {
54+
&self.state.resolver
55+
}
56+
}
57+
58+
impl FactorInstancePreparer<VariablesFactor> for InstancePreparer {
59+
fn new<Factors: SpinFactors>(
60+
_factor: &VariablesFactor,
61+
app_component: &spin_factors::AppComponent,
62+
ctx: spin_factors::PrepareContext<Factors>,
63+
) -> Result<Self> {
64+
let component_id = app_component.id().to_string();
65+
let resolver = ctx
66+
.app_config::<VariablesFactor>()
67+
.unwrap()
68+
.resolver
69+
.clone();
70+
Ok(Self {
71+
state: InstanceState {
72+
component_id,
73+
resolver,
74+
},
75+
})
76+
}
77+
78+
fn prepare(self) -> Result<<VariablesFactor as Factor>::InstanceState> {
79+
Ok(self.state)
80+
}
81+
}
82+
83+
pub struct InstanceState {
84+
component_id: String,
85+
resolver: Arc<ProviderResolver>,
86+
}
87+
88+
#[async_trait]
89+
impl variables::Host for InstanceState {
90+
async fn get(&mut self, key: String) -> Result<String, variables::Error> {
91+
let key = spin_expressions::Key::new(&key).map_err(expressions_to_variables_err)?;
92+
self.resolver
93+
.resolve(&self.component_id, key)
94+
.await
95+
.map_err(expressions_to_variables_err)
96+
}
97+
98+
fn convert_error(&mut self, error: variables::Error) -> Result<variables::Error> {
99+
Ok(error)
100+
}
101+
}
102+
103+
#[async_trait]
104+
impl v1_config::Host for InstanceState {
105+
async fn get_config(&mut self, key: String) -> Result<String, v1_config::Error> {
106+
<Self as variables::Host>::get(self, key)
107+
.await
108+
.map_err(|err| match err {
109+
variables::Error::InvalidName(msg) => v1_config::Error::InvalidKey(msg),
110+
variables::Error::Undefined(msg) => v1_config::Error::Provider(msg),
111+
other => v1_config::Error::Other(format!("{other}")),
112+
})
113+
}
114+
115+
fn convert_error(&mut self, err: v1_config::Error) -> anyhow::Result<v1_config::Error> {
116+
Ok(err)
117+
}
118+
}
119+
120+
fn expressions_to_variables_err(err: spin_expressions::Error) -> variables::Error {
121+
use spin_expressions::Error;
122+
match err {
123+
Error::InvalidName(msg) => variables::Error::InvalidName(msg),
124+
Error::Undefined(msg) => variables::Error::Undefined(msg),
125+
Error::Provider(err) => variables::Error::Provider(err.to_string()),
126+
other => variables::Error::Other(format!("{other}")),
127+
}
128+
}

crates/factors/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ wasmtime = { workspace = true }
1414
serde_json = "1.0"
1515
spin-factors-derive = { path = "../factors-derive", features = ["expander"] }
1616
spin-factor-outbound-networking = { path = "../factor-outbound-networking" }
17+
spin-factor-variables = { path = "../factor-variables" }
1718
spin-factor-wasi = { path = "../factor-wasi" }
1819

1920
[lints]

crates/factors/src/lib.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,9 @@ pub trait Factor: Any + Sized {
3737
}
3838

3939
/// Performs factor-specific validation and configuration for the given
40-
/// [`App`] and [`RuntimeConfig`]. A runtime may - but is not required to -
41-
/// reuse the returned config across multiple instances. Note that this may
42-
/// be called without any call to `init` in cases where only validation is
43-
/// needed.
40+
/// [`App`]. A runtime may - but is not required to - reuse the returned
41+
/// config across multiple instances. Note that this may be called without
42+
/// any call to `init` in cases where only validation is needed.
4443
fn configure_app<Factors: SpinFactors>(
4544
&self,
4645
app: &App,
@@ -150,6 +149,8 @@ pub trait FactorInstancePreparer<T: Factor>: Sized {
150149
/// to any already-initialized [`FactorInstancePreparer`]s, allowing for
151150
/// inter-[`Factor`] dependencies.
152151
pub struct PrepareContext<'a, Factors: SpinFactors> {
152+
// TODO(lann): mixing & and &mut like this seems like a recipe for pain
153+
// later; maybe use context only for & and separate params for &mut?
153154
configured_app: &'a ConfiguredApp<Factors>,
154155
instance_preparers: &'a mut Factors::InstancePreparers,
155156
}

crates/factors/tests/smoke.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
use spin_app::App;
22
use spin_factor_outbound_networking::OutboundNetworkingFactor;
3+
use spin_factor_variables::VariablesFactor;
34
use spin_factor_wasi::{preview1::WasiPreview1Factor, DummyFilesMounter, WasiFactor};
45
use spin_factors::SpinFactors;
56

67
#[derive(SpinFactors)]
78
struct Factors {
89
wasi: WasiFactor,
910
wasip1: WasiPreview1Factor,
11+
variables: VariablesFactor,
1012
outbound_networking_factor: OutboundNetworkingFactor,
1113
}
1214

1315
fn main() -> anyhow::Result<()> {
1416
let mut factors = Factors {
1517
wasi: WasiFactor::new(DummyFilesMounter),
1618
wasip1: WasiPreview1Factor,
19+
variables: VariablesFactor,
1720
outbound_networking_factor: OutboundNetworkingFactor,
1821
// outbound_http_factor: OutboundHttpFactor,
1922
};

crates/world/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ authors = { workspace = true }
55
edition = { workspace = true }
66

77
[dependencies]
8+
async-trait = "0.1"
89
wasmtime = { workspace = true }

crates/world/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#![allow(missing_docs)]
22

3+
pub use async_trait::async_trait;
4+
35
wasmtime::component::bindgen!({
46
inline: r#"
57
package fermyon:runtime;

0 commit comments

Comments
 (0)