-
Notifications
You must be signed in to change notification settings - Fork 289
Implement wasi-config #2869
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement wasi-config #2869
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,6 +52,22 @@ impl ProviderResolver { | |
| self.resolve_template(template).await | ||
| } | ||
|
|
||
| /// Resolves all variables for the given component. | ||
| pub async fn resolve_all(&self, component_id: &str) -> Result<Vec<(String, String)>> { | ||
| use futures::FutureExt; | ||
|
|
||
| let Some(keys2templates) = self.internal.component_configs.get(component_id) else { | ||
| return Ok(vec![]); | ||
| }; | ||
|
|
||
| let resolve_futs = keys2templates.iter().map(|(key, template)| { | ||
| self.resolve_template(template) | ||
| .map(|r| r.map(|value| (key.to_string(), value))) | ||
| }); | ||
|
|
||
| futures::future::try_join_all(resolve_futs).await | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am slightly worried about performance - at some point we might want to see if adding some sort of caching mechanism to providers is worth it.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably push this down into the provider as e.g. (untested): trait Provider {
async fn get(&self, key: &Key) -> anyhow::Result<Option<String>>;
async fn get_many(&self, keys: impl IntoIterator<Item = &Key>) -> anyhow::Result<impl IntoIterator<Option<String>>> {
try_join_all(keys.into_iter().map(|key| self.get(key)).await
}
}
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think any of our current providers can optimise this beyond running the futures concurrently - all appear to support retrieving only one secret at once - so I propose we defer this.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
|
|
||
| /// Resolves the given template. | ||
| pub async fn resolve_template(&self, template: &Template) -> Result<String> { | ||
| let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ impl Factor for VariablesFactor { | |
| fn init<T: Send + 'static>(&mut self, mut ctx: InitContext<T, Self>) -> anyhow::Result<()> { | ||
| ctx.link_bindings(spin_world::v1::config::add_to_linker)?; | ||
| ctx.link_bindings(spin_world::v2::variables::add_to_linker)?; | ||
| ctx.link_bindings(spin_world::wasi::config::store::add_to_linker)?; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we'll want to consider whether we want to make this configurable for users of the factor - i.e., do we want to force all runtimes that use the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Crate feature flag?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like the feature is light-weight enough that we could just do a dynamic check instead of a compile time feature flag, but I don't feel strongly either way.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll need to make a decision on this (and a similar consideration for wasi-keyvalue) fairly soon if we hope to land this in Spin 3.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My suggestion is just an API concern for those using this factor as library (in alternate runtimes). Whether Spin CLI turns this feature on, puts it behind a feature flag, or something else, is unrelated to whether the factor allows embedder to turn on wasi config or not. That all being said, I'm highly in favor of allowing embedders to control what gets linked. I have less strong feelings about how that gets accomplished, but I think I prefer a dynamic check (i.e., a bool, enum, or maybe even bitflags that the embedder passes to the factor to indicate which interfaces should be linked in).
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It sounds like you're expressing a general principle about allowing embedders to control which interfaces get linked, rather than anything specific to wasi-config, or am I misunderstanding? |
||
| Ok(()) | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,7 @@ wasmtime::component::bindgen!({ | |
| "fermyon:spin/[email protected]/error" => v2::sqlite::Error, | ||
| "fermyon:spin/sqlite/error" => v1::sqlite::Error, | ||
| "fermyon:spin/[email protected]/error" => v2::variables::Error, | ||
| "wasi:config/store/error" => wasi::config::store::Error, | ||
| }, | ||
| trappable_imports: true, | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| spin_manifest_version = "1" | ||
| authors = [""] | ||
| description = "" | ||
| name = "variables" | ||
| trigger = { type = "http" } | ||
| version = "0.1.0" | ||
|
|
||
| [variables] | ||
| variable = { default = "value" } | ||
|
|
||
| [[component]] | ||
| id = "variables" | ||
| source = "%{source=variables}" | ||
| [component.trigger] | ||
| route = "/..." | ||
| [component.config] | ||
| variable = "{{ variable }}" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| spin_manifest_version = "1" | ||
| authors = [""] | ||
| description = "" | ||
| name = "wasi-config" | ||
| trigger = { type = "http" } | ||
| version = "0.1.0" | ||
|
|
||
| [variables] | ||
| variable = { default = "value" } | ||
|
|
||
| [[component]] | ||
| id = "wasi-config" | ||
| source = "%{source=wasi-config}" | ||
| [component.trigger] | ||
| route = "/..." | ||
| [component.config] | ||
| variable = "{{ variable }}" |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| [package] | ||
| name = "wasi-config" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
|
|
||
| [lib] | ||
| crate-type = ["cdylib"] | ||
|
|
||
| [dependencies] | ||
| helper = { path = "../../helper" } | ||
| wit-bindgen = "0.16.0" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # Variables | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should consider adding a conformance test for this. The runtime tests here are meant to be Spin CLI specific but this test seems like it should be applied to all Spin compliant runtimes.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't do that as part of this PR but I've raised fermyon/conformance-tests#40 to remind us to do so once this merges. |
||
|
|
||
| Tests the wasi:config interface. | ||
|
|
||
| ## Expectations | ||
|
|
||
| This test component expects the following to be true: | ||
| * Only the variable named "variable" is defined with value "value" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| use helper::ensure_matches; | ||
|
|
||
| use bindings::wasi::config::store::{get, get_all}; | ||
|
|
||
| helper::define_component!(Component); | ||
|
|
||
| impl Component { | ||
| fn main() -> Result<(), String> { | ||
| ensure_matches!(get("variable"), Ok(Some(val)) if val == "value"); | ||
| ensure_matches!(get("non_existent"), Ok(None)); | ||
|
|
||
| let expected_all = vec![ | ||
| ("variable".to_owned(), "value".to_owned()), | ||
| ]; | ||
| ensure_matches!(get_all(), Ok(val) if val == expected_all); | ||
|
|
||
| ensure_matches!(get("invalid-name"), Ok(None)); | ||
| ensure_matches!(get("invalid!name"), Ok(None)); | ||
| ensure_matches!(get("4invalidname"), Ok(None)); | ||
|
|
||
| Ok(()) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,4 +9,5 @@ world http-trigger { | |
| /// The imports needed for a guest to run on a Spin host | ||
| world platform { | ||
| include fermyon:spin/[email protected]; | ||
| import wasi:config/[email protected]; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| interface store { | ||
| /// An error type that encapsulates the different errors that can occur fetching configuration values. | ||
| variant error { | ||
| /// This indicates an error from an "upstream" config source. | ||
| /// As this could be almost _anything_ (such as Vault, Kubernetes ConfigMaps, KeyValue buckets, etc), | ||
| /// the error message is a string. | ||
| upstream(string), | ||
| /// This indicates an error from an I/O operation. | ||
| /// As this could be almost _anything_ (such as a file read, network connection, etc), | ||
| /// the error message is a string. | ||
| /// Depending on how this ends up being consumed, | ||
| /// we may consider moving this to use the `wasi:io/error` type instead. | ||
| /// For simplicity right now in supporting multiple implementations, it is being left as a string. | ||
| io(string), | ||
| } | ||
|
|
||
| /// Gets a configuration value of type `string` associated with the `key`. | ||
| /// | ||
| /// The value is returned as an `option<string>`. If the key is not found, | ||
| /// `Ok(none)` is returned. If an error occurs, an `Err(error)` is returned. | ||
| get: func( | ||
| /// A string key to fetch | ||
| key: string | ||
| ) -> result<option<string>, error>; | ||
|
|
||
| /// Gets a list of configuration key-value pairs of type `string`. | ||
| /// | ||
| /// If an error occurs, an `Err(error)` is returned. | ||
| get-all: func() -> result<list<tuple<string, string>>, error>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package wasi:[email protected]; | ||
|
|
||
| world imports { | ||
| /// The interface for wasi:config/store | ||
| import store; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.