Skip to content

Commit 1006722

Browse files
feat: add support for running a subset of app components
Signed-off-by: Kate Goldenring <[email protected]>
1 parent 92d3461 commit 1006722

File tree

3 files changed

+109
-17
lines changed

3 files changed

+109
-17
lines changed

containerd-shim-spin/Cargo.toml

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,32 @@ Containerd shim for running Spin workloads.
1313
[dependencies]
1414
containerd-shim-wasm = "0.6.0"
1515
containerd-shim = "0.7.1"
16+
http = "1"
1617
log = "0.4"
17-
spin-app = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
18-
spin-core = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
19-
spin-componentize = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
18+
spin-app = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
19+
spin-core = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
20+
spin-componentize = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
2021
# Enable loading components precompiled by the shim
21-
spin-trigger = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c", features = [
22+
spin-trigger = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c", features = [
2223
"unsafe-aot-compilation",
2324
] }
24-
spin-trigger-http = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
25-
spin-trigger-redis = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
25+
spin-trigger-http = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
26+
spin-trigger-redis = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
2627
trigger-mqtt = { git = "https://github.com/spinkube/spin-trigger-mqtt", rev = "4c76e52dc5d3e7961c7b4d4933543c1adb2778cf" }
2728
trigger-sqs = { git = "https://github.com/fermyon/spin-trigger-sqs", rev = "71877907ebd822bb1aacf7a20065733b7cd188dc" }
2829
trigger-command = { git = "https://github.com/fermyon/spin-trigger-command", rev = "db55291552233e04189275a2dd82c07e5fa4fdf2" }
29-
spin-manifest = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
30-
spin-loader = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
31-
spin-oci = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
32-
spin-common = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
33-
spin-expressions = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
34-
spin-factors-executor = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
35-
spin-telemetry = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
36-
spin-runtime-factors = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
37-
spin-factors = { git = "https://github.com/fermyon/spin", rev = "485b04090644ecfda4d0034891a5feca9a90332c" }
30+
spin-manifest = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
31+
spin-loader = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
32+
spin-oci = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
33+
spin-common = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
34+
spin-expressions = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
35+
spin-factors-executor = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
36+
spin-telemetry = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
37+
spin-runtime-factors = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
38+
spin-factors = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
39+
spin-factor-outbound-networking = { git = "https://github.com/fermyon/spin", rev = "788dec12bec206b40ef6226a90e508691cf9312c" }
3840
wasmtime = "22.0"
39-
tokio = { version = "1.38", features = ["rt"] }
41+
tokio = { version = "1", features = ["rt"] }
4042
openssl = { version = "*", features = ["vendored"] }
4143
serde = "1.0"
4244
serde_json = "1.0"

containerd-shim-spin/src/constants.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ pub(crate) const SPIN_MANIFEST_FILE_PATH: &str = "/spin.toml";
1818
pub(crate) const SPIN_APPLICATION_VARIABLE_PREFIX: &str = "SPIN_VARIABLE";
1919
/// Working directory for Spin applications
2020
pub(crate) const SPIN_TRIGGER_WORKING_DIR: &str = "/";
21+
/// Defines the subset of application components that should be executable by the shim
22+
/// If empty or DNE, all components will be supported
23+
pub(crate) const SPIN_COMPONENTS_TO_RETAIN_ENV: &str = "SPIN_COMPONENTS_TO_RETAIN";

containerd-shim-spin/src/engine.rs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::{
44
hash::{Hash, Hasher},
55
};
66

