Skip to content

Commit 3babba5

Browse files
author
sai kiran pikili
committed
feat: add operations field to mutation ApplyTo spec
Add granular operation control to Gatekeeper mutations by introducing an 'operations' field in the ApplyTo specification. This allows users to specify which Kubernetes admission operations (CREATE, UPDATE, DELETE) should trigger mutations. Key Changes: - Add Operations []string field to ApplyTo struct - Update mutation matching logic to check operation compatibility - Add Operation field to Mutable type for context propagation - Maintain backward compatibility (defaults to CREATE+UPDATE) - Add comprehensive unit tests for operation matching - Update documentation with usage examples and best practices - Include practical examples demonstrating various operation patterns Fixes: Resolves issues with mutations attempting to modify immutable fields during UPDATE operations, such as Pod environment variables in CronJob scenarios. Backward Compatibility: Existing mutations without operations field continue working unchanged with default CREATE+UPDATE behavior. Testing: Added unit tests, live cluster validation, and comprehensive examples covering all supported operation combinations. Signed-off-by: sai kiran pikili <saikiranpikili@localhost.localdomain>
1 parent 4ecfddd commit 3babba5

File tree

13 files changed

+590
-11
lines changed

13 files changed

+590
-11
lines changed

config/crd/bases/expansion.gatekeeper.sh_expansiontemplate.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ spec:
5656
items:
5757
type: string
5858
type: array
59+
operations:
60+
items:
61+
type: string
62+
type: array
5963
versions:
6064
items:
6165
type: string
@@ -173,6 +177,10 @@ spec:
173177
items:
174178
type: string
175179
type: array
180+
operations:
181+
items:
182+
type: string
183+
type: array
176184
versions:
177185
items:
178186
type: string

config/crd/bases/mutations.gatekeeper.sh_assign.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ spec:
5757
items:
5858
type: string
5959
type: array
60+
operations:
61+
items:
62+
type: string
63+
type: array
6064
versions:
6165
items:
6266
type: string
@@ -424,6 +428,10 @@ spec:
424428
items:
425429
type: string
426430
type: array
431+
operations:
432+
items:
433+
type: string
434+
type: array
427435
versions:
428436
items:
429437
type: string
@@ -791,6 +799,10 @@ spec:
791799
items:
792800
type: string
793801
type: array
802+
operations:
803+
items:
804+
type: string
805+
type: array
794806
versions:
795807
items:
796808
type: string

config/crd/bases/mutations.gatekeeper.sh_assignimage.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ spec:
5757
items:
5858
type: string
5959
type: array
60+
operations:
61+
items:
62+
type: string
63+
type: array
6064
versions:
6165
items:
6266
type: string

config/crd/bases/mutations.gatekeeper.sh_modifyset.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ spec:
5959
items:
6060
type: string
6161
type: array
62+
operations:
63+
items:
64+
type: string
65+
type: array
6266
versions:
6367
items:
6468
type: string
@@ -393,6 +397,10 @@ spec:
393397
items:
394398
type: string
395399
type: array
400+
operations:
401+
items:
402+
type: string
403+
type: array
396404
versions:
397405
items:
398406
type: string
@@ -727,6 +735,10 @@ spec:
727735
items:
728736
type: string
729737
type: array
738+
operations:
739+
items:
740+
type: string
741+
type: array
730742
versions:
731743
items:
732744
type: string

