Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
aec1d1e
feat: Add CEL-based conditional function execution
SurbhiAgarwal1 Mar 17, 2026
84e1d94
fix: remove k8s.io/apiserver dependency from CEL evaluator
SurbhiAgarwal1 Mar 19, 2026
08742aa
refactor: move CELEnvironment to runneroptions, compile program per E…
SurbhiAgarwal1 Mar 19, 2026
df38833
Potential fix for pull request finding
SurbhiAgarwal1 Mar 19, 2026
6d13e92
refactor: replace Go unit e2e test with proper fn-render testdata
SurbhiAgarwal1 Mar 19, 2026
5573f7a
fix: remove k8s.io/apiserver from go.mod, fix celeval_test.go imports
SurbhiAgarwal1 Mar 19, 2026
2f777f8
fix: nil CELEnvironment in cmdeval test struct comparison
SurbhiAgarwal1 Mar 19, 2026
259e803
fix: two e2e test failures in condition testdata
SurbhiAgarwal1 Mar 19, 2026
728c384
fix: update condition testdata diff.patch with correct expected output
SurbhiAgarwal1 Mar 21, 2026
caebfa4
fix: correct e2e testdata and clarify k8s CEL library decision
SurbhiAgarwal1 Mar 26, 2026
6d1fb6b
fix: update diff.patch with exact hashes from CI output
SurbhiAgarwal1 Mar 26, 2026
1782849
feat: add k8s.io/apiserver CEL library extensions (IP, CIDR, Quantity…
SurbhiAgarwal1 Mar 28, 2026
f08fcb8
fix: correct SemVer function name to SemverLib in k8s CEL library
SurbhiAgarwal1 Mar 28, 2026
2f3382c
fix: increase reconcile-timeout from 2m to 5m in live-apply e2e tests
SurbhiAgarwal1 Mar 28, 2026
bbf9321
fix: address Copilot review comments
SurbhiAgarwal1 Mar 29, 2026
ffeadea
fix: address PR review feedback from mozesl-nokia and Copilot
SurbhiAgarwal1 Apr 2, 2026
910fbd5
Merge upstream main into feat/cel-conditional-execution
SurbhiAgarwal1 Apr 3, 2026
367b250
chore: trigger CI rebuild
SurbhiAgarwal1 Apr 3, 2026
19bf57c
chore: Remove unwanted files and fix test expectations
SurbhiAgarwal1 Apr 3, 2026
b6f3d8e
docs: add condition field documentation for CEL-based conditional fun…
SurbhiAgarwal1 Apr 3, 2026
b04a856
Implement Kpt Function Conditional Rendering (Nephio #1084)
SurbhiAgarwal1 Apr 4, 2026
c396947
chore: update kyaml/kustomize API versions to current versions
SurbhiAgarwal1 Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions commands/fn/render/cmdrender.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ type Runner struct {

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

func (r *Runner) preRunE(_ *cobra.Command, args []string) error {
Expand Down
62 changes: 62 additions & 0 deletions documentation/content/en/book/04-using-functions/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,68 @@ will merge each function pipeline list as an associative list, using
`name` as the merge key. An unspecified `name` or duplicated names may
result in unexpected merges.

### Specifying `condition`

The `condition` field lets you skip a function based on the current state of the resources in the package.
It takes a [CEL](https://cel.dev/) expression that is evaluated against the resource list. If the expression
returns `true`, the function runs. If it returns `false`, the function is skipped.

The expression receives a variable called `resources`, which is a list of all KRM resources passed to
this function step (after `selectors` and `exclude` have been applied). Each resource is a map with
the standard fields: `apiVersion`, `kind`, `metadata`, `spec`, `status`.

For example, only run the `set-labels` function if a `ConfigMap` named `app-config` exists in the package:

```yaml
# wordpress/Kptfile (Excerpt)
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: wordpress
pipeline:
mutators:
- image: ghcr.io/kptdev/krm-functions-catalog/set-labels:latest
configMap:
app: wordpress
condition: resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'app-config')
```

When you render the package, kpt shows whether the function ran or was skipped:

```shell
$ kpt fn render wordpress
Package "wordpress":

[RUNNING] "ghcr.io/kptdev/krm-functions-catalog/set-labels:latest"
[PASS] "ghcr.io/kptdev/krm-functions-catalog/set-labels:latest"

Successfully executed 1 function(s) in 1 package(s).
```

If the condition is not met:

```shell
$ kpt fn render wordpress
Package "wordpress":

[SKIPPED] "ghcr.io/kptdev/krm-functions-catalog/set-labels:latest" (condition not met)

Successfully executed 1 function(s) in 1 package(s).
```

Some useful CEL expression patterns:

- Check if a resource of a specific kind exists:
`resources.exists(r, r.kind == 'Deployment')`
- Check if a specific resource exists by name:
`resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'my-config')`
- Check the count of resources:
`resources.filter(r, r.kind == 'Deployment').size() > 0`

The `condition` field can be combined with `selectors` and `exclude`. The condition is evaluated
after selectors and exclusions are applied, so `resources` only contains the resources that
passed the selection criteria.

### Specifying `selectors`

In some cases, you want to invoke the function only on a subset of resources based on a
Expand Down
10 changes: 10 additions & 0 deletions documentation/content/en/reference/schema/kptfile/kptfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ definitions:
this is primarily used for merging function declaration with upstream counterparts
type: string
x-go-name: Name
condition:
description: |-
`Condition` is an optional CEL expression that determines whether this
function should be executed. The expression is evaluated against the list
of KRM resources passed to this function step (after `Selectors` and
`Exclude` have been applied) and should return a boolean value.
If omitted or evaluates to true, the function executes normally.
If evaluates to false, the function is skipped.
type: string
x-go-name: Condition
selectors:
description: |-
`Selectors` are used to specify resources on which the function should be executed
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
actualStripLines:
- " 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'"

stdErrStripLines:
- " 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\""

stdErr: |
Package: "condition-met"
[RUNNING] "ghcr.io/kptdev/krm-functions-catalog/no-op"
[PASS] "ghcr.io/kptdev/krm-functions-catalog/no-op" in 0s
Successfully executed 1 function(s) in 1 package(s).
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
diff --git a/Kptfile b/Kptfile
index eb90ac3..ace574a 100644
--- a/Kptfile
+++ b/Kptfile
@@ -5,4 +5,12 @@ metadata:
pipeline:
mutators:
- image: ghcr.io/kptdev/krm-functions-catalog/no-op
- condition: "resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'app-config')"
+ condition: resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'app-config')
+status:
+ conditions:
+ - type: Rendered
+ status: "True"
+ reason: RenderSuccess
+ renderStatus:
+ mutationSteps:
+ - image: ghcr.io/kptdev/krm-functions-catalog/no-op
+ exitCode: 0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.expected
8 changes: 8 additions & 0 deletions e2e/testdata/fn-render/condition/condition-met/Kptfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: app
pipeline:
mutators:
- image: ghcr.io/kptdev/krm-functions-catalog/no-op
condition: "resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'app-config')"
13 changes: 13 additions & 0 deletions e2e/testdata/fn-render/condition/condition-met/resources.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
key: value
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
actualStripLines:
- " 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'"

stdErrStripLines:
- " 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\""

stdErr: |
Package: "condition-not-met"
[SKIPPED] "ghcr.io/kptdev/krm-functions-catalog/no-op" (condition not met)
Successfully executed 1 function(s) in 1 package(s).
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
diff --git a/Kptfile b/Kptfile
index eb90ac3..ace574a 100644
--- a/Kptfile
+++ b/Kptfile
@@ -5,4 +5,11 @@ metadata:
pipeline:
mutators:
- image: ghcr.io/kptdev/krm-functions-catalog/no-op
- condition: "resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'app-config')"
+ condition: resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'app-config')
+status:
+ conditions:
+ - type: Rendered
+ status: "True"
+ reason: RenderSuccess
+ renderStatus:
+ mutationSteps: []
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.expected
8 changes: 8 additions & 0 deletions e2e/testdata/fn-render/condition/condition-not-met/Kptfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: app
pipeline:
mutators:
- image: ghcr.io/kptdev/krm-functions-catalog/no-op
condition: "resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'app-config')"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
2 changes: 1 addition & 1 deletion e2e/testdata/live-apply/apply-depends-on/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ parallel: true
kptArgs:
- "live"
- "apply"
- "--reconcile-timeout=2m"
- "--reconcile-timeout=5m"

stdOut: |
inventory update started
Expand Down
2 changes: 1 addition & 1 deletion e2e/testdata/live-apply/json-output/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ kptArgs:
- "live"
- "apply"
- "--output=json"
- "--reconcile-timeout=2m"
- "--reconcile-timeout=5m"
stdOut: |
{"action":"Inventory","status":"Started","timestamp":"<TIMESTAMP>","type":"group"}
{"action":"Inventory","status":"Finished","timestamp":"<TIMESTAMP>","type":"group"}
Expand Down
31 changes: 18 additions & 13 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/bytecodealliance/wasmtime-go v1.0.0
github.com/cpuguy83/go-md2man/v2 v2.0.7
github.com/go-errors/errors v1.5.1
github.com/google/cel-go v0.26.0
github.com/google/go-cmp v0.7.0
github.com/google/go-containerregistry v0.20.6
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
Expand All @@ -25,26 +26,28 @@ require (
golang.org/x/text v0.31.0
gopkg.in/yaml.v2 v2.4.0
gotest.tools v2.2.0+incompatible
k8s.io/api v0.34.1
k8s.io/api v0.35.0
k8s.io/apiextensions-apiserver v0.34.1
k8s.io/apimachinery v0.34.1
k8s.io/cli-runtime v0.34.1
k8s.io/client-go v0.34.1
k8s.io/component-base v0.34.1
k8s.io/apimachinery v0.35.0
k8s.io/cli-runtime v0.35.0
k8s.io/client-go v0.35.0
k8s.io/component-base v0.35.0
k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912
k8s.io/kubectl v0.34.1
k8s.io/kubectl v0.35.0
sigs.k8s.io/cli-utils v0.37.2
sigs.k8s.io/controller-runtime v0.22.4
sigs.k8s.io/kustomize/api v0.20.1
sigs.k8s.io/kustomize/kyaml v0.20.1
sigs.k8s.io/kustomize/api v0.21.0
sigs.k8s.io/kustomize/kyaml v0.21.0
sigs.k8s.io/yaml v1.6.0
)

require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
Expand Down Expand Up @@ -79,7 +82,6 @@ require (
github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
Expand All @@ -88,14 +90,12 @@ require (
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/onsi/gomega v1.37.0 // indirect
github.com/onsi/gomega v1.38.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/otiai10/mint v1.6.3 // indirect
Expand All @@ -109,24 +109,29 @@ require (
github.com/sergi/go-diff v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spyzhov/ajson v0.9.6 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/vbatts/tar-split v0.12.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/oauth2 v0.32.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/component-helpers v0.34.1 // indirect
k8s.io/apiserver v0.34.1
k8s.io/component-helpers v0.35.0 // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
Expand Down
Loading
Loading