Skip to content

Commit 6d4aed1

Browse files
feat: Add CEL-based conditional function execution (#4388)
Adds support for CEL expressions in Kptfile pipeline functions via a new 'condition' field. Functions with a condition are only executed if the CEL expression evaluates to true against the current resource list. - Add CELEnvironment in pkg/lib/runneroptions/celenv.go - Integrate condition check in FunctionRunner.Filter (runner.go) - Append skipped result to fnResults when condition is not met - Add 'condition' field to kptfile/v1 Function type - Update all callers of InitDefaults to also call InitCELEnvironment - Add e2e testdata for condition-met and condition-not-met cases - Add unit tests for CEL evaluation and condition checking - Update documentation: kptfile schema and book/04-using-functions - Fix go.mod: mark k8s.io/apiserver as direct dependency Resolves #4388 Signed-off-by: SurbhiAgarwal1 <agarwalsurbhi1807@gmail.com>
1 parent c59c2a6 commit 6d4aed1

32 files changed

Lines changed: 842 additions & 12 deletions

File tree

commands/fn/render/cmdrender.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ type Runner struct {
8585

8686
func (r *Runner) InitDefaults() {
8787
r.RunnerOptions.InitDefaults(runneroptions.GHCRImagePrefix)
88+
// Initialize CEL environment for condition evaluation
89+
// Ignore error as conditions are optional; if CEL init fails, conditions will error at runtime
90+
_ = r.RunnerOptions.InitCELEnvironment()
8891
}
8992

9093
func (r *Runner) preRunE(_ *cobra.Command, args []string) error {

documentation/content/en/book/01-getting-started/_index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ documents for [`kpt fn render`](../../reference/cli/fn/render/) and [`kpt fn eva
4343

4444
### Kubernetes cluster
4545

46-
To deploy the examples, you need a Kubernetes cluster and a configured kubectl context.
46+
To deploy the examples, you need a Kubernetes cluster and a configured kubeconfig context.
4747

4848
For testing purposes, the [kind](https://kind.sigs.k8s.io/docs/user/quick-start/) tool is useful for running an ephemeral Kubernetes
4949
cluster on your local host.
@@ -106,7 +106,7 @@ vim deployment.yaml
106106
#### Automating one-time edits with functions
107107

108108
The [`kpt fn`](../../reference/cli/fn/) set of commands enables you to execute programs called _kpt functions_. These programs are
109-
packaged as containers and take YAML files as input, mutate or validate them, and then output YAML.
109+
packaged as containers and take in YAML files, mutate or validate them, and then output YAML.
110110

111111
For example, you can use a function (`ghcr.io/kptdev/krm-functions-catalog/search-replace:latest`) to search for and replace all the occurrences of the `app` key, in the `spec` section of the YAML document (`spec.**.app`), and set the value to `my-nginx`.
112112

documentation/content/en/book/04-using-functions/_index.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,68 @@ will merge each function pipeline list as an associative list, using
375375
`name` as the merge key. An unspecified `name` or duplicated names may
376376
result in unexpected merges.
377377

378+
### Specifying `condition`
379+
380+
The `condition` field lets you skip a function based on the current state of the resources in the package.
381+
It takes a [CEL](https://cel.dev/) expression that is evaluated against the resource list. If the expression
382+
returns `true`, the function runs. If it returns `false`, the function is skipped.
383+
384+
The expression receives a variable called `resources`, which is a list of all KRM resources passed to
385+
this function step (after `selectors` and `exclude` have been applied). Each resource is a map with
386+
the standard fields: `apiVersion`, `kind`, `metadata`, `spec`, `status`.
387+
388+
For example, only run the `set-labels` function if a `ConfigMap` named `app-config` exists in the package:
389+
390+
```yaml
391+
# wordpress/Kptfile (Excerpt)
392+
apiVersion: kpt.dev/v1
393+
kind: Kptfile
394+
metadata:
395+
name: wordpress
396+
pipeline:
397+
mutators:
398+
- image: ghcr.io/kptdev/krm-functions-catalog/set-labels:latest
399+
configMap:
400+
app: wordpress
401+
condition: resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'app-config')
402+
```
403+
404+
When you render the package, kpt shows whether the function ran or was skipped:
405+
406+
```shell
407+
$ kpt fn render wordpress
408+
Package "wordpress":
409+
410+
[RUNNING] "ghcr.io/kptdev/krm-functions-catalog/set-labels:latest"
411+
[PASS] "ghcr.io/kptdev/krm-functions-catalog/set-labels:latest"
412+
413+
Successfully executed 1 function(s) in 1 package(s).
414+
```
415+
416+
If the condition is not met:
417+
418+
```shell
419+
$ kpt fn render wordpress
420+
Package "wordpress":
421+
422+
[SKIPPED] "ghcr.io/kptdev/krm-functions-catalog/set-labels:latest" (condition not met)
423+
424+
Successfully executed 1 function(s) in 1 package(s).
425+
```
426+
427+
Some useful CEL expression patterns:
428+
429+
- Check if a resource of a specific kind exists:
430+
`resources.exists(r, r.kind == 'Deployment')`
431+
- Check if a specific resource exists by name:
432+
`resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'my-config')`
433+
- Check the count of resources:
434+
`resources.filter(r, r.kind == 'Deployment').size() > 0`
435+
436+
The `condition` field can be combined with `selectors` and `exclude`. The condition is evaluated
437+
after selectors and exclusions are applied, so `resources` only contains the resources that
438+
passed the selection criteria.
439+
378440
### Specifying `selectors`
379441

380442
In some cases, you want to invoke the function only on a subset of resources based on a

documentation/content/en/reference/schema/kptfile/kptfile.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ definitions:
7171
this is primarily used for merging function declaration with upstream counterparts
7272
type: string
7373
x-go-name: Name
74+
condition:
75+
description: |-
76+
`Condition` is an optional CEL expression that determines whether this
77+
function should be executed. The expression is evaluated against the list
78+
of KRM resources passed to this function step (after `Selectors` and
79+
`Exclude` have been applied) and should return a boolean value.
80+
If omitted or evaluates to true, the function executes normally.
81+
If evaluates to false, the function is skipped.
82+
type: string
83+
x-go-name: Condition
7484
selectors:
7585
description: |-
7686
`Selectors` are used to specify resources on which the function should be executed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
actualStripLines:
2+
- " stderr: 'WARNING: The requested image''s platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested'"
3+
4+
stdErrStripLines:
5+
- " Stderr:"
6+
- " \"WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested\""
7+
8+
stdErr: |
9+
Package: "condition-met"
10+
[RUNNING] "ghcr.io/kptdev/krm-functions-catalog/no-op:latest"
11+
[PASS] "ghcr.io/kptdev/krm-functions-catalog/no-op:latest" in 0s
12+
Successfully executed 1 function(s) in 1 package(s).
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
diff --git a/Kptfile b/Kptfile
2+
index 2210a5f..5ccde1c 100755
3+
--- a/Kptfile
4+
+++ b/Kptfile
5+
@@ -1,8 +1,17 @@
6+
-apiVersion: kpt.dev/v1
7+
-kind: Kptfile
8+
-metadata:
9+
- name: app
10+
-pipeline:
11+
- mutators:
12+
- - image: ghcr.io/kptdev/krm-functions-catalog/no-op
13+
- condition: "resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'app-config')"
14+
+apiVersion: kpt.dev/v1
15+
+kind: Kptfile
16+
+metadata:
17+
+ name: app
18+
+pipeline:
19+
+ mutators:
20+
+ - image: ghcr.io/kptdev/krm-functions-catalog/no-op
21+
+ condition: resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'app-config')
22+
+status:
23+
+ conditions:
24+
+ - type: Rendered
25+
+ status: "True"
26+
+ reason: RenderSuccess
27+
+ renderStatus:
28+
+ mutationSteps:
29+
+ - image: ghcr.io/kptdev/krm-functions-catalog/no-op:latest
30+
+ exitCode: 0
31+
diff --git a/resources.yaml b/resources.yaml
32+
index 2f4405b..47bec8b 100755
33+
--- a/resources.yaml
34+
+++ b/resources.yaml
35+
@@ -1,13 +1,13 @@
36+
-apiVersion: v1
37+
-kind: ConfigMap
38+
-metadata:
39+
- name: app-config
40+
-data:
41+
- key: value
42+
----
43+
-apiVersion: apps/v1
44+
-kind: Deployment
45+
-metadata:
46+
- name: my-app
47+
-spec:
48+
- replicas: 1
49+
+apiVersion: v1
50+
+kind: ConfigMap
51+
+metadata:
52+
+ name: app-config
53+
+data:
54+
+ key: value
55+
+---
56+
+apiVersion: apps/v1
57+
+kind: Deployment
58+
+metadata:
59+
+ name: my-app
60+
+spec:
61+
+ replicas: 1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.expected
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: kpt.dev/v1
2+
kind: Kptfile
3+
metadata:
4+
name: app
5+
pipeline:
6+
mutators:
7+
- image: ghcr.io/kptdev/krm-functions-catalog/no-op
8+
condition: "resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'app-config')"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: app-config
5+
data:
6+
key: value
7+
---
8+
apiVersion: apps/v1
9+
kind: Deployment
10+
metadata:
11+
name: my-app
12+
spec:
13+
replicas: 1
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
actualStripLines:
2+
- " stderr: 'WARNING: The requested image''s platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested'"
3+
4+
stdErrStripLines:
5+
- " Stderr:"
6+
- " \"WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested\""
7+
8+
stdErr: |
9+
Package: "condition-not-met"
10+
[SKIPPED] "ghcr.io/kptdev/krm-functions-catalog/no-op:latest" (condition not met)
11+
Successfully executed 0 function(s) in 1 package(s).

0 commit comments

Comments
 (0)