KeySelector Reference¶
Complete reference for keySelector expressions used in Genifest change definitions.
Overview¶
KeySelectors use yq-style path expressions to specify which parts of YAML documents to modify. This implementation provides a robust, grammar-based parser that supports a carefully chosen subset of the expression syntax used by tools like yq and jq.
Basic Syntax¶
Field Access¶
Access fields in YAML objects using dot notation:
# Simple field access
keySelector: ".metadata.name"
keySelector: ".spec.replicas"
keySelector: ".data.config"
# Nested field access
keySelector: ".spec.template.spec"
keySelector: ".metadata.labels.app"
keySelector: ".spec.template.metadata.annotations"
Array Indexing¶
Access array elements using bracket notation with numeric indices:
# Positive indexing (0-based)
keySelector: ".spec.containers[0]" # First container
keySelector: ".spec.ports[1]" # Second port
keySelector: ".items[5]" # Sixth item
# Negative indexing (from end)
keySelector: ".spec.containers[-1]" # Last container
keySelector: ".items[-2]" # Second-to-last item
Map Key Access with Quotes¶
Access map keys that contain special characters using quoted strings:
# Double quotes
keySelector: ".data.[\"app.yaml\"]"
keySelector: ".labels.[\"app.kubernetes.io/name\"]"
keySelector: ".annotations.[\"deployment.kubernetes.io/revision\"]"
# Single quotes
keySelector: ".data.['config-file']"
keySelector: ".labels.['custom-key']"
keySelector: ".annotations.['build-timestamp']"
# Important: Quoted strings starting with numbers
keySelector: ".data.[\"1password.json\"]" # String key (not numeric index 1)
keySelector: ".config.[\"2fa-settings\"]" # String key (not numeric index 2)
keySelector: ".secrets.[\"3rd-party-key\"]" # String key (not numeric index 3)
Key Point: The parser correctly distinguishes between numeric indices ([1]) and quoted string keys (["1password.json"]). Always use quotes for string keys that start with numbers to prevent them from being interpreted as array indices.
Array Slicing¶
Extract ranges of elements from arrays using slice notation:
# Range slicing [start:end] - excludes end index
keySelector: ".items[1:3]" # Elements at indices 1 and 2
keySelector: ".spec.containers[0:2]" # First two containers
# Open-ended slicing
keySelector: ".items[2:]" # From index 2 to end
keySelector: ".items[:3]" # First 3 elements (indices 0,1,2)
keySelector: ".items[:]" # All elements (full copy)
# Negative indices in slicing
keySelector: ".items[:-1]" # All except last element
keySelector: ".items[-3:]" # Last 3 elements
Array Iteration and Pipeline Operations¶
Process all elements in an array and chain operations:
# Array iteration - process all containers
keySelector: ".spec.containers[]"
# Filter with select() function
keySelector: ".spec.containers[] | select(.name == \"frontend\")"
keySelector: ".spec.containers[] | select(.name != \"sidecar\")"
# Complete pipeline operations
keySelector: ".spec.containers[] | select(.name == \"frontend\") | .image"
keySelector: ".spec.template.spec.containers[] | select(.name == \"backend\") | .image"
# Filter and access nested properties
keySelector: ".spec.containers[] | select(.name == \"app\") | .ports[0].containerPort"
keySelector: ".spec.volumes[] | select(.name == \"config\") | .configMap.name"
Comparison Operators¶
Use comparison operators in select() functions:
# Equality comparison
keySelector: ".spec.containers[] | select(.name == \"frontend\")"
keySelector: ".metadata.labels[] | select(.key == \"app.kubernetes.io/name\")"
# Inequality comparison
keySelector: ".spec.containers[] | select(.name != \"sidecar\")"
keySelector: ".spec.volumes[] | select(.name != \"tmp\")"
Alternative/Default Operator¶
Use the // operator to provide fallback values when paths don't exist or are empty:
# Basic alternative values
keySelector: ".metadata.annotations[\"missing-annotation\"] // \"default-value\""
keySelector: ".spec.replicas // \"1\""
keySelector: ".data.config // \"fallback-config\""
# Complex alternatives with nested paths
keySelector: ".spec.template.spec.containers[0].resources.limits.memory // \"256Mi\""
keySelector: ".metadata.labels[\"version\"] // \"unknown\""
# Combined with pipelines (alternatives evaluated last)
keySelector: ".spec.containers[] | select(.name == \"app\") | .image // \"default:latest\""
Complex Examples¶
Deep Nested Access¶
# Kubernetes-style deep navigation
keySelector: ".spec.template.spec.containers[0].image"
keySelector: ".spec.template.spec.containers[0].ports[0].containerPort"
keySelector: ".spec.template.spec.volumes[1].configMap.items[0].key"
# Complex metadata access
keySelector: ".metadata.annotations.[\"kubectl.kubernetes.io/last-applied-configuration\"]"
keySelector: ".spec.template.metadata.labels.[\"app.kubernetes.io/version\"]"
Mixed Array and Object Operations¶
# Array of objects with field access
keySelector: ".spec.rules[0].host"
keySelector: ".spec.containers[1].env[2].value"
keySelector: ".status.conditions[-1].type"
# Complex ConfigMap operations
keySelector: ".data.[\"application.properties\"]"
keySelector: ".data.[\"nginx.conf\"]"
keySelector: ".data.[\"config.json\"]"
Real-World Scenarios¶
# Update specific container image by name (modern approach)
keySelector: ".spec.template.spec.containers[] | select(.name == \"frontend\") | .image"
keySelector: ".spec.template.spec.containers[] | select(.name == \"backend\") | .image"
# Update container image by index (legacy approach)
keySelector: ".spec.template.spec.containers[0].image"
# Modify resource limits for specific container with defaults
keySelector: ".spec.template.spec.containers[] | select(.name == \"app\") | .resources.limits.memory // \"256Mi\""
keySelector: ".spec.template.spec.containers[0].resources.limits.memory // \"128Mi\""
# Update environment variables with fallback values
keySelector: ".spec.template.spec.containers[] | select(.name == \"app\") | .env[0].value // \"default-value\""
# Update ConfigMap data with alternatives
keySelector: ".data.[\"app.properties\"] // \"# Default configuration\""
keySelector: ".data.config // \"default: true\""
# Configuration with fallbacks
keySelector: ".spec.replicas // \"3\"" # Default replica count
keySelector: ".spec.ports[0].port // \"8080\"" # Default port
keySelector: ".spec.rules[0].host // \"localhost\"" # Default host
# Secrets with alternatives
keySelector: ".data.[\"database-password\"] // \"default-password\""
keySelector: ".metadata.annotations[\"backup.policy\"] // \"daily\""
# Volume mounts with defaults
keySelector: ".spec.template.spec.containers[] | select(.name == \"app\") | .volumeMounts[0].mountPath // \"/data\""
Common Errors¶
# ❌ Invalid syntax
keySelector: ".spec[containers" # Missing closing bracket
keySelector: ".spec..replicas" # Double dots not supported
keySelector: ".spec[0:1:2]" # Step slicing not supported
# ❌ Runtime errors (detected during execution)
keySelector: ".spec.containers[999]" # Index out of bounds
keySelector: ".nonexistent.field" # Field doesn't exist
keySelector: ".spec.replicas[0]" # Can't index scalar value
Supported vs yq/jq¶
✅ Fully Supported Features¶
- Object field access:
.field,.nested.field - Array indexing:
[0],[-1], positive and negative indices - Array slicing:
[1:3],[2:],[:3],[:] - Array iteration:
[]for processing all elements - Quoted key access:
["key"],['key'], handling special characters - Pipeline operations:
|chaining multiple operations - Alternative operator:
//for fallback values when paths don't exist - Filtering with select():
select(.name == "value")for conditional filtering - Comparison operators:
==,!=for equality/inequality tests - Complex nested paths: mixing all above operations
- Grammar-based parsing: robust expression handling
- Parse-time validation: syntax checking before execution
- Smart bracket parsing: correctly distinguishes numeric indices from quoted string keys
❌ Not Supported (by design)¶
- Advanced filtering functions:
map(),has(),contains(),keys(),values() - Conditional expressions:
if-then-elseconstructs - Arithmetic operations:
+,-,*,/,% - String functions:
split(),join(),length(), regex operations - Recursive descent:
..(find anywhere) - Variable assignment: setting temporary variables
- Complex queries: SQL-like operations with multiple conditions
- Step slicing:
[start:end:step]with step parameter - Advanced comparison operators:
<,>,<=,>=
Best Practices¶
Clarity and Maintainability¶
# ✅ Good: Clear, specific selectors with reasonable defaults
keySelector: ".spec.template.spec.containers[] | select(.name == \"frontend\") | .image // \"nginx:latest\""
keySelector: ".data.[\"application.yaml\"] // \"default-config\""
# ✅ Good: Modern approach using names instead of indices
keySelector: ".spec.containers[] | select(.name == \"app\") | .image"
# ✅ Good: Provide sensible fallbacks for optional configuration
keySelector: ".spec.replicas // \"3\""
keySelector: ".metadata.annotations[\"backup.policy\"] // \"daily\""
# ⚠️ Acceptable but less maintainable: Index-based access
keySelector: ".spec.template.spec.containers[0].image"
# ❌ Avoid: Overly complex nested expressions
keySelector: ".spec.template.spec.volumes[2].configMap.items[1].path"
Pipeline Best Practices¶
# ✅ Good: Use descriptive container names for filtering
keySelector: ".spec.containers[] | select(.name == \"frontend\") | .image"
keySelector: ".spec.containers[] | select(.name == \"sidecar\") | .env[0].value"
# ✅ Good: Simple pipeline with clear intent
keySelector: ".spec.volumes[] | select(.name == \"config\") | .configMap.name"
# ❌ Avoid: Chaining too many operations
keySelector: ".spec.containers[] | select(.name == \"app\") | .volumeMounts[] | select(.name == \"data\") | .mountPath"
Error Prevention¶
# ✅ Good: Use quoted keys for special characters
keySelector: ".labels.[\"app.kubernetes.io/name\"]"
keySelector: ".data.[\"nginx.conf\"]"
# ✅ Good: Use quoted keys for strings starting with numbers
keySelector: ".data.[\"1password.json\"]" # String key, not index 1
keySelector: ".config.[\"2fa-settings\"]" # String key, not index 2
# ❌ Risky: Special characters without quotes (may fail)
keySelector: ".labels.app.kubernetes.io/name" # Fails: dots in key
# ❌ Wrong: Unquoted strings that start with numbers (parsed as indices)
keySelector: ".data.[1password.json]" # Tries to use index 1, then fails
Performance Considerations¶
# ✅ Good: Direct access is fastest
keySelector: ".spec.replicas"
keySelector: ".data.config"
# ⚠️ Moderate: Array iteration and filtering (requires processing multiple elements)
keySelector: ".spec.containers[] | select(.name == \"frontend\") | .image"
# ⚠️ Note: Deep nesting is supported but slower
keySelector: ".spec.template.spec.containers[0].env[5].value"
# ⚠️ Slower: Complex pipelines with multiple operations
keySelector: ".spec.containers[] | select(.name == \"app\") | .volumeMounts[0].mountPath"
Testing KeySelectors¶
Use the genifest validate command to test your keySelectors without applying changes:
# Validate configuration and keySelectors
genifest validate
# See the parsed configuration structure
genifest config
See Also¶
- Configuration Schema - Complete configuration reference
- Core Concepts - Understanding the system
- Examples - Real-world usage patterns