title = "SIP 000 - `Component Dependencies`" template = "main" date = "2024-05-20" --- Summary: TODO Owner(s): brian.hardock@fermyon.com Created: May 20, 2024 ## Background This proposal outlines a design for how a Spin developer can use WebAssembly components to satisfy dependencies within their Spin applications from a Spin component. The goal of this SIP is to enable the 80% use-case for composition while outlining a forward compatible path for more sophisticated composition scenarios that we can unlock as broader ecosystem tooling & WASI features become available. ### What are dependencies? Functionally dependencies are component imports. At the manifest level, dependencies describe which imports of a component to satisfy and how. ### Why not use `wac`? Spin as developer tool for building and running WebAssembly components currently has no support for allowing developers to specify how a dependency should be satisfied whether be it an import part of the target world or a custom developer-defined import that spin is unaware of. Today, developers can use ecosystem tooling like [wac](https://github.com/bytecodealliance/wac) to pre-compose, however that requires extra tooling and steps to be added as part of a developer's workflow outside of the `spin-{build, up}` workflow. For advanced composition, it is still recommended to use wac. ## Proposal ### Using dependencies from disk (local) ```toml [component."infra-dashboard".dependencies] # Components can satisfy dependencies by import name (e.g. "aws:client/s3") # using a component from disk. Implicitly it is assumed that the # `my_aws_client.wasm` component exports an instance of the # `aws:client/s3`interface. TODO: point resolution doc in wac "aws:client/s3" = { path = "my_aws_client.wasm" } # Optionally, explicitly specify the export name to use to satisfy the `aws:client/s3` interface. "aws:client/s3" = { path = "my_aws_client.wasm", export = "my-s3-client" } # Without an interface name, emulate ["wac plug"](TODO -- link) functionality. # Attempts to resolve every import of the "aws:client" package. "aws:client" = { path = "my_aws_client.wasm" } ``` > NOTE: using `aws:client` and `aws:client/s3` will be forbidden to prevent resolution ambiguities. ### Using depdendencies from a registry (remote) ```toml [component."infra-dashboard".dependencies] # Equivalent to { version = "1.0.0" , package = "aws:client"} "aws:client" = "1.0.0" # Use the `aws:client` component package to satisfy any number of "wasi:blobstore" imports ... "wasi:blobstore" = { registry = "my-registry.io", version = "0.1.0", package = "aws:client" } ``` ### Manifest Schema This following is a rough sketch of the `rust` manifest datastructures to be added to support the previous example snippets. ```rust /// The format for each dependency name is required to be a kebab-cased name (e.g. foo-bar), interface id (e.g. foo:bar/baz), or package name (e.g. foo:bar) as described by the [Component Model Explainer](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#import-and-export-definitions).) enum DepedencyName { Interfaces { package: PackageName, // Only one specific interface name: Option<String>, } /// A kebab-name "foo-bar" Plain { name: String } } struct PackageName { namespace: String, name: String, version: Option<semver::Version>, } struct Component { // ... existing component fields // This is introduced by this proposal dependencies: HashMap<DepdendencyName, Dependency>, } /// Represents a dependency enum Dependency { /// `... = ">= 0.1.0"` Version(semver::VersionReq), /// `... = { version = "0.1.0", registry = "registry.io", ...}` Package { version: semver::VersionReq, registry: Option<String>, package: Option<String>, export: Option<String> }, /// `... = { path = "path/to/component.wasm", export = "my-export" }` Local { path: PathBuf, export: Option<String>, }, // FUTURE! This is how components can attach "capabilities" to their // dependencies by enveloping a dependency with a component manifest. // Component { // component: ComponentIdOrInline, // } } ``` ## Open Questions: 1. Supporting direct plug via `*` dependencies? E.g.: ```toml [component."infra-dashboard".dependencies] "*" = { path = "path/to/my_depedency.wasm" } ``` 2. How to configure capability inheritance, e.g. component, dependency, or application level flag? E.g.: ```toml [component."infra-dashboard"] # ... inherit_capabilities = false # opt-out but validation error until we can enforce isolation inherit_capabilities = true # opt-in all inherit_capabilities = { http = true } # granular opt-in using wasi-virt (if it can)? [component."infra-dashboard".dependencies] # ... ``` ## Future Work / Discussion / Other things to mention - Capability Inheritance? - Why punting on isolation via virtualization for now and what is the path forward to that future given the proposed schema?