7-
use anyhow::{Context, Result};
7+
use anyhow::{bail, Context, Result};
88
use containerd_shim_wasm::{
99
container::{Engine, RuntimeContext, Stdio},
1010
sandbox::WasmLayer,
@@ -13,6 +13,7 @@ use containerd_shim_wasm::{
1313
use futures::future;
1414
use log::info;
1515
use spin_app::locked::LockedApp;
16+
use spin_factor_outbound_networking::{allowed_outbound_hosts, parse_service_chaining_target};
1617
use spin_trigger::cli::NoCliArgs;
1718
use spin_trigger_http::HttpTrigger;
1819
use spin_trigger_redis::RedisTrigger;
@@ -136,6 +137,12 @@ impl SpinEngine {
136137
async fn wasm_exec_async(&self, ctx: &impl RuntimeContext) -> Result<()> {
137138
let cache = initialize_cache().await?;
138139
let app_source = Source::from_ctx(ctx, &cache).await?;
140+
let components_to_execute = env::var(constants::SPIN_COMPONENTS_TO_RETAIN_ENV)
141+
.ok()
142+
.map(|s| s.split(',').map(|s| s.to_string()).collect::<Vec<String>>());
143+
if components_to_execute.is_some() {
144+
log::info!("Components to execute: {:?}", components_to_execute);
145+
}
139146
let locked_app = app_source.to_locked_app(&cache).await?;
140147
configure_application_variables_from_environment_variables(&locked_app)?;
141148
let trigger_cmds = get_supported_triggers(&locked_app)
@@ -218,6 +225,86 @@ impl SpinEngine {
218225
}
219226
}
220227

228+
/// Scrubs the locked app to only contain the given list of components
229+
/// Introspects the LockedApp to find and selectively retain the triggers that correspond to those components
230+
fn retain_components(locked_app: &mut LockedApp, retained_components: &[String]) -> Result<()> {
231+
// Create a temporary app to access parsed component and trigger information
232+
let tmp_app = spin_app::App::new("tmp", locked_app.clone());
233+
validate_retained_components_exist(&tmp_app, retained_components)?;
234+
validate_retained_components_service_chaining(&tmp_app, retained_components)?;
235+
let (component_ids, trigger_ids): (HashSet<String>, HashSet<String>) = tmp_app
236+
.triggers()
237+
.filter_map(|t| match t.component() {
238+
Ok(comp) if retained_components.contains(&comp.id().to_string()) => {
239+
Some((comp.id().to_owned(), t.id().to_owned()))
240+
}
241+
_ => None,
242+
})
243+
.collect();
244+
locked_app
245+
.components
246+
.retain(|c| component_ids.contains(&c.id));
247+
locked_app.triggers.retain(|t| trigger_ids.contains(&t.id));
248+
Ok(())
249+
}
250+
251+
/// Validates that all service chaining of an app will be satisfied by the
252+
/// retained components.
253+
///
254+
/// This does a best effort look up of components that are
255+
/// allowed to be accessed through service chaining and will error early if a
256+
/// component is configured to to chain to another component that is not
257+
/// retained. All wildcard service chaining is disallowed and all templated URLs
258+
/// are ignored.
259+
fn validate_retained_components_service_chaining(
260+
app: &spin_app::App,
261+
retained_components: &[String],
262+
) -> Result<()> {
263+
app
264+
.triggers().try_for_each(|t| {
265+
let Ok(component) = t.component() else { return Ok(()) };
266+
if retained_components.contains(&component.id().to_string()) {
267+
let allowed_hosts = allowed_outbound_hosts(&component).context("failed to get allowed hosts")?;
268+
for host in allowed_hosts {
269+
// Templated URLs are not yet resolved at this point, so ignore unresolvable URIs
270+
if let Ok(uri) = host.parse::<http::Uri>() {
271+
if let Some(chaining_target) = parse_service_chaining_target(&uri) {
272+
if !retained_components.contains(&chaining_target) {
273+
if chaining_target == "*" {
274+
bail!("Component selected with '--component {}' cannot use wildcard service chaining: allowed_outbound_hosts = [\"http://*.spin.internal\"]", component.id());
275+
}
276+
bail!(
277+
"Component selected with '--component {}' cannot use service chaining to unselected component: allowed_outbound_hosts = [\"http://{}.spin.internal\"]",
278+
component.id(), chaining_target
279+
);
280+
}
281+
}
282+
}
283+
}
284+
}
285+
anyhow::Ok(())
286+
})?;
287+
288+
Ok(())
289+
}
290+
291+
/// Validates that all components specified to be retained actually exist in the app
292+
fn validate_retained_components_exist(
293+
app: &spin_app::App,
294+
retained_components: &[String],
295+
) -> Result<()> {
296+
let app_components = app
297+
.components()
298+
.map(|c| c.id().to_string())
299+
.collect::<HashSet<_>>();
300+
for c in retained_components {
301+
if !app_components.contains(c) {
302+
bail!("Specified component \"{c}\" not found in application");
303+
}
304+
}
305+
Ok(())
306+
}
307+
221308
#[cfg(test)]
222309
mod tests {
223310
use oci_spec::image::MediaType;

0 commit comments

Comments
 (0)