diff --git a/crates/apollo_deployments/src/bin/deployment_config_generator.rs b/crates/apollo_deployments/src/bin/deployment_config_generator.rs index ce27dea97ce..60b94a114fe 100644 --- a/crates/apollo_deployments/src/bin/deployment_config_generator.rs +++ b/crates/apollo_deployments/src/bin/deployment_config_generator.rs @@ -11,6 +11,7 @@ use std::env; use std::path::PathBuf; use apollo_deployments::jsonnet_generation::{ + eval_build_service_with_overrides, eval_build_with_overrides, overrides_from_sequencer_config, service_config_to_preset, @@ -57,21 +58,26 @@ fn main() { .expect("Couldn't set working dir."); let overrides = overrides_from_sequencer_config(&flat_overrides); - let service_configs = eval_build_with_overrides(&args.layout, &overrides); - let services = service_configs.as_object().expect("build result is a service-keyed object"); let output = match &args.service { - Some(service) => service_config_to_preset( - services - .get(service) - .unwrap_or_else(|| panic!("layout '{}' has no service '{service}'", args.layout)), - ), - None => Value::Object( - services - .iter() - .map(|(name, config)| (name.clone(), service_config_to_preset(config))) - .collect(), - ), + // Build only the requested service: a per-service deploy supplies just that service's + // overrides, so the other services' (absent) override keys must never be forced. + Some(service) => { + let service_config = + eval_build_service_with_overrides(&args.layout, &overrides, service); + service_config_to_preset(&service_config) + } + None => { + let service_configs = eval_build_with_overrides(&args.layout, &overrides); + let services = + service_configs.as_object().expect("build result is a service-keyed object"); + Value::Object( + services + .iter() + .map(|(name, config)| (name.clone(), service_config_to_preset(config))) + .collect(), + ) + } }; println!("{}", serde_json::to_string_pretty(&output).expect("config is serializable")); diff --git a/crates/apollo_deployments/src/jsonnet_generation.rs b/crates/apollo_deployments/src/jsonnet_generation.rs index 312d58103e5..42a43291c44 100644 --- a/crates/apollo_deployments/src/jsonnet_generation.rs +++ b/crates/apollo_deployments/src/jsonnet_generation.rs @@ -28,7 +28,16 @@ pub(crate) fn jsonnet_state() -> State { /// Evaluates `build(layout, overrides)`. pub fn eval_build_with_overrides(layout: &str, overrides: &Value) -> Value { let overrides_literal = serde_json::to_string(overrides).expect("overrides is serializable"); - eval_build_with_expr(layout, &overrides_literal) + eval_build_with_expr(layout, &overrides_literal, None) +} + +/// Evaluates only `build(layout, overrides)[service]`, so jsonnet forces just that one service's +/// config. A per-service deploy supplies only the keys its own components reference (e.g. the +/// gateway service's overrides carry no `committer_config`), and building just that service means +/// the other services' overrides are never accessed — building the whole map would force them all. +pub fn eval_build_service_with_overrides(layout: &str, overrides: &Value, service: &str) -> Value { + let overrides_literal = serde_json::to_string(overrides).expect("overrides is serializable"); + eval_build_with_expr(layout, &overrides_literal, Some(service)) } /// Renders one service's nested `build` output in the node-loadable flat dotted dump/preset format @@ -93,12 +102,22 @@ pub fn eval_overrides_file(overrides_path: &str) -> Value { /// Evaluates `build(layout, )`, where `overrides_expr` is a jsonnet expression for /// the overrides (a file `import` or an inlined JSON literal). -pub(crate) fn eval_build_with_expr(layout: &str, overrides_expr: &str) -> Value { +pub(crate) fn eval_build_with_expr( + layout: &str, + overrides_expr: &str, + service: Option<&str>, +) -> Value { let state = jsonnet_state(); let _guard = state.enter(); let layout_literal = serde_json::to_string(layout).expect("layout is serializable"); + let index = match service { + Some(service) => { + format!("[{}]", serde_json::to_string(service).expect("service is serializable")) + } + None => String::new(), + }; let snippet = - format!("(import 'lib/build.libsonnet').build({layout_literal}, {overrides_expr})"); + format!("(import 'lib/build.libsonnet').build({layout_literal}, {overrides_expr}){index}"); let val = state .evaluate_snippet("build_entry.jsonnet", snippet) .expect("build.libsonnet failed to evaluate"); diff --git a/crates/apollo_deployments/src/jsonnet_test.rs b/crates/apollo_deployments/src/jsonnet_test.rs index 6dbcc0cb759..283ab1d682c 100644 --- a/crates/apollo_deployments/src/jsonnet_test.rs +++ b/crates/apollo_deployments/src/jsonnet_test.rs @@ -89,7 +89,7 @@ where } fn eval_test_build(layout: &str) -> Value { - eval_build_with_expr(layout, "import 'testing/overrides.libsonnet'") + eval_build_with_expr(layout, "import 'testing/overrides.libsonnet'", None) } /// Flattens a nested JSON object into a map from dot-separated leaf path to value. Non-empty