Skip to content

Commit f610ec6

Browse files
committed
VariablesFactor wip
Signed-off-by: Lann Martin <[email protected]>
1 parent 755ff5c commit f610ec6

File tree

13 files changed

+361
-155
lines changed

13 files changed

+361
-155
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/factor-outbound-networking/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ 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
1315
spin-outbound-networking = { path = "../outbound-networking" }
16+
tracing = { workspace = true }
1417

1518
[lints]
1619
workspace = true
Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
use std::{collections::HashMap, sync::Arc};
22

33
use anyhow::Context;
4+
use futures_util::{
5+
future::{BoxFuture, Shared},
6+
FutureExt,
7+
};
8+
use spin_factor_variables::VariablesFactor;
49
use spin_factor_wasi::WasiFactor;
5-
use spin_factors::{Factor, FactorInstancePreparer, Result, SpinFactors};
6-
use spin_outbound_networking::{AllowedHostsConfig, HostConfig, PortConfig, ALLOWED_HOSTS_KEY};
10+
use spin_factors::{
11+
Factor, FactorInstancePreparer, InstancePreparers, PrepareContext, Result, SpinFactors,
12+
};
13+
use spin_outbound_networking::{AllowedHostsConfig, ALLOWED_HOSTS_KEY};
714

815
pub struct OutboundNetworkingFactor;
916

@@ -17,70 +24,93 @@ impl Factor for OutboundNetworkingFactor {
1724
app: &spin_factors::App,
1825
_ctx: spin_factors::ConfigureAppContext<Factors>,
1926
) -> 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)
27+
// Extract allowed_outbound_hosts for all components
28+
let component_allowed_hosts = app
29+
.components()
30+
.map(|component| {
31+
Ok((
32+
component.id().to_string(),
33+
component
34+
.get_metadata(ALLOWED_HOSTS_KEY)?
35+
.unwrap_or_default()
36+
.into_boxed_slice()
37+
.into(),
38+
))
39+
})
40+
.collect::<Result<_>>()?;
41+
Ok(AppConfig {
42+
component_allowed_hosts,
43+
})
3144
}
3245
}
3346

3447
#[derive(Default)]
3548
pub struct AppConfig {
36-
component_allowed_hosts: HashMap<String, Arc<AllowedHostsConfig>>,
49+
component_allowed_hosts: HashMap<String, Arc<[String]>>,
3750
}
3851

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

43-
impl InstancePreparer {
44-
pub fn allowed_hosts(&self) -> &Arc<AllowedHostsConfig> {
45-
&self.allowed_hosts
46-
}
54+
pub struct InstancePreparer {
55+
allowed_hosts_future: AllowedHostsFuture,
4756
}
4857

4958
impl FactorInstancePreparer<OutboundNetworkingFactor> for InstancePreparer {
5059
fn new<Factors: SpinFactors>(
51-
_factor: &OutboundNetworkingFactor,
52-
app_component: &spin_factors::AppComponent,
53-
mut ctx: spin_factors::PrepareContext<Factors>,
60+
ctx: PrepareContext<OutboundNetworkingFactor>,
61+
mut preparers: InstancePreparers<Factors>,
5462
) -> Result<Self> {
55-
let allowed_hosts = ctx
56-
.app_config::<OutboundNetworkingFactor>()?
63+
let hosts = ctx
64+
.app_config()
5765
.component_allowed_hosts
58-
.get(app_component.id())
59-
.context("missing component")?
60-
.clone();
66+
.get(ctx.app_component().id())
67+
.cloned()
68+
.context("missing component allowed hosts")?;
69+
let resolver = preparers.get_mut::<VariablesFactor>()?.resolver().clone();
70+
let allowed_hosts_future = async move {
71+
let prepared = resolver.prepare().await?;
72+
AllowedHostsConfig::parse(&hosts, &prepared)
73+
}
74+
.map(Arc::new)
75+
.boxed()
76+
.shared();
77+
// let prepared_resolver = resolver.prepare().await?;
78+
// let allowed_hosts = AllowedHostsConfig::parse(
79+
// .context("missing component allowed hosts")?,
80+
// &prepared_resolver,
81+
// )?;
6182

6283
// Update Wasi socket allowed ports
63-
let wasi_preparer = ctx.instance_preparer_mut::<WasiFactor>()?;
64-
match &*allowed_hosts {
65-
AllowedHostsConfig::All => wasi_preparer.inherit_network(),
66-
AllowedHostsConfig::SpecificHosts(configs) => {
67-
for config in configs {
68-
if config.scheme().allows_any() {
69-
match (config.host(), config.port()) {
70-
(HostConfig::Cidr(ip_net), PortConfig::Any) => {
71-
wasi_preparer.socket_allow_ports(*ip_net, 0, None)
72-
}
73-
_ => todo!(), // TODO: complete and validate against existing Network TriggerHooks
74-
}
84+
let wasi_preparer = preparers.get_mut::<WasiFactor>()?;
85+
let hosts_future = allowed_hosts_future.clone();
86+
wasi_preparer.outbound_socket_addr_check(move |addr| {
87+
let hosts_future = hosts_future.clone();
88+
async move {
89+
match &*hosts_future.await {
90+
Ok(allowed_hosts) => {
91+
// TODO: verify this actually works...
92+
spin_outbound_networking::check_url(&addr.to_string(), "*", allowed_hosts)
93+
}
94+
Err(err) => {
95+
// TODO: should this trap (somehow)?
96+
tracing::error!(%err, "allowed_outbound_hosts variable resolution failed");
97+
false
7598
}
7699
}
77100
}
78-
}
79-
80-
Ok(Self { allowed_hosts })
101+
});
102+
Ok(Self {
103+
allowed_hosts_future,
104+
})
81105
}
82106

83107
fn prepare(self) -> Result<<OutboundNetworkingFactor as Factor>::InstanceState> {
84108
Ok(())
85109
}
86110
}
111+
112+
impl InstancePreparer {
113+
pub async fn resolve_allowed_hosts(&self) -> Arc<anyhow::Result<AllowedHostsConfig>> {
114+
self.allowed_hosts_future.clone().await
115+
}
116+
}

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

0 commit comments

Comments
 (0)