Patching Syntax
This guide explains how to use the patching system in OpenChoreo Traits to modify resources generated by ComponentTypes.
Overviewβ
Traits can modify existing resources using patches, which are JSON Patch operations enhanced with:
- Array filtering using JSONPath-like syntax
- CEL-based resource targeting
- forEach iteration support
Basic Patch Structureβ
Patches are defined in the Trait's spec.patches section:
apiVersion: openchoreo.dev/v1alpha1
kind: Trait
metadata:
name: monitoring-sidecar
spec:
patches:
- target:
kind: Deployment
group: apps
version: v1
operations:
- op: add
path: /spec/template/spec/containers/-
value:
name: prometheus-exporter
image: prom/node-exporter:latest
ports:
- containerPort: 9100
Supported Operationsβ
addβ
Adds a value at the specified path:
# Add a new label
- op: add
path: /metadata/labels/monitoring
value: "enabled"
# Append to array (using -)
- op: add
path: /spec/containers/-
value:
name: sidecar
image: sidecar:latest
# Add nested structure
- op: add
path: /metadata/annotations/example.com~1version
value: "v2.0"
replaceβ
Replaces an existing value. The path must exist:
# Replace a value
- op: replace
path: /spec/replicas
value: 3
# Replace array element
- op: replace
path: /spec/containers/0/image
value: nginx:latest
removeβ
Removes a value at the path:
# Remove a label
- op: remove
path: /metadata/labels/deprecated
# Remove array element
- op: remove
path: /spec/containers/1
Array Filteringβ
Use JSONPath-like syntax to target specific array elements by field value:
Basic Filteringβ
# Target container by name
- op: add
path: /spec/template/spec/containers[?(@.name=='app')]/env/-
value:
name: MONITORING
value: enabled
# Target volume by name
- op: replace
path: /spec/template/spec/volumes[?(@.name=='data')]/emptyDir
value:
sizeLimit: 10Gi
Nested Field Filtersβ
# Filter by nested field path (dot notation)
- op: replace
path: /spec/containers[?(@.resources.limits.memory=='2Gi')]/image
value: app:high-mem-v2
# Filter by configMap name
- op: add
path: /spec/volumes[?(@.configMap.name=='app-config')]/configMap/defaultMode
value: 0644
Filter Limitationsβ
Supported: Simple equality filters of the form @.field.path=='value'
# Supported
- op: add
path: /spec/containers[?(@.name=='app')]/env/-
value: {name: VAR, value: val}
Not supported: Multiple conditions (&&, ||), operators like contains, array indexing in filters, or existence checks.
CEL Expression Supportβ
CEL expressions (${...}) can be used in patch fields:
In pathβ
# Dynamic path segments
- op: add
path: /data/${env.name}
value: ${env.value}
# Dynamic filter conditions
- op: add
path: /spec/containers[?(@.name=='${parameters.containerName}')]/env/-
value:
name: VERSION
value: ${parameters.version}
In valueβ
- op: add
path: /metadata/labels/app
value: ${metadata.name}
- op: add
path: /spec/template/spec/containers/-
value:
name: ${parameters.sidecarName}
image: ${parameters.sidecarImage}
ports:
- containerPort: ${parameters.sidecarPort}
Resource Targetingβ
Basic Targetingβ
The target spec requires kind, group, and version:
patches:
# Patch apps/v1 Deployment
- target:
kind: Deployment
group: apps
version: v1
operations:
- op: add
path: /metadata/labels/patched
value: "true"
# Patch core v1 Service (empty string for core API)
- target:
kind: Service
group: "" # Empty string for core API resources
version: v1
operations:
- op: add
path: /metadata/annotations/patched
value: "true"
Key points:
kind,group, andversionare required- For core API resources (Service, ConfigMap, Secret), use
group: "" targetPlaneis optional, defaults to"dataplane"
CEL-Based Filtering with whereβ
Use where clause to target resources conditionally:
patches:
- target:
kind: Deployment
group: apps
version: v1
where: ${resource.spec.replicas > 1} # Only multi-replica deployments
operations:
- op: add
path: /metadata/annotations/ha-mode
value: "true"
- target:
kind: Service
group: ""
version: v1
where: ${resource.spec.type == 'LoadBalancer'}
operations:
- op: add
path: /metadata/annotations/external
value: "true"
ForEach Iterationβ
Apply patches iteratively over a list:
patches:
- target:
kind: ConfigMap
group: ""
version: v1
forEach: ${parameters.environments}
var: env
operations:
- op: add
path: /data/${env.name}
value: ${env.value}
- target:
kind: Deployment
group: apps
version: v1
forEach: ${parameters.extraPorts}
var: port
operations:
- op: add
path: /spec/template/spec/containers[?(@.name=='app')]/ports/-
value:
containerPort: ${port.number}
name: ${port.name}
Path Resolution Behaviorβ
| Path Type | Operation | Behavior |
|---|---|---|
| Map key | add | Auto-creates parent maps if missing |
| Map key | replace | Error if target doesn't exist |
| Map key | remove | Idempotent - no error if key doesn't exist |
Filter [?(...)] | add, replace, remove | Error if no match |
| Array index | add, replace, remove | Error if index out of bounds |
Auto-create and idempotent removal - The add operation automatically creates missing parent objects, and remove on map keys succeeds silently if the key doesn't exist. This reduces boilerplate and matches Kubernetes Strategic Merge Patch behavior.
Array indices - All array index operations error on out-of-bounds indices, as this likely indicates a mismatch between the patch and the resource. Use filters like [?(@.name=='app')] instead of positional indices for resilient patches.
Path Escapingβ
Paths use JSON Pointer syntax with special character escaping:
/in a key β~1~in a key β~0
This is commonly needed for Kubernetes annotations that contain /:
# Annotation: kubernetes.io/ingress-class
- op: add
path: /metadata/annotations/kubernetes.io~1ingress-class
value: nginx
# Annotation: sidecar.istio.io/inject
- op: add
path: /spec/template/metadata/annotations/sidecar.istio.io~1inject
value: "true"
# Key containing ~
- op: add
path: /data/config~0backup
value: backup-data
Common Patternsβ
Safe Label Additionβ
# Good - adds individual labels without affecting existing ones
- op: add
path: /metadata/labels/monitoring
value: enabled
# Bad - replaces all existing labels
- op: replace
path: /metadata/labels
value:
monitoring: enabled
Add Sidecar Containerβ
patches:
- target:
kind: Deployment
group: apps
version: v1
operations:
# Add sidecar container
- op: add
path: /spec/template/spec/containers/-
value:
name: fluentd
image: fluent/fluentd:v1.14
volumeMounts:
- name: logs
mountPath: /var/log
# Add shared volume
- op: add
path: /spec/template/spec/volumes/-
value:
name: logs
emptyDir: {}
# Mount to main container
- op: add
path: /spec/template/spec/containers[?(@.name=='app')]/volumeMounts/-
value:
name: logs
mountPath: /app/logs
Volume Mount Injectionβ
- op: add
path: /spec/template/spec/volumes/-
value:
name: config
configMap:
name: ${metadata.name}-config
- op: add
path: /spec/template/spec/containers[?(@.name=='app')]/volumeMounts/-
value:
name: config
mountPath: /etc/config
readOnly: true
Environment Variables from Parametersβ
patches:
- target:
kind: Deployment
group: apps
version: v1
forEach: ${parameters.envVars}
var: envVar
operations:
- op: add
path: /spec/template/spec/containers[?(@.name=='${parameters.containerName}')]/env/-
value:
name: ${envVar.name}
value: ${envVar.value}
Use filters for explicit targeting - Prefer [?(@.name=='app')] over positional indices like [0]. Filters are more resilient to changes in resource structure.
Related Resourcesβ
- Templating Syntax - CEL expressions for dynamic values
- Schema Syntax - Parameter definitions for Traits
- Trait API Reference - Full CRD specification