P3 host plugin loader#5348
Open
ricochet wants to merge 3 commits into
Open
Conversation
ricochet
force-pushed
the
p3-host-plugin-loader
branch
2 times, most recently
from
July 16, 2026 14:03
61de1c8 to
698d2ed
Compare
ricochet
force-pushed
the
p3-host-plugin-loader
branch
4 times, most recently
from
July 21, 2026 20:32
92026d5 to
f38ae8d
Compare
ricochet
commented
Jul 21, 2026
| {{- /* Host component plugins: one --host-plugin flag per entry, each | ||
| rendered into the CLI's `key=value` grammar. */}} | ||
| {{- range .hostPlugins }} | ||
| - "--host-plugin=id={{ .id }}{{ if .image }},image={{ .image }}{{ if .pullPolicy }},pull={{ .pullPolicy }}{{ end }}{{ if .digest }},digest={{ .digest }}{{ end }}{{ else if .file }},file={{ .file }}{{ end }}{{ if .maxRestarts }},max-restarts={{ .maxRestarts }}{{ end }}" |
Contributor
Author
There was a problem hiding this comment.
FYI I opted to have this be plain image refs and not Artifacts.
Host is a status-only CRD (heartbeat-derived, OSArch, Version, LastSeen…), and there's no HostGroup CRD. Today these are just helm-templated into the host Deployment.
So if we want this in the future we'd essentially create a HostGroup CRD (or a HostSpec) whose hostPlugins[] carry artifactFrom references instead of inline images. Then the host reconciler:
- watches HostGroup + the referenced plugin Artifacts
- for each plugin, waits for its Artifact to reach Published
- renders the resolved Status.ArtifactURL into the host Deployment's --host-plugin args
- owns the host Deployment, so there's a single owner and re-rendering on a new artifact rolls the pod cleanly.
AKA this is something we would add on top if we wanted it.
ricochet
force-pushed
the
p3-host-plugin-loader
branch
from
July 22, 2026 00:35
f38ae8d to
c80a3ef
Compare
vados-cosmonic
previously approved these changes
Jul 22, 2026
vados-cosmonic
left a comment
Contributor
There was a problem hiding this comment.
LGTM 🚀
Nothing but nits!
ricochet
force-pushed
the
p3-host-plugin-loader
branch
from
July 22, 2026 20:46
c80a3ef to
8202688
Compare
Wire the existing `ComponentHostPlugin` adapter to `wash host`, `wash dev`, and the runtime-operator chart so host component plugins — WebAssembly components that provide a host capability — can be loaded declaratively. - wash-runtime: `ComponentPluginSpec`/`PluginSource` (always compiled) plus a feature-gated `load_component_plugin` that fetches the wasm (OCI pull or local file), verifies an optional digest pin, and builds the plugin. - wash host: `--host-plugin`/`WASH_HOST_PLUGINS` (repeatable, `key=value` grammar). Gated wiring that fails clearly on a build without the `host-component-plugins` feature rather than silently dropping the request. Private-registry plugin pulls authenticate with `--host-plugin-registry-user`/ `--host-plugin-registry-password` (env `WASH_HOST_PLUGIN_REGISTRY_USER`/ `WASH_HOST_PLUGIN_REGISTRY_PASSWORD`, required together, `hide_env_values` + sourced from the environment so the secret stays out of the `--host-plugin` arg and the pod spec), falling back to the ambient docker credential helper and then anonymous access. Workload components keep their own per-workload image pull secret. - wash dev: `dev.host_plugins` config entries (file or image source). - chart: per-host-group `hostPlugins` values rendered into `--host-plugin` args. Loading is host-operator controlled only; nothing in a workload request can register a host-global capability provider. The loader stays gated on `host-component-plugins` (all-features builds only) — a release host handed a plugin it cannot build fails to start. The gated wash unit tests (`cargo test -p wash --lib --features host-component-plugins`) run in CI alongside the wash-runtime feature tests. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
The gateway's workloadHostname registered a Host-header route only for workloads declaring the p2 `wasi:http/incoming-handler` interface. A workload that exports the p3 `wasi:http/handler@0.3.0` — which the host serves all the same (wash-runtime's is_incoming_http_handler accepts either) — reached Ready but got no gateway route, so every request to it 503'd at the gateway with no trace on the host. Register a route for either interface, matching what the host serves. Covered by a workloadHostname unit test (p2, p3, missing host config, non-http). Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
Add an operator e2e spec that proves the host-plugin loading path end to end in a real cluster: a hostGroup `hostPlugins` entry renders to `--host-plugin`, `wash host` fetches and registers the plugin at startup, a workload importing the plugin's bespoke `acme:kv/store` interface binds across the store boundary, and a value written via `/set` reads back via `/get` from the plugin's store. Built on the in-cluster registry flow (make e2e-images), like the implements and messaging specs, rather than out-of-band published images: - xtask e2e-images: build and push the kv-plugin and kv-plugin-caller fixtures into the in-cluster oci-registry. FIXTURES now carries each fixture's FixtureKind so the P3 (async) plugin fixtures resolve to their wasm32-wasip1 build output rather than the wasip2 path the plain components use. - host_plugin_test.go: pull both fixtures by their in-cluster registryRef; gate on the in-cluster registry AND an all-features fixture host (the host-component-plugins feature is off in release builds), self-skipping otherwise. A plugin loads at host startup, so BeforeAll rolls the default hostgroup with the plugin via `helm upgrade` (the registry is serving by then) and AfterAll restores a plugin-free host, keeping the spec order-independent. The caller exposes its capability over the P3 wasi:http/handler@0.3.0 export. - e2e_suite_test.go: the local BUILD_RUNTIME_IMAGE build enables host-component-plugins alongside implements, matching the all-features wash-image build; it also builds the runtime-gateway from the local tree (and points gateway.image at it) so branch changes to the gateway — like routing the p3 handler above — are exercised instead of the published canary. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
ricochet
force-pushed
the
p3-host-plugin-loader
branch
from
July 22, 2026 22:04
8202688 to
5cd52cc
Compare
jfleitz
approved these changes
Jul 22, 2026
ricochet
enabled auto-merge (rebase)
July 22, 2026 22:34
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#5335 added the
ComponentHostPluginadapter, a WebAssembly component that provides a host capability from its own supervised store but nothing could actually load one outside of tests. This PR adds the production loading path across all three host front-ends, plus an end-to-end test.A host component plugin is loaded exactly like a built-in Rust plugin (
HostBuilder::with_plugin, started atHost::start); the only new work is turning a declaration (an id + a place to fetch the wasm) into a registered plugin.Runtime primitive (
wash-runtime)ComponentPluginSpec/PluginSource— a plain, always-compiled declaration (id + OCI-image-or-file source + optionalmax_restarts/ digest pin), with aFromStrfor the CLI grammar.load_component_plugin(gated onhost-component-plugins) — fetches the wasm (oci::pull_componentortokio::fs::read), verifies an optional digest pin, and builds theComponentHostPlugin. The caller registers it withwith_pluginbeforeHost::start.wash host(cluster / operator path)--host-plugin/WASH_HOST_PLUGINS(repeatable). Comma-separatedkey=value:id=<name>,image=<ref>[,pull=…][,max-restarts=N][,digest=…]orid=<name>,file=<path>.wash dev(local path)dev.host_pluginsconfig entries (fileorimagesource).Chart (operator path)
runtime.hostGroups[].hostPluginsvalues, rendered into--host-pluginargs on the host Deployment.Design decisions
WorkloadStartRequestcan register a host-global provider.HostApiis only heartbeat/workload_start/status/stop) and the registry is fixed afterbuild(). Loading resolves once at boot. Dynamic load/unload on a running host is noted as future work (needs a mutable registry, workload re-bind, and anArc<str>id).host-component-plugins(not in release builds). The spec/flag/config types are always compiled, so a plugin declared against a release host produces a clear "requires the feature" error rather than a silent drop — important for the operator/env path.HostCRD is status-only. It's created from the host's heartbeat (HostPodReconcileronly bridges pod↔Host lifecycle); host pods are Helm-templated per host-group. So the declarative host-config seam is the host-group values, which is wherehostPluginslives.&'static strid is interned via a boundedBox::leak(plugins load once at startup and live for the process); flagged to revisit if dynamic loading lands.Testing
ComponentPluginSpecgrammar parsing (valid/invalid).load_component_pluginfrom a file builds a plugin with the right id/world; digest-pin-on-file and missing-file error paths; and an opt-in OCI-source test (HOST_PLUGIN_OCI_REF, self-skips in CI).HostPluginConfig::to_specvalidation +dev.host_pluginsYAML round-trip.runtime-operator/test/e2e/host_plugin_test.go): a ginkgo/kind spec that loads a plugin onto a hostGroup viahostPlugins, deploys a workload importing the plugin'sacme:kv/store, and asserts a value written via/setreads back via/get— proving the CLI/chart loading path and cross-store bind work in a real cluster.Verified end-to-end locally in kind against a
registry:2on the kind network: built the all-features host image, pushed the two fixtures withwash oci push, and ran the ginkgo spec — the host pulled the plugin from OCI, loaded it, anacme:kvworkload scheduled and reached Ready, and/set→/getround-tripped a value through the plugin's store.SUCCESS! -- 1 Passed. The gated OCI-sourceload_component_plugintest and a directwash host --host-plugin …run were also verified against the same registry. In CI this stays gated on the two fixtures being published (HOST_PLUGIN_E2E_IMAGE,HOST_PLUGIN_CALLER_E2E_IMAGE).Follow-up to activate the e2e
The e2e self-skips until two fixtures are published (same bootstrap the implements/messaging specs used), because host plugins load at host startup. A non-pullable image would fail host boot and break the all-features leg. To turn it on:
crates/wash-runtime/tests/fixtures/kv-pluginandkv-plugin-calleras OCI images.host_plugin_image/host_plugin_caller_imagematrix values inwash.yml(all-features leg).