Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions crates/edgezero-adapter-fastly/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ const FASTLY_INSTALL_HINT: &str = "install the Fastly CLI (https://www.fastly.co

/// The config store the runtime opens for `EDGEZERO__*` overrides. Compute@Edge
/// has no process env, so the runtime reads its config-store KEY selector from
/// here (see `env_config_from_runtime_dictionary` in lib.rs).
/// here (see `runtime_env_config` in lib.rs).
const RUNTIME_ENV_STORE: &str = "edgezero_runtime_env";

/// Base name of the staging twin of [`RUNTIME_ENV_STORE`]. The actual store is
Expand Down Expand Up @@ -546,12 +546,11 @@ impl Adapter for FastlyCliAdapter {
// Store named `edgezero_runtime_env`. Compute@Edge has no
// process env, so `EDGEZERO__STORES__CONFIG__<ID>__KEY` and
// similar overrides have to come from a platform Config Store
// the runtime opens by name (see
// `env_config_from_runtime_dictionary` in lib.rs). Provision
// owns the store creation alongside the operator's declared
// stores so the runtime override path is wired correctly out
// of the box; if the store already appears in
// `[setup.config_stores.edgezero_runtime_env]`, skip.
// the runtime opens by name (see `runtime_env_config` in
// lib.rs). Provision owns the store creation alongside the
// operator's declared stores so the runtime override path is
// wired correctly out of the box; if the store already appears
// in `[setup.config_stores.edgezero_runtime_env]`, skip.
let runtime_env_kind = "config";
let runtime_env_name = "edgezero_runtime_env";
if dry_run {
Expand Down
98 changes: 78 additions & 20 deletions crates/edgezero-adapter-fastly/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ pub mod response;
pub mod secret_store;

#[cfg(feature = "fastly")]
use edgezero_core::app::{Hooks, StoresMetadata};
use edgezero_core::app::Hooks;
#[cfg(any(feature = "fastly", test))]
use edgezero_core::app::StoresMetadata;
#[cfg(feature = "fastly")]
use edgezero_core::env_config::EnvConfig;
#[cfg(feature = "fastly")]
Expand Down Expand Up @@ -139,7 +141,7 @@ where
F: FnOnce(&fastly::Request, &mut Extensions),
{
let stores = A::stores();
let env = env_config_from_runtime_dictionary(stores);
let env = runtime_env_config(stores);
let logging = logging_from_env(&env);
if logging.use_fastly_logger && !A::owns_logging() {
let endpoint = logging.endpoint.as_deref().unwrap_or("stdout");
Expand All @@ -158,23 +160,24 @@ where
}

/// Build an [`EnvConfig`] from the optional `edgezero_runtime_env`
/// Fastly Config Store. Compute@Edge has no process env -- the
/// `EDGEZERO__*` runtime overrides spec 5.2/5.4 expects must come
/// from a Config Store the operator pre-populates (locally via
/// `fastly.toml`'s `[local_server.config_stores.edgezero_runtime_env]`
/// block; remotely via a `fastly config-store` named `edgezero_runtime_env`).
/// Fastly Config Store.
///
/// The Cloudflare adapter does the same thing through `env.var(...)`
/// (lib.rs:55) -- Workers also have no `std::env`. Mirroring the
/// approach here closes the spec 12.7 gap where `__KEY` runtime
/// overrides silently fell back to the binding's default id.
/// Compute@Edge has no process env, so the `EDGEZERO__*` runtime overrides
/// (logging settings, per-store platform names, the config-store `__KEY`
/// selector) come from a Config Store the operator pre-populates: locally via
/// `fastly.toml`'s `[local_server.config_stores.edgezero_runtime_env]` block,
/// remotely via a `fastly config-store` named `edgezero_runtime_env`.
///
/// If the store is missing or empty, returns an empty `EnvConfig` --
/// the rest of the runtime then uses the baked-in defaults (which is
/// what the pre-fix code did, just without the env-driven override
/// path the spec promises).
/// If the store is missing or empty, returns an empty `EnvConfig` and the rest
/// of the runtime uses its baked-in defaults.
///
/// [`run_app`] calls this itself. A custom Fastly entry point that bypasses
/// [`run_app`] should call it with its own `A::stores()` so staged and
/// overridden store selectors resolve identically.
#[cfg(feature = "fastly")]
fn env_config_from_runtime_dictionary(stores: StoresMetadata) -> EnvConfig {
#[must_use]
#[inline]
pub fn runtime_env_config(stores: StoresMetadata) -> EnvConfig {
use fastly::ConfigStore;
use std::iter::empty;
let Ok(dict) = ConfigStore::try_open("edgezero_runtime_env") else {
Expand All @@ -194,6 +197,17 @@ fn env_config_from_runtime_dictionary(stores: StoresMetadata) -> EnvConfig {
);
return EnvConfig::from_vars(empty::<(String, String)>());
};
let vars = runtime_env_keys(stores)
.into_iter()
.filter_map(|key| dict.get(&key).map(|value| (key, value)));
EnvConfig::from_vars(vars)
}

/// The `EDGEZERO__*` keys the Fastly runtime looks up: the fixed adapter and
/// logging settings, plus a `__NAME` selector for every declared store id and
/// a `__KEY` selector for config-store ids only.
#[cfg(any(feature = "fastly", test))]
fn runtime_env_keys(stores: StoresMetadata) -> Vec<String> {
let mut keys: Vec<String> = vec![
"EDGEZERO__ADAPTER__HOST".to_owned(),
"EDGEZERO__ADAPTER__PORT".to_owned(),
Expand All @@ -217,10 +231,7 @@ fn env_config_from_runtime_dictionary(stores: StoresMetadata) -> EnvConfig {
}
}
}
let vars = keys
.into_iter()
.filter_map(|key| dict.get(&key).map(|value| (key, value)));
EnvConfig::from_vars(vars)
keys
}

/// Dispatch with a config store wired explicitly. Use `run_app` for
Expand Down Expand Up @@ -270,3 +281,50 @@ mod tests {
assert!(logging.use_fastly_logger);
}
}

#[cfg(test)]
mod runtime_env_key_tests {
use super::runtime_env_keys;
use edgezero_core::app::{StoreMetadata, StoresMetadata};

fn contains(keys: &[String], key: &str) -> bool {
keys.iter().any(|candidate| candidate.as_str() == key)
}

#[test]
fn runtime_env_keys_name_every_store_and_key_only_config_stores() {
let stores = StoresMetadata {
config: Some(StoreMetadata {
default: "main",
ids: &["main", "edge"],
}),
kv: Some(StoreMetadata {
default: "cache",
ids: &["cache"],
}),
secrets: Some(StoreMetadata {
default: "vault",
ids: &["vault"],
}),
};

let keys = runtime_env_keys(stores);

assert!(contains(&keys, "EDGEZERO__ADAPTER__HOST"));
assert!(contains(&keys, "EDGEZERO__ADAPTER__PORT"));
assert!(contains(&keys, "EDGEZERO__LOGGING__LEVEL"));
assert!(contains(&keys, "EDGEZERO__LOGGING__ENDPOINT"));
assert!(contains(&keys, "EDGEZERO__LOGGING__USE_FASTLY_LOGGER"));
assert!(contains(&keys, "EDGEZERO__LOGGING__ECHO_STDOUT"));

assert!(contains(&keys, "EDGEZERO__STORES__CONFIG__MAIN__NAME"));
assert!(contains(&keys, "EDGEZERO__STORES__CONFIG__EDGE__NAME"));
assert!(contains(&keys, "EDGEZERO__STORES__KV__CACHE__NAME"));
assert!(contains(&keys, "EDGEZERO__STORES__SECRETS__VAULT__NAME"));

assert!(contains(&keys, "EDGEZERO__STORES__CONFIG__MAIN__KEY"));
assert!(contains(&keys, "EDGEZERO__STORES__CONFIG__EDGE__KEY"));
assert!(!contains(&keys, "EDGEZERO__STORES__KV__CACHE__KEY"));
assert!(!contains(&keys, "EDGEZERO__STORES__SECRETS__VAULT__KEY"));
}
}
2 changes: 1 addition & 1 deletion scripts/smoke_test_config_key_override.sh
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ upper() {
# Seed the Fastly local config store `edgezero_runtime_env` with the
# runtime override env vars. The Fastly Compute@Edge runtime has no
# process env, so EDGEZERO__* overrides are read from this dedicated
# Config Store (see env_config_from_runtime_dictionary in
# Config Store (see runtime_env_config in
# crates/edgezero-adapter-fastly/src/lib.rs). $1 is the fastly.toml
# path; $2 is the per-row __KEY override value (empty -> no override).
seed_fastly_runtime_env() {
Expand Down
Loading