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
Add spin up --key-value
option
#1442
Conversation
Before I added the |
FYI, you can annotate Tokio tests using |
This has been manually tested, but doesn't have automated tests - I'm not sure how best to go about those. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Regarding testing: perhaps we could add a variation on the key_value_works
test in tests/spinup_tests.rs
, tests/testcases/mod.rs
, and tests/testcases/key-value
.
#1448 is thwarting my ability to implement the suggested test. I am thinking I will merge this as-is and open a new issue to get it under e2e test coverage. |
fn get_for_init(&self) -> Option<Arc<dyn StoreManager>>; // Some stores might not support init | ||
} | ||
|
||
pub struct SingletonStoreManagerManager { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😱
crates/trigger/src/lib.rs
Outdated
runtime_config::key_value::build_key_value_component(&runtime_config)?, | ||
init_data.kv, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless I'm missing something you should be able to pass this init data into the function that creates the component here:
runtime_config::key_value::build_key_value_component(&runtime_config)?, | |
init_data.kv, | |
runtime_config::key_value::build_key_value_component(&runtime_config, init_data.kv)?, |
and then in build_key_value_component
do something like:
pub fn build_key_value_component(runtime_config: &RuntimeConfig, initial_values: Vec<(String, String)>) -> Result<KeyValueComponent> {
let mut stores = runtime_config
.key_value_stores()
.context("Failed to build key-value component")?
.collect::<HashMap<_, _>>();
if !initial_values.is_empty() {
if let Some(manager) = stores.get("default") {
let store = manager.get("default").unwrap();
for (key, value) in initial_values {
store.set(&key, value.as_bytes())?;
}
} else {
// bail? log warning?
}
}
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was kind of thinking it would be nice to have the initialisation login in the component itself rather than as a side effect in a function in the runtime_config
module. But maybe I should get over myself!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the DynamicHostComponent::initialize
method is necessary.
crates/trigger/src/lib.rs
Outdated
self.loader | ||
.add_dynamic_host_component( | ||
&mut builder, | ||
runtime_config::key_value::build_key_value_component(&runtime_config)?, | ||
init_data.kv, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was kind of thinking it would be nice to have the initialisation logic in the component itself
Sure!
self.loader | |
.add_dynamic_host_component( | |
&mut builder, | |
runtime_config::key_value::build_key_value_component(&runtime_config)?, | |
init_data.kv, | |
let component = runtime_config::key_value::build_key_value_component(&runtime_config)?; | |
component.set_initial_values(init_data.kv)?; | |
self.loader | |
.add_dynamic_host_component( | |
&mut builder, | |
component |
impl KeyValueComponent {
pub fn set_intitial_values ...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...or some variation thereof... maybe the set_initial_values
call would be better off inside build_key_value_component
🤷
crates/app/src/lib.rs
Outdated
@@ -79,7 +79,7 @@ impl AppLoader { | |||
/// | |||
/// This calls [`EngineBuilder::add_host_component`] for you; it should not | |||
/// be called separately. | |||
pub fn add_dynamic_host_component<T: Send + Sync, DHC: DynamicHostComponent>( | |||
pub async fn add_dynamic_host_component<T: Send + Sync, DHC: DynamicHostComponent>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be removed now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aargh, good spot, thanks!
crates/trigger/src/cli.rs
Outdated
@@ -243,6 +254,16 @@ async fn warn_slow_wasm_build() { | |||
println!(); | |||
} | |||
|
|||
// Parse the key/values passed in as `key=value` pairs. | |||
// TODO: `spin-utils` WHEN |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀
crates/trigger/src/cli.rs
Outdated
let parts: Vec<_> = s.splitn(2, '=').collect(); | ||
if parts.len() != 2 { | ||
bail!("Key/Values must be of the form `key=value`"); | ||
} | ||
Ok((parts[0].to_owned(), parts[1].to_owned())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slightly nicer:
let parts: Vec<_> = s.splitn(2, '=').collect(); | |
if parts.len() != 2 { | |
bail!("Key/Values must be of the form `key=value`"); | |
} | |
Ok((parts[0].to_owned(), parts[1].to_owned())) | |
let Some((key, value)) = s.split_once('=') else { | |
bail!("Key/Values must be of the form `key=value`"); | |
} | |
Ok((key.to_owned(), value.to_owned())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not only nicer, but more correct (if a value contains an =
sign e.g. base64). But I wanted to keep it exactly the same as the deploy
one (and didn't want to change deploy
) so that when spin-utils came along we wouldn't need to eyeball them for subtle differences.
But yeah now I can (rebase and) move to common.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think splitn(2, ...)
makes it equivalent. (I had the exact same thought on first look)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And you are, of course, correct. Can I start today again please, this time with more tea...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move to common
Ooo yeah we're probably duplicating a few of these arg parsers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How similar this is to the environment variable parser will shock you.
crates/app/src/host_component.rs
Outdated
@@ -8,6 +8,7 @@ use crate::AppComponent; | |||
/// | |||
/// This extends [`HostComponent`] to support per-[`AppComponent`] dynamic | |||
/// runtime configuration. | |||
#[async_trait::async_trait] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove?
@@ -54,6 +54,7 @@ impl HostComponent for KeyValueComponent { | |||
} | |||
} | |||
|
|||
#[async_trait] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove?
Signed-off-by: itowlson <ivan.towlson@fermyon.com>
I saw there is a comment in The new v1.2.0 release of spin has this struct as a argument to |
@Mossaka |
Fixes #1404.
Note this currently assumes the answer to my questions there is "default store suffices." The core logic is easy enough to extend to support non-default stores; the difficulty is only in the CLI syntax.
This PR contains (in
key-value/src/host_component.rs
) the greatest Rust crime I have ever committed. @dicej it would be great to get your thoughts on the right way to do this. The trouble I kept running into was thatStoreManagerManager
would only dispense a Store if you gave it an AppComponent. My guess is I need to add aget_for_app
method toStoreManagerManager
but then we will not longer be able to implement it via a function...Also @lann does the proposed entry point for carrying out before-run host component initialisation seem reasonable? I was wondering about using
builder.hooks
to set anapp_loaded
hook. The place where hooks are currently set didn't seem to have access to the host component itself, but would it be reasonable to set a hook where we instantiate and add the key/value DynamicHostComponent (TriggerExecutorBuilder::build
)?Or are these questions two sides of the same coin, and if I set the right hook in the right place then I wouldn't need to go through
StoreManagerManager
at all?