Skip to content
Closed
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
107 changes: 84 additions & 23 deletions deployments/sequencer/src/constructs/configmap.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import json
import os
import subprocess
import tempfile

from imports import k8s
from src.config.loaders import NodeConfigLoader
from src.constructs.base import BaseConstruct

# Opt-in: when this env var is set, the per-service ConfigMap is produced by the jsonnet
# `build(layout, overrides)` generator binary instead of the `apply_sequencer_overrides` placeholder
# substitution. Unset (the default) keeps the substitution path, so production is unaffected until
# the binary is provisioned in the synth pipeline.
USE_CONFIG_GENERATOR_ENV_VAR = "APOLLO_USE_CONFIG_GENERATOR"
CONFIG_GENERATOR_BINARY = "target/release/deployment_config_generator"

# The deploy's service names match the jsonnet layout's service keys, except `sierracompiler` (the
# jsonnet layout key is `sierra_compiler`). Used only for the generator's `--service` argument; the
# k8s resource name keeps the deploy name.
_GENERATOR_SERVICE_NAMES = {"sierracompiler": "sierra_compiler"}


class ConfigMapConstruct(BaseConstruct):
def __init__(
Expand Down Expand Up @@ -39,47 +54,93 @@ def _get_config_map(self) -> k8s.KubeConfigMap:
f"config.configList is required for service '{self.service_config.name}' but was not provided"
)

# Load JSON configs using NodeConfigLoader
if os.environ.get(USE_CONFIG_GENERATOR_ENV_VAR):
node_config = self._node_config_via_generator()
else:
node_config = self._node_config_via_substitution()

config_data = json.dumps(node_config, indent=2)

return k8s.KubeConfigMap(
self,
"configmap",
metadata=k8s.ObjectMeta(
name=f"sequencer-{self.service_config.name}-config",
labels=self.labels,
),
data=dict(config=config_data),
) # Key is "config" to match node/ format, mounted as /config/sequencer/presets/config

def _node_config_via_substitution(self) -> dict:
"""Assemble the node config the original way: load the service's `replacer_*` files from
`configList` and substitute the `$$$` placeholders with the merged `sequencerConfig`."""
node_config_loader = NodeConfigLoader(
config_list_json_path=self.service_config.config.configList,
)
node_config = node_config_loader.load()

# sequencerConfig is now already merged from common into service_config
# sequencerConfig is now already merged from common into service_config.
merged_sequencer_config = (
self.service_config.config.sequencerConfig
if self.service_config.config and self.service_config.config.sequencerConfig
else {}
)

# Apply merged overrides (includes validation for both unused keys and remaining placeholders)
# Apply merged overrides (validates both unused keys and remaining placeholders).
if merged_sequencer_config:
node_config = NodeConfigLoader.apply_sequencer_overrides(
return NodeConfigLoader.apply_sequencer_overrides(
node_config,
merged_sequencer_config,
service_name=self.service_config.name,
config_list_path=self.service_config.config.configList,
layout=self.layout,
overlays=self.overlays,
)
else:
# If no sequencer config overrides, still validate for remaining placeholders
NodeConfigLoader.validate_no_remaining_placeholders(
node_config,
config_list_path=self.service_config.config.configList,
layout=self.layout,
overlays=self.overlays,
service_name=self.service_config.name,
)
# No overrides: still validate that no placeholders remain.
NodeConfigLoader.validate_no_remaining_placeholders(
node_config,
config_list_path=self.service_config.config.configList,
layout=self.layout,
overlays=self.overlays,
service_name=self.service_config.name,
)
return node_config

config_data = json.dumps(node_config, indent=2)
def _node_config_via_generator(self) -> dict:
"""Assemble the node config from the jsonnet `build(layout, overrides)` generator binary: the
merged (flat dotted) `sequencerConfig` is the overrides input, and the binary prints this
service's final node-loadable config JSON to stdout. Only the non-secret config is produced;
secrets are mounted separately and merged by the node as a later `--config_file`."""
merged_sequencer_config = self.service_config.config.sequencerConfig or {}
service_name = _GENERATOR_SERVICE_NAMES.get(
self.service_config.name, self.service_config.name
)
binary = os.path.join(NodeConfigLoader.ROOT_DIR, CONFIG_GENERATOR_BINARY)

return k8s.KubeConfigMap(
self,
"configmap",
metadata=k8s.ObjectMeta(
name=f"sequencer-{self.service_config.name}-config",
labels=self.labels,
),
data=dict(config=config_data),
) # Key is "config" to match node/ format, mounted as /config/sequencer/presets/config
# The binary reads the overrides from a file; write the merged sequencerConfig to a temp one.
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as overrides_file:
json.dump(merged_sequencer_config, overrides_file)
overrides_path = overrides_file.name
try:
result = subprocess.run(
[
binary,
"--layout",
self.layout,
"--config-file",
overrides_path,
"--service",
service_name,
],
capture_output=True,
text=True,
)
finally:
os.unlink(overrides_path)

if result.returncode != 0:
raise RuntimeError(
f"config generator failed for service '{self.service_config.name}' "
f"(layout '{self.layout}'):\n{result.stderr}"
)
return json.loads(result.stdout)