From 1c1f8b0fbaf5eb79cc78c9c566d6c7fdbcac9dea Mon Sep 17 00:00:00 2001 From: Caleb Schoepp Date: Fri, 30 Aug 2024 14:54:22 -0600 Subject: [PATCH] Merge host component key value implementations into new factor crates Signed-off-by: Caleb Schoepp --- Cargo.lock | 80 ++---- Cargo.toml | 2 - crates/factor-key-value-azure/Cargo.toml | 19 -- crates/factor-key-value-azure/src/lib.rs | 58 ---- crates/factor-key-value-redis/Cargo.toml | 15 - crates/factor-key-value-redis/src/lib.rs | 38 --- crates/factor-key-value-spin/Cargo.toml | 15 - crates/factor-key-value/Cargo.toml | 12 +- .../lib.rs => factor-key-value/src/host.rs} | 11 +- crates/factor-key-value/src/lib.rs | 14 +- crates/factor-key-value/src/runtime_config.rs | 2 +- .../src/runtime_config/spin.rs | 2 +- .../src/util.rs | 0 crates/factor-key-value/tests/factor_test.rs | 4 +- crates/key-value-azure/Cargo.toml | 16 +- crates/key-value-azure/src/lib.rs | 261 +++--------------- crates/key-value-azure/src/store.rs | 233 ++++++++++++++++ crates/key-value-redis/Cargo.toml | 5 +- crates/key-value-redis/src/lib.rs | 120 ++------ crates/key-value-redis/src/store.rs | 108 ++++++++ .../Cargo.toml | 7 +- .../src/lib.rs | 4 +- .../lib.rs => key-value-spin/src/store.rs} | 4 +- crates/key-value/Cargo.toml | 18 -- crates/key-value/src/host_component.rs | 105 ------- crates/runtime-config/Cargo.toml | 6 +- crates/runtime-config/src/lib.rs | 8 +- examples/spin-timer/Cargo.lock | 67 +---- tests/test-components/components/Cargo.lock | 22 +- 29 files changed, 508 insertions(+), 748 deletions(-) delete mode 100644 crates/factor-key-value-azure/Cargo.toml delete mode 100644 crates/factor-key-value-azure/src/lib.rs delete mode 100644 crates/factor-key-value-redis/Cargo.toml delete mode 100644 crates/factor-key-value-redis/src/lib.rs delete mode 100644 crates/factor-key-value-spin/Cargo.toml rename crates/{key-value/src/lib.rs => factor-key-value/src/host.rs} (96%) rename crates/{key-value => factor-key-value}/src/util.rs (100%) create mode 100644 crates/key-value-azure/src/store.rs create mode 100644 crates/key-value-redis/src/store.rs rename crates/{key-value-sqlite => key-value-spin}/Cargo.toml (71%) rename crates/{factor-key-value-spin => key-value-spin}/src/lib.rs (97%) rename crates/{key-value-sqlite/src/lib.rs => key-value-spin/src/store.rs} (98%) delete mode 100644 crates/key-value/Cargo.toml delete mode 100644 crates/key-value/src/host_component.rs diff --git a/Cargo.lock b/Cargo.lock index ad3850d08f..5f4cf9fc74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,7 +571,7 @@ dependencies = [ [[package]] name = "azure_core" version = "0.20.0" -source = "git+https://github.com/azure/azure-sdk-for-rust.git?rev=8c4caa251c3903d5eae848b41bb1d02a4d65231c#8c4caa251c3903d5eae848b41bb1d02a4d65231c" +source = "git+https://github.com/azure/azure-sdk-for-rust?rev=8c4caa251c3903d5eae848b41bb1d02a4d65231c#8c4caa251c3903d5eae848b41bb1d02a4d65231c" dependencies = [ "async-trait", "base64 0.22.1", @@ -599,7 +599,7 @@ dependencies = [ [[package]] name = "azure_data_cosmos" version = "0.20.0" -source = "git+https://github.com/azure/azure-sdk-for-rust.git?rev=8c4caa251c3903d5eae848b41bb1d02a4d65231c#8c4caa251c3903d5eae848b41bb1d02a4d65231c" +source = "git+https://github.com/azure/azure-sdk-for-rust?rev=8c4caa251c3903d5eae848b41bb1d02a4d65231c#8c4caa251c3903d5eae848b41bb1d02a4d65231c" dependencies = [ "async-trait", "azure_core", @@ -617,7 +617,7 @@ dependencies = [ [[package]] name = "azure_identity" version = "0.20.0" -source = "git+https://github.com/azure/azure-sdk-for-rust.git?rev=8c4caa251c3903d5eae848b41bb1d02a4d65231c#8c4caa251c3903d5eae848b41bb1d02a4d65231c" +source = "git+https://github.com/azure/azure-sdk-for-rust?rev=8c4caa251c3903d5eae848b41bb1d02a4d65231c#8c4caa251c3903d5eae848b41bb1d02a4d65231c" dependencies = [ "async-lock 3.3.0", "async-process 2.2.2", @@ -637,7 +637,7 @@ dependencies = [ [[package]] name = "azure_security_keyvault" version = "0.20.0" -source = "git+https://github.com/azure/azure-sdk-for-rust.git?rev=8c4caa251c3903d5eae848b41bb1d02a4d65231c#8c4caa251c3903d5eae848b41bb1d02a4d65231c" +source = "git+https://github.com/azure/azure-sdk-for-rust?rev=8c4caa251c3903d5eae848b41bb1d02a4d65231c#8c4caa251c3903d5eae848b41bb1d02a4d65231c" dependencies = [ "async-trait", "azure_core", @@ -7130,8 +7130,6 @@ dependencies = [ "spin-doctor", "spin-expressions", "spin-http", - "spin-key-value", - "spin-key-value-sqlite", "spin-loader", "spin-locked-app", "spin-manifest", @@ -7274,46 +7272,20 @@ name = "spin-factor-key-value" version = "2.8.0-pre0" dependencies = [ "anyhow", + "lru 0.9.0", "serde 1.0.197", - "spin-factor-key-value-redis", - "spin-factor-key-value-spin", + "spin-core", "spin-factors", "spin-factors-test", - "spin-key-value", + "spin-key-value-redis", + "spin-key-value-spin", + "spin-locked-app", "spin-world", + "table", "tempfile", "tokio", "toml 0.8.14", -] - -[[package]] -name = "spin-factor-key-value-azure" -version = "2.8.0-pre0" -dependencies = [ - "anyhow", - "serde 1.0.197", - "spin-factor-key-value", - "spin-key-value-azure", -] - -[[package]] -name = "spin-factor-key-value-redis" -version = "2.8.0-pre0" -dependencies = [ - "anyhow", - "serde 1.0.197", - "spin-factor-key-value", - "spin-key-value-redis", -] - -[[package]] -name = "spin-factor-key-value-spin" -version = "2.8.0-pre0" -dependencies = [ - "anyhow", - "serde 1.0.197", - "spin-factor-key-value", - "spin-key-value-sqlite", + "tracing", ] [[package]] @@ -7581,20 +7553,6 @@ dependencies = [ "wasmtime-wasi-http", ] -[[package]] -name = "spin-key-value" -version = "2.8.0-pre0" -dependencies = [ - "anyhow", - "lru 0.9.0", - "spin-app", - "spin-core", - "spin-world", - "table", - "tokio", - "tracing", -] - [[package]] name = "spin-key-value-azure" version = "2.8.0-pre0" @@ -7605,7 +7563,7 @@ dependencies = [ "futures", "serde 1.0.197", "spin-core", - "spin-key-value", + "spin-factor-key-value", "tokio", "tracing", "url", @@ -7617,8 +7575,9 @@ version = "2.8.0-pre0" dependencies = [ "anyhow", "redis 0.21.7", + "serde 1.0.197", "spin-core", - "spin-key-value", + "spin-factor-key-value", "spin-world", "tokio", "tracing", @@ -7626,14 +7585,15 @@ dependencies = [ ] [[package]] -name = "spin-key-value-sqlite" +name = "spin-key-value-spin" version = "2.8.0-pre0" dependencies = [ "anyhow", "once_cell", "rusqlite", + "serde 1.0.197", "spin-core", - "spin-key-value", + "spin-factor-key-value", "spin-world", "tokio", "tracing", @@ -7821,9 +7781,6 @@ version = "2.8.0-pre0" dependencies = [ "anyhow", "spin-factor-key-value", - "spin-factor-key-value-azure", - "spin-factor-key-value-redis", - "spin-factor-key-value-spin", "spin-factor-llm", "spin-factor-outbound-http", "spin-factor-outbound-mqtt", @@ -7835,6 +7792,9 @@ dependencies = [ "spin-factor-variables", "spin-factor-wasi", "spin-factors", + "spin-key-value-azure", + "spin-key-value-redis", + "spin-key-value-spin", "spin-sqlite", "toml 0.8.14", ] diff --git a/Cargo.toml b/Cargo.toml index c656a30777..f63752f72b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,8 +49,6 @@ spin-common = { path = "crates/common" } spin-doctor = { path = "crates/doctor" } spin-expressions = { path = "crates/expressions" } spin-http = { path = "crates/http" } -spin-key-value = { path = "crates/key-value" } -spin-key-value-sqlite = { path = "crates/key-value-sqlite" } spin-loader = { path = "crates/loader" } spin-locked-app = { path = "crates/locked-app" } spin-manifest = { path = "crates/manifest" } diff --git a/crates/factor-key-value-azure/Cargo.toml b/crates/factor-key-value-azure/Cargo.toml deleted file mode 100644 index 318855ff98..0000000000 --- a/crates/factor-key-value-azure/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "spin-factor-key-value-azure" -version.workspace = true -authors.workspace = true -edition.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true -rust-version.workspace = true - -[dependencies] -anyhow = "1.0" -serde = { version = "1.0", features = ["rc"] } -spin-factor-key-value = { path = "../factor-key-value" } -# TODO: merge with this crate -spin-key-value-azure = { path = "../key-value-azure" } - -[lints] -workspace = true diff --git a/crates/factor-key-value-azure/src/lib.rs b/crates/factor-key-value-azure/src/lib.rs deleted file mode 100644 index cad2da1ee8..0000000000 --- a/crates/factor-key-value-azure/src/lib.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::Deserialize; -use spin_factor_key_value::runtime_config::spin::MakeKeyValueStore; -use spin_key_value_azure::{ - KeyValueAzureCosmos, KeyValueAzureCosmosAuthOptions, KeyValueAzureCosmosRuntimeConfigOptions, -}; - -/// A key-value store that uses Azure Cosmos as the backend. -#[derive(Default)] -pub struct AzureKeyValueStore { - _priv: (), -} - -impl AzureKeyValueStore { - /// Creates a new `AzureKeyValueStore`. - pub fn new() -> Self { - Self::default() - } -} - -/// Runtime configuration for the Azure Cosmos key-value store. -#[derive(Deserialize)] -pub struct AzureCosmosKeyValueRuntimeConfig { - /// The authorization token for the Azure Cosmos DB account. - key: Option, - /// The Azure Cosmos DB account name. - account: String, - /// The Azure Cosmos DB database. - database: String, - /// The Azure Cosmos DB container where data is stored. - /// The CosmosDB container must be created with the default partition key, /id - container: String, -} - -impl MakeKeyValueStore for AzureKeyValueStore { - const RUNTIME_CONFIG_TYPE: &'static str = "azure_cosmos"; - - type RuntimeConfig = AzureCosmosKeyValueRuntimeConfig; - - type StoreManager = KeyValueAzureCosmos; - - fn make_store( - &self, - runtime_config: Self::RuntimeConfig, - ) -> anyhow::Result { - let auth_options = match runtime_config.key { - Some(key) => KeyValueAzureCosmosAuthOptions::RuntimeConfigValues( - KeyValueAzureCosmosRuntimeConfigOptions::new(key), - ), - None => KeyValueAzureCosmosAuthOptions::Environmental, - }; - KeyValueAzureCosmos::new( - runtime_config.account, - runtime_config.database, - runtime_config.container, - auth_options, - ) - } -} diff --git a/crates/factor-key-value-redis/Cargo.toml b/crates/factor-key-value-redis/Cargo.toml deleted file mode 100644 index 1c19c58ff5..0000000000 --- a/crates/factor-key-value-redis/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "spin-factor-key-value-redis" -version = { workspace = true } -authors = { workspace = true } -edition = { workspace = true } - -[dependencies] -anyhow = "1.0" -serde = { version = "1.0", features = ["rc"] } -spin-factor-key-value = { path = "../factor-key-value" } -# TODO: merge with this crate -spin-key-value-redis = { path = "../key-value-redis" } - -[lints] -workspace = true diff --git a/crates/factor-key-value-redis/src/lib.rs b/crates/factor-key-value-redis/src/lib.rs deleted file mode 100644 index 67d71ac3e5..0000000000 --- a/crates/factor-key-value-redis/src/lib.rs +++ /dev/null @@ -1,38 +0,0 @@ -use serde::Deserialize; -use spin_factor_key_value::runtime_config::spin::MakeKeyValueStore; -use spin_key_value_redis::KeyValueRedis; - -/// A key-value store that uses Redis as the backend. -#[derive(Default)] -pub struct RedisKeyValueStore { - _priv: (), -} - -impl RedisKeyValueStore { - /// Creates a new `RedisKeyValueStore`. - pub fn new() -> Self { - Self::default() - } -} - -/// Runtime configuration for the Redis key-value store. -#[derive(Deserialize)] -pub struct RedisKeyValueRuntimeConfig { - /// The URL of the Redis server. - url: String, -} - -impl MakeKeyValueStore for RedisKeyValueStore { - const RUNTIME_CONFIG_TYPE: &'static str = "redis"; - - type RuntimeConfig = RedisKeyValueRuntimeConfig; - - type StoreManager = KeyValueRedis; - - fn make_store( - &self, - runtime_config: Self::RuntimeConfig, - ) -> anyhow::Result { - KeyValueRedis::new(runtime_config.url) - } -} diff --git a/crates/factor-key-value-spin/Cargo.toml b/crates/factor-key-value-spin/Cargo.toml deleted file mode 100644 index 29ca47c3f3..0000000000 --- a/crates/factor-key-value-spin/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "spin-factor-key-value-spin" -version = { workspace = true } -authors = { workspace = true } -edition = { workspace = true } - -[dependencies] -anyhow = "1.0" -serde = { version = "1.0", features = ["rc"] } -spin-factor-key-value = { path = "../factor-key-value" } -# TODO: merge with this crate -spin-key-value-sqlite = { path = "../key-value-sqlite" } - -[lints] -workspace = true diff --git a/crates/factor-key-value/Cargo.toml b/crates/factor-key-value/Cargo.toml index 9efd51b703..594a4ec4c9 100644 --- a/crates/factor-key-value/Cargo.toml +++ b/crates/factor-key-value/Cargo.toml @@ -6,16 +6,20 @@ edition = { workspace = true } [dependencies] anyhow = "1.0" +lru = "0.9.0" serde = { version = "1.0", features = ["rc"] } +spin-core = { path = "../core" } spin-factors = { path = "../factors" } -# TODO: merge with this crate -spin-key-value = { path = "../key-value" } +spin-locked-app = { path = "../locked-app" } spin-world = { path = "../world" } +table = { path = "../table" } +tokio = { version = "1", features = ["macros", "sync", "rt"] } toml = "0.8" +tracing = { workspace = true } [dev-dependencies] -spin-factor-key-value-redis = { path = "../factor-key-value-redis" } -spin-factor-key-value-spin = { path = "../factor-key-value-spin" } +spin-key-value-redis = { path = "../key-value-redis" } +spin-key-value-spin = { path = "../key-value-spin" } spin-factors-test = { path = "../factors-test" } tempfile = "3.12.0" tokio = { version = "1", features = ["macros", "rt"] } diff --git a/crates/key-value/src/lib.rs b/crates/factor-key-value/src/host.rs similarity index 96% rename from crates/key-value/src/lib.rs rename to crates/factor-key-value/src/host.rs index 2e5c7f4b1b..fd9da9a394 100644 --- a/crates/key-value/src/lib.rs +++ b/crates/factor-key-value/src/host.rs @@ -1,18 +1,11 @@ +use crate::util::EmptyStoreManager; use anyhow::{Context, Result}; -use spin_app::MetadataKey; use spin_core::{async_trait, wasmtime::component::Resource}; +use spin_locked_app::MetadataKey; use spin_world::v2::key_value; use std::{collections::HashSet, sync::Arc}; use table::Table; -// TODO(factors): Code left for reference; remove after migration to factors -// mod host_component; -mod util; - -pub use util::{ - CachingStoreManager, DefaultManagerGetter, DelegatingStoreManager, EmptyStoreManager, -}; - pub const KEY_VALUE_STORES_KEY: MetadataKey> = MetadataKey::new("key_value_stores"); const DEFAULT_STORE_TABLE_CAPACITY: u32 = 256; diff --git a/crates/factor-key-value/src/lib.rs b/crates/factor-key-value/src/lib.rs index 77a4ce1e8a..1f43b8ba73 100644 --- a/crates/factor-key-value/src/lib.rs +++ b/crates/factor-key-value/src/lib.rs @@ -1,4 +1,6 @@ +mod host; pub mod runtime_config; +mod util; use std::{ collections::{HashMap, HashSet}, @@ -6,16 +8,16 @@ use std::{ }; use anyhow::ensure; +use host::KEY_VALUE_STORES_KEY; use spin_factors::{ ConfigureAppContext, Factor, FactorInstanceBuilder, InitContext, InstanceBuilders, PrepareContext, RuntimeFactors, }; -use spin_key_value::{ - CachingStoreManager, DefaultManagerGetter, DelegatingStoreManager, KeyValueDispatch, - StoreManager, KEY_VALUE_STORES_KEY, -}; +use util::{CachingStoreManager, DefaultManagerGetter}; +pub use host::{log_error, Error, KeyValueDispatch, Store, StoreManager}; pub use runtime_config::RuntimeConfig; +pub use util::DelegatingStoreManager; /// A factor that provides key-value storage. pub struct KeyValueFactor { @@ -23,8 +25,8 @@ pub struct KeyValueFactor { } impl KeyValueFactor { - /// Create a new KeyValueFactor. - /// + /// Create a new KeyValueFactor. + /// /// The `default_label_resolver` is used to resolve store managers for labels that /// are not defined in the runtime configuration. pub fn new(default_label_resolver: impl DefaultLabelResolver + 'static) -> Self { diff --git a/crates/factor-key-value/src/runtime_config.rs b/crates/factor-key-value/src/runtime_config.rs index 0c83243a31..76e06da9d9 100644 --- a/crates/factor-key-value/src/runtime_config.rs +++ b/crates/factor-key-value/src/runtime_config.rs @@ -2,7 +2,7 @@ pub mod spin; use std::{collections::HashMap, sync::Arc}; -use spin_key_value::StoreManager; +use crate::StoreManager; /// Runtime configuration for all key value stores. #[derive(Default, Clone)] diff --git a/crates/factor-key-value/src/runtime_config/spin.rs b/crates/factor-key-value/src/runtime_config/spin.rs index 64c4e1d57f..cae8b6eb42 100644 --- a/crates/factor-key-value/src/runtime_config/spin.rs +++ b/crates/factor-key-value/src/runtime_config/spin.rs @@ -1,11 +1,11 @@ //! Runtime configuration implementation used by Spin CLI. +use crate::StoreManager; use crate::{DefaultLabelResolver, RuntimeConfig}; use anyhow::Context as _; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use spin_factors::runtime_config::toml::GetTomlValue; -use spin_key_value::StoreManager; use std::{collections::HashMap, sync::Arc}; /// Defines the construction of a key value store from a serialized runtime config. diff --git a/crates/key-value/src/util.rs b/crates/factor-key-value/src/util.rs similarity index 100% rename from crates/key-value/src/util.rs rename to crates/factor-key-value/src/util.rs diff --git a/crates/factor-key-value/tests/factor_test.rs b/crates/factor-key-value/tests/factor_test.rs index 890e95ca9c..bdccc89638 100644 --- a/crates/factor-key-value/tests/factor_test.rs +++ b/crates/factor-key-value/tests/factor_test.rs @@ -3,10 +3,10 @@ use spin_factor_key_value::{ runtime_config::spin::{MakeKeyValueStore, RuntimeConfigResolver}, KeyValueFactor, RuntimeConfig, }; -use spin_factor_key_value_redis::RedisKeyValueStore; -use spin_factor_key_value_spin::{SpinKeyValueRuntimeConfig, SpinKeyValueStore}; use spin_factors::{FactorRuntimeConfigSource, RuntimeConfigSourceFinalizer, RuntimeFactors}; use spin_factors_test::{toml, TestEnvironment}; +use spin_key_value_redis::RedisKeyValueStore; +use spin_key_value_spin::{SpinKeyValueRuntimeConfig, SpinKeyValueStore}; use spin_world::v2::key_value::HostStore; use std::{collections::HashSet, sync::Arc}; diff --git a/crates/key-value-azure/Cargo.toml b/crates/key-value-azure/Cargo.toml index 13804c3b2e..1ee79d9f96 100644 --- a/crates/key-value-azure/Cargo.toml +++ b/crates/key-value-azure/Cargo.toml @@ -1,17 +1,21 @@ [package] name = "spin-key-value-azure" -version = { workspace = true } -authors = { workspace = true } -edition = { workspace = true } +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true [dependencies] -anyhow = "1" +anyhow = "1.0" azure_data_cosmos = { git = "https://github.com/azure/azure-sdk-for-rust.git", rev = "8c4caa251c3903d5eae848b41bb1d02a4d65231c" } azure_identity = { git = "https://github.com/azure/azure-sdk-for-rust.git", rev = "8c4caa251c3903d5eae848b41bb1d02a4d65231c" } futures = "0.3.28" -serde = { version = "1.0", features = ["derive"] } +serde = { version = "1.0", features = ["derive", "rc"] } spin-core = { path = "../core" } -spin-key-value = { path = "../key-value" } +spin-factor-key-value = { path = "../factor-key-value" } tokio = "1" tracing = { workspace = true } url = "2" diff --git a/crates/key-value-azure/src/lib.rs b/crates/key-value-azure/src/lib.rs index 204874dae6..f36fde8abf 100644 --- a/crates/key-value-azure/src/lib.rs +++ b/crates/key-value-azure/src/lib.rs @@ -1,233 +1,60 @@ -use std::sync::Arc; +mod store; -use anyhow::Result; -use azure_data_cosmos::{ - prelude::{AuthorizationToken, CollectionClient, CosmosClient, Query}, - CosmosEntity, +use serde::Deserialize; +use spin_factor_key_value::runtime_config::spin::MakeKeyValueStore; +use store::{ + KeyValueAzureCosmos, KeyValueAzureCosmosAuthOptions, KeyValueAzureCosmosRuntimeConfigOptions, }; -use futures::StreamExt; -use serde::{Deserialize, Serialize}; -use spin_core::async_trait; -use spin_key_value::{log_error, Error, Store, StoreManager}; -use tracing::{instrument, Level}; -pub struct KeyValueAzureCosmos { - client: CollectionClient, +/// A key-value store that uses Azure Cosmos as the backend. +#[derive(Default)] +pub struct AzureKeyValueStore { + _priv: (), } -/// Azure Cosmos Key / Value runtime config literal options for authentication -#[derive(Clone, Debug)] -pub struct KeyValueAzureCosmosRuntimeConfigOptions { - key: String, -} - -impl KeyValueAzureCosmosRuntimeConfigOptions { - pub fn new(key: String) -> Self { - Self { key } +impl AzureKeyValueStore { + /// Creates a new `AzureKeyValueStore`. + pub fn new() -> Self { + Self::default() } } -/// Azure Cosmos Key / Value enumeration for the possible authentication options -#[derive(Clone, Debug)] -pub enum KeyValueAzureCosmosAuthOptions { - /// Runtime Config values indicates the account and key have been specified directly - RuntimeConfigValues(KeyValueAzureCosmosRuntimeConfigOptions), - /// Environmental indicates that the environment variables of the process should be used to - /// create the TokenCredential for the Cosmos client. This will use the Azure Rust SDK's - /// DefaultCredentialChain to derive the TokenCredential based on what environment variables - /// have been set. - /// - /// Service Principal with client secret: - /// - `AZURE_TENANT_ID`: ID of the service principal's Azure tenant. - /// - `AZURE_CLIENT_ID`: the service principal's client ID. - /// - `AZURE_CLIENT_SECRET`: one of the service principal's secrets. - /// - /// Service Principal with certificate: - /// - `AZURE_TENANT_ID`: ID of the service principal's Azure tenant. - /// - `AZURE_CLIENT_ID`: the service principal's client ID. - /// - `AZURE_CLIENT_CERTIFICATE_PATH`: path to a PEM or PKCS12 certificate file including the private key. - /// - `AZURE_CLIENT_CERTIFICATE_PASSWORD`: (optional) password for the certificate file. - /// - /// Workload Identity (Kubernetes, injected by the Workload Identity mutating webhook): - /// - `AZURE_TENANT_ID`: ID of the service principal's Azure tenant. - /// - `AZURE_CLIENT_ID`: the service principal's client ID. - /// - `AZURE_FEDERATED_TOKEN_FILE`: TokenFilePath is the path of a file containing a Kubernetes service account token. - /// - /// Managed Identity (User Assigned or System Assigned identities): - /// - `AZURE_CLIENT_ID`: (optional) if using a user assigned identity, this will be the client ID of the identity. - /// - /// Azure CLI: - /// - `AZURE_TENANT_ID`: (optional) use a specific tenant via the Azure CLI. - /// - /// Common across each: - /// - `AZURE_AUTHORITY_HOST`: (optional) the host for the identity provider. For example, for Azure public cloud the host defaults to "https://login.microsoftonline.com". - /// See also: https://github.com/Azure/azure-sdk-for-rust/blob/main/sdk/identity/README.md - Environmental, +/// Runtime configuration for the Azure Cosmos key-value store. +#[derive(Deserialize)] +pub struct AzureCosmosKeyValueRuntimeConfig { + /// The authorization token for the Azure Cosmos DB account. + key: Option, + /// The Azure Cosmos DB account name. + account: String, + /// The Azure Cosmos DB database. + database: String, + /// The Azure Cosmos DB container where data is stored. + /// The CosmosDB container must be created with the default partition key, /id + container: String, } -impl KeyValueAzureCosmos { - pub fn new( - account: String, - database: String, - container: String, - auth_options: KeyValueAzureCosmosAuthOptions, - ) -> Result { - let token = match auth_options { - KeyValueAzureCosmosAuthOptions::RuntimeConfigValues(config) => { - AuthorizationToken::primary_key(config.key).map_err(log_error)? - } - KeyValueAzureCosmosAuthOptions::Environmental => { - AuthorizationToken::from_token_credential( - azure_identity::create_default_credential()?, - ) - } - }; - let cosmos_client = CosmosClient::new(account, token); - let database_client = cosmos_client.database_client(database); - let client = database_client.collection_client(container); - - Ok(Self { client }) - } -} - -#[async_trait] -impl StoreManager for KeyValueAzureCosmos { - async fn get(&self, name: &str) -> Result, Error> { - Ok(Arc::new(AzureCosmosStore { - _name: name.to_owned(), - client: self.client.clone(), - })) - } - - fn is_defined(&self, _store_name: &str) -> bool { - true - } +impl MakeKeyValueStore for AzureKeyValueStore { + const RUNTIME_CONFIG_TYPE: &'static str = "azure_cosmos"; - fn summary(&self, _store_name: &str) -> Option { - let database = self.client.database_client().database_name(); - let collection = self.client.collection_name(); - Some(format!( - "Azure CosmosDB database: {database}, collection: {collection}" - )) - } -} + type RuntimeConfig = AzureCosmosKeyValueRuntimeConfig; -struct AzureCosmosStore { - _name: String, - client: CollectionClient, -} + type StoreManager = KeyValueAzureCosmos; -#[async_trait] -impl Store for AzureCosmosStore { - #[instrument(name = "spin_key_value_azure.get", skip(self), err(level = Level::INFO), fields( - otel.kind = "client" - ))] - async fn get(&self, key: &str) -> Result>, Error> { - let pair = self.get_pair(key).await?; - Ok(pair.map(|p| p.value)) - } - - #[instrument( - name = "spin_key_value_azure.set", - skip(self, value), - err(level = Level::INFO), - fields(otel.kind = "client") - )] - async fn set(&self, key: &str, value: &[u8]) -> Result<(), Error> { - let pair = Pair { - id: key.to_string(), - value: value.to_vec(), + fn make_store( + &self, + runtime_config: Self::RuntimeConfig, + ) -> anyhow::Result { + let auth_options = match runtime_config.key { + Some(key) => KeyValueAzureCosmosAuthOptions::RuntimeConfigValues( + KeyValueAzureCosmosRuntimeConfigOptions::new(key), + ), + None => KeyValueAzureCosmosAuthOptions::Environmental, }; - self.client - .create_document(pair) - .is_upsert(true) - .await - .map_err(log_error)?; - Ok(()) - } - - #[instrument(name = "spin_key_value_azure.delete", skip(self), err(level = Level::INFO), fields( - otel.kind = "client" - ))] - async fn delete(&self, key: &str) -> Result<(), Error> { - if self.exists(key).await? { - let document_client = self.client.document_client(key, &key).map_err(log_error)?; - document_client.delete_document().await.map_err(log_error)?; - } - Ok(()) - } - - #[instrument(name = "spin_key_value_azure.exists", skip(self), err(level = Level::INFO), fields( - otel.kind = "client" - ))] - async fn exists(&self, key: &str) -> Result { - Ok(self.get_pair(key).await?.is_some()) - } - - #[instrument( - name = "spin_key_value_azure.get_keys", - skip(self), - err(level = Level::INFO), - fields(otel.kind = "client") - )] - async fn get_keys(&self) -> Result, Error> { - self.get_keys().await - } -} - -impl AzureCosmosStore { - async fn get_pair(&self, key: &str) -> Result, Error> { - let query = self - .client - .query_documents(Query::new(format!("SELECT * FROM c WHERE c.id='{}'", key))) - .query_cross_partition(true) - .max_item_count(1); - - // There can be no duplicated keys, so we create the stream and only take the first result. - let mut stream = query.into_stream::(); - let res = stream.next().await; - match res { - Some(r) => { - let r = r.map_err(log_error)?; - match r.results.first().cloned() { - Some((p, _)) => Ok(Some(p)), - None => Ok(None), - } - } - None => Ok(None), - } - } - - async fn get_keys(&self) -> Result, Error> { - let query = self - .client - .query_documents(Query::new("SELECT * FROM c".to_string())) - .query_cross_partition(true); - let mut res = Vec::new(); - - let mut stream = query.into_stream::(); - while let Some(resp) = stream.next().await { - let resp = resp.map_err(log_error)?; - for (pair, _) in resp.results { - res.push(pair.id); - } - } - - Ok(res) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct Pair { - // In Azure CosmosDB, the default partition key is "/id", and this implementation assumes that partition ID is not changed. - pub id: String, - pub value: Vec, -} - -impl CosmosEntity for Pair { - type Entity = String; - - fn partition_key(&self) -> Self::Entity { - self.id.clone() + KeyValueAzureCosmos::new( + runtime_config.account, + runtime_config.database, + runtime_config.container, + auth_options, + ) } } diff --git a/crates/key-value-azure/src/store.rs b/crates/key-value-azure/src/store.rs new file mode 100644 index 0000000000..955c1c0e7c --- /dev/null +++ b/crates/key-value-azure/src/store.rs @@ -0,0 +1,233 @@ +use std::sync::Arc; + +use anyhow::Result; +use azure_data_cosmos::{ + prelude::{AuthorizationToken, CollectionClient, CosmosClient, Query}, + CosmosEntity, +}; +use futures::StreamExt; +use serde::{Deserialize, Serialize}; +use spin_core::async_trait; +use spin_factor_key_value::{log_error, Error, Store, StoreManager}; +use tracing::{instrument, Level}; + +pub struct KeyValueAzureCosmos { + client: CollectionClient, +} + +/// Azure Cosmos Key / Value runtime config literal options for authentication +#[derive(Clone, Debug)] +pub struct KeyValueAzureCosmosRuntimeConfigOptions { + key: String, +} + +impl KeyValueAzureCosmosRuntimeConfigOptions { + pub fn new(key: String) -> Self { + Self { key } + } +} + +/// Azure Cosmos Key / Value enumeration for the possible authentication options +#[derive(Clone, Debug)] +pub enum KeyValueAzureCosmosAuthOptions { + /// Runtime Config values indicates the account and key have been specified directly + RuntimeConfigValues(KeyValueAzureCosmosRuntimeConfigOptions), + /// Environmental indicates that the environment variables of the process should be used to + /// create the TokenCredential for the Cosmos client. This will use the Azure Rust SDK's + /// DefaultCredentialChain to derive the TokenCredential based on what environment variables + /// have been set. + /// + /// Service Principal with client secret: + /// - `AZURE_TENANT_ID`: ID of the service principal's Azure tenant. + /// - `AZURE_CLIENT_ID`: the service principal's client ID. + /// - `AZURE_CLIENT_SECRET`: one of the service principal's secrets. + /// + /// Service Principal with certificate: + /// - `AZURE_TENANT_ID`: ID of the service principal's Azure tenant. + /// - `AZURE_CLIENT_ID`: the service principal's client ID. + /// - `AZURE_CLIENT_CERTIFICATE_PATH`: path to a PEM or PKCS12 certificate file including the private key. + /// - `AZURE_CLIENT_CERTIFICATE_PASSWORD`: (optional) password for the certificate file. + /// + /// Workload Identity (Kubernetes, injected by the Workload Identity mutating webhook): + /// - `AZURE_TENANT_ID`: ID of the service principal's Azure tenant. + /// - `AZURE_CLIENT_ID`: the service principal's client ID. + /// - `AZURE_FEDERATED_TOKEN_FILE`: TokenFilePath is the path of a file containing a Kubernetes service account token. + /// + /// Managed Identity (User Assigned or System Assigned identities): + /// - `AZURE_CLIENT_ID`: (optional) if using a user assigned identity, this will be the client ID of the identity. + /// + /// Azure CLI: + /// - `AZURE_TENANT_ID`: (optional) use a specific tenant via the Azure CLI. + /// + /// Common across each: + /// - `AZURE_AUTHORITY_HOST`: (optional) the host for the identity provider. For example, for Azure public cloud the host defaults to "https://login.microsoftonline.com". + /// See also: https://github.com/Azure/azure-sdk-for-rust/blob/main/sdk/identity/README.md + Environmental, +} + +impl KeyValueAzureCosmos { + pub fn new( + account: String, + database: String, + container: String, + auth_options: KeyValueAzureCosmosAuthOptions, + ) -> Result { + let token = match auth_options { + KeyValueAzureCosmosAuthOptions::RuntimeConfigValues(config) => { + AuthorizationToken::primary_key(config.key).map_err(log_error)? + } + KeyValueAzureCosmosAuthOptions::Environmental => { + AuthorizationToken::from_token_credential( + azure_identity::create_default_credential()?, + ) + } + }; + let cosmos_client = CosmosClient::new(account, token); + let database_client = cosmos_client.database_client(database); + let client = database_client.collection_client(container); + + Ok(Self { client }) + } +} + +#[async_trait] +impl StoreManager for KeyValueAzureCosmos { + async fn get(&self, name: &str) -> Result, Error> { + Ok(Arc::new(AzureCosmosStore { + _name: name.to_owned(), + client: self.client.clone(), + })) + } + + fn is_defined(&self, _store_name: &str) -> bool { + true + } + + fn summary(&self, _store_name: &str) -> Option { + let database = self.client.database_client().database_name(); + let collection = self.client.collection_name(); + Some(format!( + "Azure CosmosDB database: {database}, collection: {collection}" + )) + } +} + +struct AzureCosmosStore { + _name: String, + client: CollectionClient, +} + +#[async_trait] +impl Store for AzureCosmosStore { + #[instrument(name = "spin_key_value_azure.get", skip(self), err(level = Level::INFO), fields( + otel.kind = "client" + ))] + async fn get(&self, key: &str) -> Result>, Error> { + let pair = self.get_pair(key).await?; + Ok(pair.map(|p| p.value)) + } + + #[instrument( + name = "spin_key_value_azure.set", + skip(self, value), + err(level = Level::INFO), + fields(otel.kind = "client") + )] + async fn set(&self, key: &str, value: &[u8]) -> Result<(), Error> { + let pair = Pair { + id: key.to_string(), + value: value.to_vec(), + }; + self.client + .create_document(pair) + .is_upsert(true) + .await + .map_err(log_error)?; + Ok(()) + } + + #[instrument(name = "spin_key_value_azure.delete", skip(self), err(level = Level::INFO), fields( + otel.kind = "client" + ))] + async fn delete(&self, key: &str) -> Result<(), Error> { + if self.exists(key).await? { + let document_client = self.client.document_client(key, &key).map_err(log_error)?; + document_client.delete_document().await.map_err(log_error)?; + } + Ok(()) + } + + #[instrument(name = "spin_key_value_azure.exists", skip(self), err(level = Level::INFO), fields( + otel.kind = "client" + ))] + async fn exists(&self, key: &str) -> Result { + Ok(self.get_pair(key).await?.is_some()) + } + + #[instrument( + name = "spin_key_value_azure.get_keys", + skip(self), + err(level = Level::INFO), + fields(otel.kind = "client") + )] + async fn get_keys(&self) -> Result, Error> { + self.get_keys().await + } +} + +impl AzureCosmosStore { + async fn get_pair(&self, key: &str) -> Result, Error> { + let query = self + .client + .query_documents(Query::new(format!("SELECT * FROM c WHERE c.id='{}'", key))) + .query_cross_partition(true) + .max_item_count(1); + + // There can be no duplicated keys, so we create the stream and only take the first result. + let mut stream = query.into_stream::(); + let res = stream.next().await; + match res { + Some(r) => { + let r = r.map_err(log_error)?; + match r.results.first().cloned() { + Some((p, _)) => Ok(Some(p)), + None => Ok(None), + } + } + None => Ok(None), + } + } + + async fn get_keys(&self) -> Result, Error> { + let query = self + .client + .query_documents(Query::new("SELECT * FROM c".to_string())) + .query_cross_partition(true); + let mut res = Vec::new(); + + let mut stream = query.into_stream::(); + while let Some(resp) = stream.next().await { + let resp = resp.map_err(log_error)?; + for (pair, _) in resp.results { + res.push(pair.id); + } + } + + Ok(res) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Pair { + // In Azure CosmosDB, the default partition key is "/id", and this implementation assumes that partition ID is not changed. + pub id: String, + pub value: Vec, +} + +impl CosmosEntity for Pair { + type Entity = String; + + fn partition_key(&self) -> Self::Entity { + self.id.clone() + } +} diff --git a/crates/key-value-redis/Cargo.toml b/crates/key-value-redis/Cargo.toml index baf71731f5..88e2f488bf 100644 --- a/crates/key-value-redis/Cargo.toml +++ b/crates/key-value-redis/Cargo.toml @@ -5,10 +5,11 @@ authors = { workspace = true } edition = { workspace = true } [dependencies] -anyhow = "1" +anyhow = "1.0" redis = { version = "0.21", features = ["tokio-comp", "tokio-native-tls-comp"] } +serde = { version = "1.0", features = ["rc"] } spin-core = { path = "../core" } -spin-key-value = { path = "../key-value" } +spin-factor-key-value = { path = "../factor-key-value" } spin-world = { path = "../world" } tokio = "1" tracing = { workspace = true } diff --git a/crates/key-value-redis/src/lib.rs b/crates/key-value-redis/src/lib.rs index 1dbb378e14..7d4064e35b 100644 --- a/crates/key-value-redis/src/lib.rs +++ b/crates/key-value-redis/src/lib.rs @@ -1,108 +1,40 @@ -use anyhow::{Context, Result}; -use redis::{aio::Connection, parse_redis_url, AsyncCommands}; -use spin_core::async_trait; -use spin_key_value::{log_error, Error, Store, StoreManager}; -use std::sync::Arc; -use tokio::sync::{Mutex, OnceCell}; -use tracing::{instrument, Level}; -use url::Url; +mod store; -pub struct KeyValueRedis { - database_url: Url, - connection: OnceCell>>, -} - -impl KeyValueRedis { - pub fn new(address: String) -> Result { - let database_url = parse_redis_url(&address).context("Invalid Redis URL")?; +use serde::Deserialize; +use spin_factor_key_value::runtime_config::spin::MakeKeyValueStore; +use store::KeyValueRedis; - Ok(Self { - database_url, - connection: OnceCell::new(), - }) - } +/// A key-value store that uses Redis as the backend. +#[derive(Default)] +pub struct RedisKeyValueStore { + _priv: (), } -#[async_trait] -impl StoreManager for KeyValueRedis { - #[instrument(name = "spin_key_value_redis.get_store", skip(self), err(level = Level::INFO), fields(otel.kind = "client"))] - async fn get(&self, _name: &str) -> Result, Error> { - let connection = self - .connection - .get_or_try_init(|| async { - redis::Client::open(self.database_url.clone())? - .get_async_connection() - .await - .map(Mutex::new) - .map(Arc::new) - }) - .await - .map_err(log_error)?; - - Ok(Arc::new(RedisStore { - connection: connection.clone(), - })) - } - - fn is_defined(&self, _store_name: &str) -> bool { - true - } - - fn summary(&self, _store_name: &str) -> Option { - let redis::ConnectionInfo { addr, .. } = self.database_url.as_str().parse().ok()?; - Some(format!("Redis at {addr}")) +impl RedisKeyValueStore { + /// Creates a new `RedisKeyValueStore`. + pub fn new() -> Self { + Self::default() } } -struct RedisStore { - connection: Arc>, +/// Runtime configuration for the Redis key-value store. +#[derive(Deserialize)] +pub struct RedisKeyValueRuntimeConfig { + /// The URL of the Redis server. + url: String, } -#[async_trait] -impl Store for RedisStore { - #[instrument(name = "spin_key_value_redis.get", skip(self), err(level = Level::INFO), fields(otel.kind = "client"))] - async fn get(&self, key: &str) -> Result>, Error> { - let mut conn = self.connection.lock().await; - conn.get(key).await.map_err(log_error) - } - - #[instrument(name = "spin_key_value_redis.set", skip(self, value), err(level = Level::INFO), fields(otel.kind = "client"))] - async fn set(&self, key: &str, value: &[u8]) -> Result<(), Error> { - self.connection - .lock() - .await - .set(key, value) - .await - .map_err(log_error) - } +impl MakeKeyValueStore for RedisKeyValueStore { + const RUNTIME_CONFIG_TYPE: &'static str = "redis"; - #[instrument(name = "spin_key_value_redis.delete", skip(self), err(level = Level::INFO), fields(otel.kind = "client"))] - async fn delete(&self, key: &str) -> Result<(), Error> { - self.connection - .lock() - .await - .del(key) - .await - .map_err(log_error) - } + type RuntimeConfig = RedisKeyValueRuntimeConfig; - #[instrument(name = "spin_key_value_redis.exists", skip(self), err(level = Level::INFO), fields(otel.kind = "client"))] - async fn exists(&self, key: &str) -> Result { - self.connection - .lock() - .await - .exists(key) - .await - .map_err(log_error) - } + type StoreManager = KeyValueRedis; - #[instrument(name = "spin_key_value_redis.get_keys", skip(self), err(level = Level::INFO), fields(otel.kind = "client"))] - async fn get_keys(&self) -> Result, Error> { - self.connection - .lock() - .await - .keys("*") - .await - .map_err(log_error) + fn make_store( + &self, + runtime_config: Self::RuntimeConfig, + ) -> anyhow::Result { + KeyValueRedis::new(runtime_config.url) } } diff --git a/crates/key-value-redis/src/store.rs b/crates/key-value-redis/src/store.rs new file mode 100644 index 0000000000..76bb20da13 --- /dev/null +++ b/crates/key-value-redis/src/store.rs @@ -0,0 +1,108 @@ +use anyhow::{Context, Result}; +use redis::{aio::Connection, parse_redis_url, AsyncCommands}; +use spin_core::async_trait; +use spin_factor_key_value::{log_error, Error, Store, StoreManager}; +use std::sync::Arc; +use tokio::sync::{Mutex, OnceCell}; +use tracing::{instrument, Level}; +use url::Url; + +pub struct KeyValueRedis { + database_url: Url, + connection: OnceCell>>, +} + +impl KeyValueRedis { + pub fn new(address: String) -> Result { + let database_url = parse_redis_url(&address).context("Invalid Redis URL")?; + + Ok(Self { + database_url, + connection: OnceCell::new(), + }) + } +} + +#[async_trait] +impl StoreManager for KeyValueRedis { + #[instrument(name = "spin_key_value_redis.get_store", skip(self), err(level = Level::INFO), fields(otel.kind = "client"))] + async fn get(&self, _name: &str) -> Result, Error> { + let connection = self + .connection + .get_or_try_init(|| async { + redis::Client::open(self.database_url.clone())? + .get_async_connection() + .await + .map(Mutex::new) + .map(Arc::new) + }) + .await + .map_err(log_error)?; + + Ok(Arc::new(RedisStore { + connection: connection.clone(), + })) + } + + fn is_defined(&self, _store_name: &str) -> bool { + true + } + + fn summary(&self, _store_name: &str) -> Option { + let redis::ConnectionInfo { addr, .. } = self.database_url.as_str().parse().ok()?; + Some(format!("Redis at {addr}")) + } +} + +struct RedisStore { + connection: Arc>, +} + +#[async_trait] +impl Store for RedisStore { + #[instrument(name = "spin_key_value_redis.get", skip(self), err(level = Level::INFO), fields(otel.kind = "client"))] + async fn get(&self, key: &str) -> Result>, Error> { + let mut conn = self.connection.lock().await; + conn.get(key).await.map_err(log_error) + } + + #[instrument(name = "spin_key_value_redis.set", skip(self, value), err(level = Level::INFO), fields(otel.kind = "client"))] + async fn set(&self, key: &str, value: &[u8]) -> Result<(), Error> { + self.connection + .lock() + .await + .set(key, value) + .await + .map_err(log_error) + } + + #[instrument(name = "spin_key_value_redis.delete", skip(self), err(level = Level::INFO), fields(otel.kind = "client"))] + async fn delete(&self, key: &str) -> Result<(), Error> { + self.connection + .lock() + .await + .del(key) + .await + .map_err(log_error) + } + + #[instrument(name = "spin_key_value_redis.exists", skip(self), err(level = Level::INFO), fields(otel.kind = "client"))] + async fn exists(&self, key: &str) -> Result { + self.connection + .lock() + .await + .exists(key) + .await + .map_err(log_error) + } + + #[instrument(name = "spin_key_value_redis.get_keys", skip(self), err(level = Level::INFO), fields(otel.kind = "client"))] + async fn get_keys(&self) -> Result, Error> { + self.connection + .lock() + .await + .keys("*") + .await + .map_err(log_error) + } +} diff --git a/crates/key-value-sqlite/Cargo.toml b/crates/key-value-spin/Cargo.toml similarity index 71% rename from crates/key-value-sqlite/Cargo.toml rename to crates/key-value-spin/Cargo.toml index ba7a89c29a..bd1d4470bf 100644 --- a/crates/key-value-sqlite/Cargo.toml +++ b/crates/key-value-spin/Cargo.toml @@ -1,15 +1,16 @@ [package] -name = "spin-key-value-sqlite" +name = "spin-key-value-spin" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } [dependencies] -anyhow = "1" +anyhow = "1.0" once_cell = "1" rusqlite = { version = "0.29.0", features = ["bundled"] } +serde = { version = "1.0", features = ["rc"] } spin-core = { path = "../core" } -spin-key-value = { path = "../key-value" } +spin-factor-key-value = { path = "../factor-key-value" } spin-world = { path = "../world" } tokio = { version = "1", features = ["rt-multi-thread"] } tracing = { workspace = true } diff --git a/crates/factor-key-value-spin/src/lib.rs b/crates/key-value-spin/src/lib.rs similarity index 97% rename from crates/factor-key-value-spin/src/lib.rs rename to crates/key-value-spin/src/lib.rs index 45d8a7fc71..1778c7af95 100644 --- a/crates/factor-key-value-spin/src/lib.rs +++ b/crates/key-value-spin/src/lib.rs @@ -1,3 +1,5 @@ +mod store; + use std::{ fs, path::{Path, PathBuf}, @@ -6,7 +8,7 @@ use std::{ use anyhow::Context as _; use serde::{Deserialize, Serialize}; use spin_factor_key_value::runtime_config::spin::MakeKeyValueStore; -use spin_key_value_sqlite::{DatabaseLocation, KeyValueSqlite}; +use store::{DatabaseLocation, KeyValueSqlite}; /// A key-value store that uses SQLite as the backend. pub struct SpinKeyValueStore { diff --git a/crates/key-value-sqlite/src/lib.rs b/crates/key-value-spin/src/store.rs similarity index 98% rename from crates/key-value-sqlite/src/lib.rs rename to crates/key-value-spin/src/store.rs index e890c06def..f38d328274 100644 --- a/crates/key-value-sqlite/src/lib.rs +++ b/crates/key-value-spin/src/store.rs @@ -2,7 +2,7 @@ use anyhow::Result; use once_cell::sync::OnceCell; use rusqlite::Connection; use spin_core::async_trait; -use spin_key_value::{log_error, Error, Store, StoreManager}; +use spin_factor_key_value::{log_error, Error, Store, StoreManager}; use std::{ path::PathBuf, sync::{Arc, Mutex}, @@ -161,7 +161,7 @@ impl Store for SqliteStore { mod test { use super::*; use spin_core::wasmtime::component::Resource; - use spin_key_value::{DelegatingStoreManager, KeyValueDispatch}; + use spin_factor_key_value::{DelegatingStoreManager, KeyValueDispatch}; use spin_world::v2::key_value::HostStore; #[tokio::test(flavor = "multi_thread", worker_threads = 1)] diff --git a/crates/key-value/Cargo.toml b/crates/key-value/Cargo.toml deleted file mode 100644 index 0d13e95c71..0000000000 --- a/crates/key-value/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "spin-key-value" -version = { workspace = true } -authors = { workspace = true } -edition = { workspace = true } - -[lib] -doctest = false - -[dependencies] -anyhow = "1.0" -lru = "0.9.0" -spin-app = { path = "../app" } -spin-core = { path = "../core" } -spin-world = { path = "../world" } -table = { path = "../table" } -tokio = { version = "1", features = ["macros", "sync", "rt"] } -tracing = { workspace = true } diff --git a/crates/key-value/src/host_component.rs b/crates/key-value/src/host_component.rs deleted file mode 100644 index 2126e5ceec..0000000000 --- a/crates/key-value/src/host_component.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::{KeyValueDispatch, StoreManager, KEY_VALUE_STORES_KEY}; -use anyhow::anyhow; -use spin_app::{AppComponent, DynamicHostComponent}; -use spin_core::HostComponent; -use std::sync::Arc; - -pub trait StoreManagerManager: Sync + Send { - fn get(&self, component: &AppComponent) -> Arc; -} - -impl Arc) + Sync + Send> StoreManagerManager for F { - fn get(&self, component: &AppComponent) -> Arc { - self(component) - } -} - -/// Help the rustc type inference engine understand that the specified closure has a higher-order bound so it can -/// be used as a [`StoreManagerManager`]. -/// -/// See https://stackoverflow.com/a/46198877 for details. -pub fn manager Fn(&'a AppComponent) -> Arc>(f: F) -> F { - f -} - -pub struct KeyValueComponent { - capacity: u32, - manager: Box, -} - -impl KeyValueComponent { - pub fn new(manager: impl StoreManagerManager + 'static) -> Self { - Self::new_with_capacity(u32::MAX, manager) - } - - pub fn new_with_capacity(capacity: u32, manager: impl StoreManagerManager + 'static) -> Self { - Self { - capacity, - manager: Box::new(manager), - } - } -} - -impl HostComponent for KeyValueComponent { - type Data = KeyValueDispatch; - - fn add_to_linker( - linker: &mut spin_core::Linker, - get: impl Fn(&mut spin_core::Data) -> &mut Self::Data + Send + Sync + Copy + 'static, - ) -> anyhow::Result<()> { - super::key_value::add_to_linker(linker, get)?; - spin_world::v1::key_value::add_to_linker(linker, get) - } - - fn build_data(&self) -> Self::Data { - KeyValueDispatch::new_with_capacity(self.capacity) - } -} - -impl DynamicHostComponent for KeyValueComponent { - fn update_data(&self, data: &mut Self::Data, component: &AppComponent) -> anyhow::Result<()> { - let key_value_stores = component - .get_metadata(crate::KEY_VALUE_STORES_KEY)? - .unwrap_or_default(); - data.init( - key_value_stores.into_iter().collect(), - self.manager.get(component), - ); - - Ok(()) - } - - fn validate_app(&self, app: &spin_app::App) -> anyhow::Result<()> { - let mut errors = vec![]; - - for component in app.components() { - let store_manager = self.manager.get(&component); - for allowed in component - .get_metadata(KEY_VALUE_STORES_KEY)? - .unwrap_or_default() - { - if !store_manager.is_defined(&allowed) { - let err = format!("- Component {} uses store '{allowed}'", component.id()); - errors.push(err); - } - } - } - - if errors.is_empty() { - Ok(()) - } else { - let prologue = vec![ - "One or more components use key-value stores which are not defined.", - "Check the spelling, or pass a runtime configuration file that defines these stores.", - "See https://developer.fermyon.com/spin/dynamic-configuration#key-value-store-runtime-configuration", - "Details:", - ]; - let lines: Vec<_> = prologue - .into_iter() - .map(|s| s.to_owned()) - .chain(errors) - .collect(); - Err(anyhow!(lines.join("\n"))) - } - } -} diff --git a/crates/runtime-config/Cargo.toml b/crates/runtime-config/Cargo.toml index 6efac61b5a..eacfb2c593 100644 --- a/crates/runtime-config/Cargo.toml +++ b/crates/runtime-config/Cargo.toml @@ -11,9 +11,9 @@ rust-version.workspace = true [dependencies] anyhow = { workspace = true } spin-factor-key-value = { path = "../factor-key-value" } -spin-factor-key-value-azure = { path = "../factor-key-value-azure" } -spin-factor-key-value-redis = { path = "../factor-key-value-redis" } -spin-factor-key-value-spin = { path = "../factor-key-value-spin" } +spin-key-value-azure = { path = "../key-value-azure" } +spin-key-value-redis = { path = "../key-value-redis" } +spin-key-value-spin = { path = "../key-value-spin" } spin-factor-llm = { path = "../factor-llm" } spin-factor-outbound-http = { path = "../factor-outbound-http" } spin-factor-outbound-mqtt = { path = "../factor-outbound-mqtt" } diff --git a/crates/runtime-config/src/lib.rs b/crates/runtime-config/src/lib.rs index d1c9742388..9967ed491f 100644 --- a/crates/runtime-config/src/lib.rs +++ b/crates/runtime-config/src/lib.rs @@ -3,7 +3,6 @@ use std::path::{Path, PathBuf}; use anyhow::Context as _; use spin_factor_key_value::runtime_config::spin::{self as key_value}; use spin_factor_key_value::{DefaultLabelResolver as _, KeyValueFactor}; -use spin_factor_key_value_spin::{SpinKeyValueRuntimeConfig, SpinKeyValueStore}; use spin_factor_llm::{spin as llm, LlmFactor}; use spin_factor_outbound_http::OutboundHttpFactor; use spin_factor_outbound_mqtt::OutboundMqttFactor; @@ -19,6 +18,7 @@ use spin_factors::runtime_config::toml::GetTomlValue as _; use spin_factors::{ runtime_config::toml::TomlKeyTracker, FactorRuntimeConfigSource, RuntimeConfigSourceFinalizer, }; +use spin_key_value_spin::{SpinKeyValueRuntimeConfig, SpinKeyValueStore}; use spin_sqlite as sqlite; /// The default state directory for the trigger. @@ -411,15 +411,15 @@ pub fn key_value_config_resolver( // Register the supported store types. // Unwraps are safe because the store types are known to not overlap. key_value - .register_store_type(spin_factor_key_value_spin::SpinKeyValueStore::new( + .register_store_type(spin_key_value_spin::SpinKeyValueStore::new( local_store_base_path.clone(), )) .unwrap(); key_value - .register_store_type(spin_factor_key_value_redis::RedisKeyValueStore::new()) + .register_store_type(spin_key_value_redis::RedisKeyValueStore::new()) .unwrap(); key_value - .register_store_type(spin_factor_key_value_azure::AzureKeyValueStore::new()) + .register_store_type(spin_key_value_azure::AzureKeyValueStore::new()) .unwrap(); // Add handling of "default" store. diff --git a/examples/spin-timer/Cargo.lock b/examples/spin-timer/Cargo.lock index 7d7e3f3b2c..2410c36db6 100644 --- a/examples/spin-timer/Cargo.lock +++ b/examples/spin-timer/Cargo.lock @@ -3774,41 +3774,16 @@ name = "spin-factor-key-value" version = "2.8.0-pre0" dependencies = [ "anyhow", + "lru 0.9.0", "serde", + "spin-core", "spin-factors", - "spin-key-value", + "spin-locked-app", "spin-world", + "table", + "tokio", "toml", -] - -[[package]] -name = "spin-factor-key-value-azure" -version = "2.8.0-pre0" -dependencies = [ - "anyhow", - "serde", - "spin-factor-key-value", - "spin-key-value-azure", -] - -[[package]] -name = "spin-factor-key-value-redis" -version = "2.8.0-pre0" -dependencies = [ - "anyhow", - "serde", - "spin-factor-key-value", - "spin-key-value-redis", -] - -[[package]] -name = "spin-factor-key-value-spin" -version = "2.8.0-pre0" -dependencies = [ - "anyhow", - "serde", - "spin-factor-key-value", - "spin-key-value-sqlite", + "tracing", ] [[package]] @@ -4018,20 +3993,6 @@ dependencies = [ "spin-factors", ] -[[package]] -name = "spin-key-value" -version = "2.8.0-pre0" -dependencies = [ - "anyhow", - "lru 0.9.0", - "spin-app", - "spin-core", - "spin-world", - "table", - "tokio", - "tracing", -] - [[package]] name = "spin-key-value-azure" version = "2.8.0-pre0" @@ -4042,7 +4003,7 @@ dependencies = [ "futures", "serde", "spin-core", - "spin-key-value", + "spin-factor-key-value", "tokio", "tracing", "url", @@ -4054,8 +4015,9 @@ version = "2.8.0-pre0" dependencies = [ "anyhow", "redis", + "serde", "spin-core", - "spin-key-value", + "spin-factor-key-value", "spin-world", "tokio", "tracing", @@ -4063,14 +4025,15 @@ dependencies = [ ] [[package]] -name = "spin-key-value-sqlite" +name = "spin-key-value-spin" version = "2.8.0-pre0" dependencies = [ "anyhow", "once_cell", "rusqlite", + "serde", "spin-core", - "spin-key-value", + "spin-factor-key-value", "spin-world", "tokio", "tracing", @@ -4122,9 +4085,6 @@ version = "2.8.0-pre0" dependencies = [ "anyhow", "spin-factor-key-value", - "spin-factor-key-value-azure", - "spin-factor-key-value-redis", - "spin-factor-key-value-spin", "spin-factor-llm", "spin-factor-outbound-http", "spin-factor-outbound-mqtt", @@ -4136,6 +4096,9 @@ dependencies = [ "spin-factor-variables", "spin-factor-wasi", "spin-factors", + "spin-key-value-azure", + "spin-key-value-redis", + "spin-key-value-spin", "spin-sqlite", "toml", ] diff --git a/tests/test-components/components/Cargo.lock b/tests/test-components/components/Cargo.lock index 928df83f2d..77d634f50d 100644 --- a/tests/test-components/components/Cargo.lock +++ b/tests/test-components/components/Cargo.lock @@ -24,7 +24,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.74", ] [[package]] @@ -166,7 +166,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.74", ] [[package]] @@ -464,7 +464,7 @@ checksum = "51c55587ac25c2d63a75e4171221b2803a9b9e83aeb7bdfde5833c4cd578b50d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.74", ] [[package]] @@ -545,9 +545,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -600,7 +600,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.74", ] [[package]] @@ -788,9 +788,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" dependencies = [ "proc-macro2", "quote", @@ -822,7 +822,7 @@ checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.74", ] [[package]] @@ -1089,7 +1089,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.74", "wit-bindgen-core 0.13.1", "wit-bindgen-rust 0.13.2", "wit-component 0.17.0", @@ -1104,7 +1104,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.74", "wit-bindgen-core 0.16.0", "wit-bindgen-rust 0.16.0", "wit-component 0.18.2",