Overview
Values are configuration settings that override schema defaults. They are authored as YAML files in the sentry-options-automator repository, organized by namespace and target.
The CLI tool validates values against schemas, merges target-specific overrides with defaults, and generates JSON files for deployment to Kubernetes ConfigMaps.
Values are written as YAML files with a single options key:
options:
system.url-prefix: "https://custom.sentry.io"
traces.sample-rate: 0.5
feature.enabled: true
feature.allowed-orgs:
- getsentry
- sentry
The top-level options key is required. All option keys must be defined in the corresponding schema.
The CLI generates JSON files with the merged values:
{
"options": {
"system.url-prefix": "https://custom.sentry.io",
"traces.sample-rate": 0.5,
"feature.enabled": true,
"feature.allowed-orgs": ["getsentry", "sentry"]
},
"generated_at": "2024-01-21T18:30:00.123456+00:00",
"commit_sha": "abc123def456"
}
Metadata fields:
generated_at - ISO 8601 timestamp of generation
commit_sha - Git commit SHA that triggered the generation
These metadata fields enable propagation delay tracking in the hot-reload observability metrics.
Directory structure
In sentry-options-automator
Values are organized by namespace and target:
option-values/
├── seer/
│ ├── default/ # Base values (required)
│ │ └── values.yaml
│ ├── us/ # US region overrides
│ │ └── values.yaml
│ └── de/ # DE region overrides
│ └── values.yaml
├── relay/
│ ├── default/
│ │ └── values.yaml
│ └── us/
│ └── values.yaml
└── getsentry/
├── default/
│ ├── core.yaml # Can split into multiple files
│ └── features.yaml
└── s4s/
└── overrides.yaml
You can split default values across multiple YAML files in the same directory. They will be merged together during processing.
Target system
What is a target?
A target represents a deployment environment or region (e.g., us, de, s4s). Each target gets its own ConfigMap with values merged from default/ plus target-specific overrides.
Target requirements
- Every namespace must have a
default target
- Non-default targets inherit from
default
- Target-specific values override defaults
- The
default target is not deployed directly - it serves as the base for other targets
Override behavior
Target values are merged with defaults using a simple override strategy:
default/values.yaml:
options:
feature.enabled: false
feature.rate-limit: 100
feature.endpoint: "https://api.example.com"
us/values.yaml:
options:
feature.enabled: true
feature.rate-limit: 200
Merged result for us target:
{
"options": {
"feature.enabled": true, // overridden
"feature.rate-limit": 200, // overridden
"feature.endpoint": "https://api.example.com" // inherited from default
}
}
Target naming rules
Target directory names must follow Kubernetes naming conventions:
- Allowed characters: lowercase alphanumeric,
-, .
- Must start and end: with alphanumeric character
- No uppercase:
US is rejected, use us
- No underscores:
us_west is rejected, use us-west
Common target names:
default - Required base target
us - US region
de - DE region
s4s - Sentry for Sentry
saas - SaaS deployment
ConfigMap generation
Generation workflow
The CI/CD pipeline generates one ConfigMap per namespace/target combination:
# For each namespace and non-default target:
sentry-options-cli write \
--schemas schemas/ \
--root option-values/ \
--output-format configmap \
--namespace seer \
--target us \
--commit-sha "$COMMIT_SHA" \
--commit-timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
This generates a ConfigMap named sentry-options-seer containing the merged values for the us target.
ConfigMap naming
ConfigMaps are named: sentry-options-{namespace}
Examples:
sentry-options-seer - Contains seer namespace values for specific target
sentry-options-relay - Contains relay namespace values for specific target
The target is not in the ConfigMap name - different targets are deployed to different clusters.
ConfigMap structure
apiVersion: v1
kind: ConfigMap
metadata:
name: sentry-options-seer
namespace: default
data:
values.json: |
{
"options": {
"feature.enabled": true,
"feature.rate-limit": 200
},
"generated_at": "2024-01-21T18:30:00.123456+00:00",
"commit_sha": "abc123def456"
}
The ConfigMap contains a single file values.json with the merged options.
Mounting ConfigMaps
In the ops repo, add pod annotations to your deployment:
spec:
template:
metadata:
annotations:
options.sentry.io/inject: 'true'
options.sentry.io/namespace: seer
The sentry-options injector automatically mounts the ConfigMap to:
/etc/sentry-options/values/seer/values.json
For multiple namespaces:
options.sentry.io/namespace: seer-code-review,seer
Validation
Schema validation
Values are validated against their namespace schema:
sentry-options-cli validate-values \
--schemas schemas/ \
--root option-values/
Validation checks:
- Unknown options: Rejected (catches typos)
- Type mismatches: String where integer expected → Error
- Invalid values: Values that don’t match schema constraints
- Missing required fields: Top-level
options key required
Validation errors
Example error output:
Value error for seer:
feature.rate-limit "not-a-number" is not of type "integer"
unknown-option Additional properties are not allowed
The CLI validates:
- All values in
default/ target
- All values in override targets (after merging)
- All namespaces in the option-values directory
If any namespace fails validation, the entire operation fails. This prevents partial deployments with invalid configuration.
Default values and schema fallback
The client libraries have a fallback chain for option values:
- Explicit value: Check if option is set in values.json
- Schema default: Return default from schema if not set
- Error: If option doesn’t exist in schema
# Schema defines: "feature.enabled": {"type": "boolean", "default": false}
# Case 1: Value set in ConfigMap
opts.get('feature.enabled') # Returns: True (from values.json)
# Case 2: ConfigMap doesn't have this option
opts.get('feature.enabled') # Returns: False (from schema default)
# Case 3: Option not in schema
opts.get('unknown.option') # Raises: UnknownOptionError
This allows services to start before ConfigMaps are deployed - they’ll use schema defaults until values are applied.
File splitting
You can split values across multiple YAML files in the same target directory:
getsentry/
└── default/
├── core.yaml
├── features.yaml
└── experimental.yaml
core.yaml:
options:
system.url-prefix: "https://sentry.io"
system.timeout: 30
features.yaml:
options:
feature.autofix-enabled: true
feature.grouping-enabled: false
The CLI merges all YAML files in the directory before validation. If the same option appears in multiple files, the behavior is undefined - avoid duplicates.
Target-to-cluster mapping
The CD pipeline maps targets to Kubernetes clusters:
# Example mapping (configured in CD pipeline)
targets:
us:
clusters:
- us-west-2
- us-east-1
de:
clusters:
- eu-central-1
s4s:
clusters:
- s4s-production
Each target’s ConfigMap is deployed to its configured clusters. The mapping is managed by the platform team.
Updating values workflow
To update configuration values:
-
Edit YAML files in sentry-options-automator
# option-values/seer/us/values.yaml
options:
feature.enabled: true # Changed from false
-
CI validates values against schemas (fetched via repos.json)
sentry-options-cli validate-values --schemas schemas/ --root option-values/
-
Merge to main triggers CD pipeline
-
CD generates ConfigMaps for each namespace/target
sentry-options-cli write --namespace seer --target us ...
-
ConfigMaps applied to target clusters
kubectl apply -f sentry-options-seer.yaml
-
Kubelet syncs ConfigMap to pods (~1-2 minutes)
-
Client library detects change via file watching (~5 seconds)
-
New values active - total latency ~1-2 minutes
No pod restart is required. Values are hot-reloaded automatically.