examples/README.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Gatekeeper Mutation Examples
2+
3+
This directory contains examples demonstrating Gatekeeper mutation capabilities, including the new `operations` field for granular operation control.
4+
5+
## Operations Field Overview
6+
7+
The `operations` field in the `applyTo` section allows you to specify which Kubernetes admission operations should trigger mutations:
8+
9+
- **`CREATE`** - Apply mutation when resources are created
10+
- **`UPDATE`** - Apply mutation when resources are updated
11+
- **`DELETE`** - Apply mutation when resources are deleted (advanced use cases)
12+
13+
### Default Behavior (Backward Compatibility)
14+
15+
If no `operations` field is specified, mutations default to `["CREATE", "UPDATE"]` to maintain compatibility with existing deployments.
16+
17+
## Example Files
18+
19+
### assign-with-operations.yaml
20+
21+
Demonstrates various patterns for using the `operations` field:
22+
23+
1. **CREATE-only mutation** - Solves immutable field issues during updates
24+
2. **Explicit CREATE+UPDATE** - Shows how to explicitly specify default behavior
25+
3. **Legacy compatibility** - Existing mutations without operations field
26+
4. **DELETE operations** - Advanced use cases (use with caution)
27+
5. **All operations** - Comprehensive mutation coverage
28+
29+
## Common Use Cases
30+
31+
### Solving Immutable Field Issues
32+
33+
Many Kubernetes resources have immutable fields that cannot be changed after creation. Setting environment variables on Pods is a common example:
34+
35+
```yaml
36+
operations: ["CREATE"] # Only mutate during creation
37+
location: "spec.containers[name:*].env[name:MY_VAR].value"
38+
```
39+
40+
### Default Value Setting
41+
42+
For fields that should have defaults on creation but can be changed later:
43+
44+
```yaml
45+
operations: ["CREATE"]
46+
location: "spec.containers[name:*].imagePullPolicy"
47+
parameters:
48+
assign:
49+
value: "IfNotPresent"
50+
```
51+
52+
### Dynamic Updates
53+
54+
For mutations that should apply whenever resources are modified:
55+
56+
```yaml
57+
operations: ["UPDATE"]
58+
location: "metadata.labels.last-updated"
59+
parameters:
60+
assign:
61+
fromMetadata:
62+
field: "timestamp"
63+
```
64+
65+
### Legacy Compatibility
66+
67+
Existing mutations continue to work without changes:
68+
69+
```yaml
70+
# No operations field = ["CREATE", "UPDATE"] behavior
71+
applyTo:
72+
- groups: [""]
73+
kinds: ["Pod"]
74+
versions: ["v1"]
75+
```
76+
77+
## Migration Guide
78+
79+
### From Existing Mutations
80+
81+
1. **No action required** - Existing mutations work unchanged
82+
2. **Gradual adoption** - Add operations field to new mutations as needed
83+
3. **Optional updates** - Update existing mutations only if you need different operation behavior
84+
85+
### Best Practices
86+
87+
1. **Start with CREATE-only** for immutable fields
88+
2. **Use explicit operations** for clarity in new mutations
89+
3. **Avoid DELETE operations** unless you have specific cleanup requirements
90+
4. **Test thoroughly** when changing operation behavior on existing mutations
91+
92+
## Related Documentation
93+
94+
- [Mutation Documentation](https://open-policy-agent.github.io/gatekeeper/website/docs/mutation/)
95+
- [Gatekeeper Installation](https://open-policy-agent.github.io/gatekeeper/website/docs/install/)
96+
- [Policy Library](https://open-policy-agent.github.io/gatekeeper-library/website/)
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Example 1: CREATE-only mutation
2+
# This addresses the common issue where mutations on UPDATE operations
3+
# fail due to immutable field constraints (e.g., Pod env variables)
4+
apiVersion: mutations.gatekeeper.sh/v1
5+
kind: Assign
6+
metadata:
7+
name: assign-otel-protocol-create-only
8+
spec:
9+
applyTo:
10+
- groups: [""]
11+
kinds: ["Pod"]
12+
versions: ["v1"]
13+
operations: ["CREATE"] # Only apply on CREATE operations
14+
match:
15+
scope: Namespaced
16+
namespaces:
17+
- "*"
18+
excludedNamespaces:
19+
- "kube-system"
20+
location: "spec.containers[name:*].env[name:OTEL_EXPORTER_OTLP_PROTOCOL].value"
21+
parameters:
22+
assign:
23+
value: "grpc"
24+
25+
---
26+
# Example 2: Explicit CREATE and UPDATE mutation
27+
# Shows how to explicitly specify the default behavior
28+
apiVersion: mutations.gatekeeper.sh/v1
29+
kind: Assign
30+
metadata:
31+
name: assign-security-context-create-update
32+
spec:
33+
applyTo:
34+
- groups: [""]
35+
kinds: ["Pod"]
36+
versions: ["v1"]
37+
operations: ["CREATE", "UPDATE"] # Apply on both CREATE and UPDATE
38+
match:
39+
scope: Namespaced
40+
location: "spec.securityContext.runAsNonRoot"
41+
parameters:
42+
assign:
43+
value: true
44+
45+
---
46+
# Example 3: Legacy mutation (backward compatibility)
47+
# Demonstrates that existing mutations without operations field continue to work
48+
apiVersion: mutations.gatekeeper.sh/v1
49+
kind: Assign
50+
metadata:
51+
name: assign-legacy-without-operations
52+
spec:
53+
applyTo:
54+
- groups: [""]
55+
kinds: ["Pod"]
56+
versions: ["v1"]
57+
# No operations field - defaults to CREATE and UPDATE for backward compatibility
58+
match:
59+
scope: Namespaced
60+
location: "metadata.labels.legacy-mutator"
61+
parameters:
62+
assign:
63+
value: "true"
64+
65+
---
66+
# Example 4: DELETE operation mutation (advanced use case)
67+
# Use with caution - most mutations don't need to run on DELETE
68+
apiVersion: mutations.gatekeeper.sh/v1
69+
kind: Assign
70+
metadata:
71+
name: assign-delete-example
72+
spec:
73+
applyTo:
74+
- groups: [""]
75+
kinds: ["Pod"]
76+
versions: ["v1"]
77+
operations: ["DELETE"] # Example: Apply only on DELETE operations (if you have a use case)
78+
match:
79+
scope: Namespaced
80+
location: "metadata.labels.deletion-timestamp"
81+
parameters:
82+
assign:
83+
value: "processing"
84+
85+
---
86+
# Example 5: All operations mutation
87+
# Demonstrates applying mutations on all possible operations
88+
apiVersion: mutations.gatekeeper.sh/v1
89+
kind: Assign
90+
metadata:
91+
name: assign-all-operations
92+
spec:
93+
applyTo:
94+
- groups: [""]
95+
kinds: ["Pod"]
96+
versions: ["v1"]
97+
operations: ["CREATE", "UPDATE", "DELETE"] # Apply on all operations
98+
match:
99+
scope: Namespaced
100+
location: "metadata.labels.lifecycle-stage"
101+
parameters:
102+
assign:
103+
value: "managed"

pkg/mutation/match/apply_to.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,26 @@ func AppliesTo(applyTo []ApplyTo, gvk schema.GroupVersionKind) bool {
1212
return false
1313
}
1414

15-
// ApplyTo determines what GVKs items the mutation should apply to.
15+
// AppliesOperationTo checks if any item in the given slice of ApplyTo allows the given operation.
16+
func AppliesOperationTo(applyTo []ApplyTo, operation string) bool {
17+
for _, apply := range applyTo {
18+
if apply.MatchesOperation(operation) {
19+
return true
20+
}
21+
}
22+
return false
23+
}
24+
25+
// ApplyTo determines what GVKs and operations the mutation should apply to.
1626
// Globs are not allowed.
1727
// +kubebuilder:object:generate=true
1828
type ApplyTo struct {
19-
Groups []string `json:"groups,omitempty"`
20-
Kinds []string `json:"kinds,omitempty"`
21-
Versions []string `json:"versions,omitempty"`
29+
Groups []string `json:"groups,omitempty"`
30+
Kinds []string `json:"kinds,omitempty"`
31+
Versions []string `json:"versions,omitempty"`
32+
// Operations specifies which admission operations (CREATE, UPDATE, DELETE) should trigger
33+
// this mutation. If empty, defaults to ["CREATE", "UPDATE"] for backward compatibility.
34+
Operations []string `json:"operations,omitempty"`
2235
}
2336

2437
// Flatten returns the set of GroupVersionKinds this ApplyTo matches.
@@ -55,3 +68,17 @@ func (a ApplyTo) Matches(gvk schema.GroupVersionKind) bool {
5568

5669
return true
5770
}
71+
72+
// MatchesOperation returns true if the operation is contained in the ApplyTo's
73+
// operations list. If no operations are specified, it defaults to allowing
74+
// CREATE and UPDATE for backward compatibility. Users can explicitly specify
75+
// DELETE operations if they have legitimate use cases.
76+
func (a ApplyTo) MatchesOperation(operation string) bool {
77+
// If no operations specified, default to CREATE and UPDATE for backward compatibility
78+
if len(a.Operations) == 0 {
79+
return operation == "CREATE" || operation == "UPDATE"
80+
}
81+
82+
// Check if the operation is explicitly allowed by the user
83+
return contains(a.Operations, operation)
84+
}

0 commit comments

Comments
 (0)