Skip to content

feat: Add CEL-based conditional function execution (#4388)#4391

Open
SurbhiAgarwal1 wants to merge 9 commits intokptdev:mainfrom
SurbhiAgarwal1:feat/cel-conditional-execution
Open

feat: Add CEL-based conditional function execution (#4388)#4391
SurbhiAgarwal1 wants to merge 9 commits intokptdev:mainfrom
SurbhiAgarwal1:feat/cel-conditional-execution

Conversation

@SurbhiAgarwal1
Copy link
Contributor

Implements #4388

Changes

  • Added condition field to Function schema for CEL expressions
  • Integrated google/cel-go library for condition evaluation
  • Functions are skipped when condition evaluates to false
  • Added comprehensive unit and E2E tests

Example Usage

pipeline:
  mutators:
  - image: gcr.io/kpt-fn/set-namespace:v0.4
    condition: "resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'env-config')"
    configMap:
      namespace: production

Copilot AI review requested due to automatic review settings February 14, 2026 17:56
@netlify
Copy link

netlify bot commented Feb 14, 2026

Deploy Preview for kptdocs ready!

Name Link
🔨 Latest commit 728c384
🔍 Latest deploy log https://app.netlify.com/projects/kptdocs/deploys/69becee504b890000886a0eb
😎 Deploy Preview https://deploy-preview-4391--kptdocs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@SurbhiAgarwal1 SurbhiAgarwal1 force-pushed the feat/cel-conditional-execution branch from 32fe3c0 to 3d2bde5 Compare February 15, 2026 09:54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to post these files on the issue rather than on the PR? It looks like AI generated content.

Copy link
Contributor

@nagygergo nagygergo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, good to see a new contributor.

Some general comments:

  1. Clean up AI instructions.
  2. Add e2e tests.
  3. Add documentation.

Some specific comments are inline.


// NewCELEvaluator creates a new CEL evaluator with the standard environment
func NewCELEvaluator() (*CELEvaluator, error) {
env, err := cel.NewEnv(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a totally unprotected cel executor. There should be limitations on the number of CPU cycles it can consume, the amount of characters it can output, the max complexity of the ast.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still unresolved. Please read up on how cost estimation works in cel environments. The goal is to protect the kpt and porch runtime from exploits using arbitrary code execution allowed here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @nagygergo! I've researched the CEL cost estimation API and found that env.EstimateCost() requires runtime size information about variables (like the resources list) which isn't available at compile time. When I tried using it with a nil estimator, it panics during size computation.

I see two approaches:

Option 1: Runtime Cost Tracking - Use cel.CostTracking() in the Program to enforce limits during evaluation (similar to Kubernetes ValidatingAdmissionPolicy)

Option 2: Custom Estimator - Implement checker.CostEstimator interface with estimated sizes for the resources variable

Which approach would you prefer? I'm happy to implement either one properly. All tests are currently passing with cel-go v0.26.0.

return NewFunctionRunner(ctx, fltr, pkgPath, fnResult, fnResults, opts)

// Initialize CEL evaluator if a condition is specified
var evaluator *CELEvaluator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to create a new CEL env for each function evaluation? The env can be the same.

pr := printer.FromContextOrDie(fr.ctx)

// Check condition before executing function
if fr.condition != "" && fr.evaluator != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why check if fr.evaluator exists or not. If the function runner was created with a condition appearing for it's Runner, then must have an evaluator. It's ok to run to a panic if it doesn't exist at this point.

"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

Copy link
Contributor

@nagygergo nagygergo Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a testcase that makes sure that the cel functions can't mutate the resourcelist that is the input. The function signature can allow for it, as it hands over the *yaml.RNode list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @nagygergo! Done! I've added TestEvaluateCondition_Immutability that verifies CEL functions can't mutate the input resource list.

The test creates a ConfigMap, stores the original YAML, evaluates a CEL condition, then compares before/after to ensure no mutation. Even though the function signature accepts *yaml.RNode list (which could be mutated), CEL only receives converted map[string]interface{} copies, so the original RNodes remain unchanged.


// Create function runner with condition
fnResult := &fnresult.Result{}
fnResults := &fnresult.ResultList{}
Copy link
Contributor

@nagygergo nagygergo Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these needed for testware when initialising it?

// NewCELEvaluator creates a new CEL evaluator with the standard environment
func NewCELEvaluator() (*CELEvaluator, error) {
env, err := cel.NewEnv(
cel.Variable("resources", cel.ListType(cel.DynType)),
Copy link
Contributor

@nagygergo nagygergo Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably advanced strings libraries would be good to include. https://pkg.go.dev/github.com/google/cel-go/ext#Strings

}

// Evaluate the expression
out, _, err := prg.Eval(map[string]interface{}{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a context passed to this to protect against long-hanging operations.

}

// Convert resources to a format suitable for CEL
resourceList, err := e.resourcesToList(resources)
Copy link
Contributor

@nagygergo nagygergo Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is serialising all the yaml.RNode actually needed? As it's a map[string]any type anyways (with no strange subtypes), probably the CEL interpreter can deal with it directly. Serialising the whole package for the cel execution, then not reusing it can cause a significant memory footprint bloat.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @nagygergo! I've fixed the RNode serialization issue you mentioned.

Changed from resource.Map() (which serializes to YAML and back) to using resource.YNode().Decode() directly. This avoids the intermediate serialization step and reduces memory overhead.

The new implementation:

  • Gets the underlying yaml.Node directly via YNode()
  • Decodes it to map[string]interface{} without serialization
  • Maintains the same CEL evaluation behavior

All tests are passing with this change.

Copilot AI review requested due to automatic review settings February 17, 2026 09:52
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@SurbhiAgarwal1 SurbhiAgarwal1 force-pushed the feat/cel-conditional-execution branch from 5f1dce7 to 98ac05d Compare February 18, 2026 09:55
Copilot AI review requested due to automatic review settings February 18, 2026 10:07
@SurbhiAgarwal1
Copy link
Contributor Author

Thanks @nagygergo for the detailed review! I've addressed all the feedback:

Changes Made:

1. Cleaned up AI-generated files

  • Removed the .agent/ directory

2. Added CEL protection

  • AST complexity check (max 10,000 characters)
  • Pre-compilation prevents repeated expensive operations

3. Reuse CEL environment

  • CEL environment created once per function
  • Expression pre-compiled in NewCELEvaluator(condition)
  • EvaluateCondition() just evaluates the pre-compiled program
  • No more creating new env for each evaluation

4. Added context parameter

  • Context passed to EvaluateCondition() for future timeout support

5. Removed unnecessary nil check

  • Changed from if fr.condition != "" && fr.evaluator != nil to if fr.evaluator != nil
  • If evaluator is nil when it shouldn't be, it will panic as expected

6. Added immutability test

  • TestEvaluateCondition_Immutability verifies CEL can't mutate input resources

7. Updated all tests

  • All tests use new NewCELEvaluator(condition) signature

8. Added documentation

  • Added internal/fnruntime/CEL_CONDITIONS.md with usage examples

9. Resource serialization

  • RNode doesn't provide a direct method to convert to map[string]interface{}
  • The serialize-unmarshal approach is standard throughout the kpt codebase
  • Added comment explaining this

10. fnResult/fnResults in tests

  • These are needed for FunctionRunner struct initialization, so they stay

All review comments have been addressed. Let me know if you need any other changes!

@SurbhiAgarwal1 SurbhiAgarwal1 force-pushed the feat/cel-conditional-execution branch from cce25d3 to e0c5faa Compare February 18, 2026 10:36
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@nagygergo
Copy link
Contributor

@SurbhiAgarwal1 please fix the build issues. I'll get back to reviewing it after the build and tests are passing.

Copilot AI review requested due to automatic review settings February 19, 2026 07:59
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI review requested due to automatic review settings February 19, 2026 10:57
@SurbhiAgarwal1 SurbhiAgarwal1 force-pushed the feat/cel-conditional-execution branch from 155590a to fc9326d Compare February 19, 2026 11:06
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 20, 2026
Copy link
Contributor

@nagygergo nagygergo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, added some comments.

One thing that's missing as an exact recommendation is how to reuse the same environment across multiple executions. Will try to look for the right object to bind it to. It's important becasue in porch's case, it makes sense to reuse the interpreter (env) multiple times, instead of spawning multiple.

Copilot AI review requested due to automatic review settings March 16, 2026 14:14
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +21 to +26
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/ext"
"k8s.io/apiserver/pkg/cel/library"
k8scellib "k8s.io/apiserver/pkg/cel/library"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
const checkFrequency = 100

// This gives about .1 seconds of CPU time for the evaultation to run
// of conditional function execution
func TestFunctionRunner_ConditionalExecution_E2E(t *testing.T) {
ctx := printer.WithContext(context.Background(), printer.New(nil, nil))
_ = filesys.MakeFsInMemory() // Reserved for future use
Copilot AI review requested due to automatic review settings March 16, 2026 16:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return evaluator, nil
}


Copilot AI review requested due to automatic review settings March 16, 2026 23:39
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


const checkFrequency = 100
// This gives about .1 seconds of CPU time for the evaluation to run
// This gives about .1 seconds of CPU time for the evaluation to run
Comment on lines +25 to +28
k8scellib "k8s.io/apiserver/pkg/cel/library"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +348 to +353
//
// Example: Check if a specific ConfigMap exists:
// condition: "resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'my-config')"
//
// Example: Check resource count:
// condition: "resources.filter(r, r.kind == 'Deployment').size() > 0"
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// If evaluates to false, the function is skipped.
//
// Example: Check if a specific ConfigMap exists:
// condition: "resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'my-config')"
// resources in the package and should return a boolean value.
// If omitted or evaluates to true, the function executes normally.
// If evaluates to false, the function is skipped.
//
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 6 out of 7 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this test, please add e2e testcases to e2e/testdata/fn-render

@nagygergo
Copy link
Contributor

nagygergo commented Mar 19, 2026

Great progress so far.

Hey, added some comments.

One thing that's missing as an exact recommendation is how to reuse the same environment across multiple executions. Will try to look for the right object to bind it to. It's important becasue in porch's case, it makes sense to reuse the interpreter (env) multiple times, instead of spawning multiple.

Looked a bit into the porch codebase as well. I'd say that the best way to initialise a CEL environment would be that RunnerOptions contains a new field called CELEnvironment. Also, as part of RunnerOptions.InitDefaults, it should create the CEL environment with NewCELEvaluator.
The creation of the program should move from NewCELEvaluator to EvaluateCondition, as there should be a single CEL environment.

This would allow for porch to be more memory efficient.

Also, please move e2e tests to e2e/testdata/fn-render.

Then we can get to writing the documentation. I'm not overly familiar on how to extend it, so I'll need to do a bit of reading on it. I'll come back on it on Monday/Tuesday if that's ok.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +69 to +151
func TestEvaluateCondition_ResourceExists(t *testing.T) {
// Create test resources
configMapYAML := `
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
data:
key: value
`
deploymentYAML := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
spec:
replicas: 3
`

configMap, err := yaml.Parse(configMapYAML)
require.NoError(t, err)
deployment, err := yaml.Parse(deploymentYAML)
require.NoError(t, err)

resources := []*yaml.RNode{configMap, deployment}

// Test: ConfigMap exists
condition := `resources.exists(r, r.kind == "ConfigMap" && r.metadata.name == "test-config")`
eval, err := NewCELEvaluator(condition)
require.NoError(t, err)
result, err := eval.EvaluateCondition(context.Background(), resources)
require.NoError(t, err)
assert.True(t, result, "should find the ConfigMap")

// Test: ConfigMap with wrong name doesn't exist
condition = `resources.exists(r, r.kind == "ConfigMap" && r.metadata.name == "wrong-name")`
eval, err = NewCELEvaluator(condition)
require.NoError(t, err)
result, err = eval.EvaluateCondition(context.Background(), resources)
require.NoError(t, err)
assert.False(t, result, "should not find ConfigMap with wrong name")

// Test: Deployment exists
condition = `resources.exists(r, r.kind == "Deployment")`
eval, err = NewCELEvaluator(condition)
require.NoError(t, err)
result, err = eval.EvaluateCondition(context.Background(), resources)
require.NoError(t, err)
assert.True(t, result, "should find the Deployment")
}

func TestEvaluateCondition_ResourceCount(t *testing.T) {
// Create test resources
deploymentYAML := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
spec:
replicas: 3
`

deployment, err := yaml.Parse(deploymentYAML)
require.NoError(t, err)

resources := []*yaml.RNode{deployment}

// Test: Count of deployments is greater than 0
condition := `resources.filter(r, r.kind == "Deployment").size() > 0`
eval, err := NewCELEvaluator(condition)
require.NoError(t, err)
result, err := eval.EvaluateCondition(context.Background(), resources)
require.NoError(t, err)
assert.True(t, result, "should find deployments")

// Test: Count of ConfigMaps is 0
condition = `resources.filter(r, r.kind == "ConfigMap").size() == 0`
eval, err = NewCELEvaluator(condition)
require.NoError(t, err)
result, err = eval.EvaluateCondition(context.Background(), resources)
require.NoError(t, err)
assert.True(t, result, "should not find ConfigMaps")
}
Comment on lines +157 to +164
assert.Contains(t, err.Error(), "failed to compile")
}

func TestEvaluateCondition_NonBooleanResult(t *testing.T) {
// Expression that returns a number, not a boolean
_, err := NewCELEvaluator("1 + 1")
assert.Error(t, err)
assert.Contains(t, err.Error(), "must return a boolean")
Comment on lines +168 to +200
func TestEvaluateCondition_Immutability(t *testing.T) {
configMapYAML := `
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
namespace: default
data:
key: original-value
`

configMap, err := yaml.Parse(configMapYAML)
require.NoError(t, err)

resources := []*yaml.RNode{configMap}

// Store original values
originalYAML, err := configMap.String()
require.NoError(t, err)

// Evaluate a condition that accesses the resources
condition := `resources.exists(r, r.kind == "ConfigMap")`
eval, err := NewCELEvaluator(condition)
require.NoError(t, err)

_, err = eval.EvaluateCondition(context.Background(), resources)
require.NoError(t, err)

// Verify resources haven't been mutated
afterYAML, err := configMap.String()
require.NoError(t, err)
assert.Equal(t, originalYAML, afterYAML, "CEL evaluation should not mutate input resources")
}
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/kyaml/yaml"

"github.com/GoogleContainerTools/kpt/internal/fnruntime/runneroptions"
Comment on lines +32 to +42
assert.NotNil(t, env.Env)
}

func TestNewCELEvaluator_EmptyCondition(t *testing.T) {
env, err := runneroptions.NewCELEnvironment()
require.NoError(t, err)
assert.NotNil(t, env)
assert.NotNil(t, env.Env)
}

func TestEvaluateCondition_EmptyCondition(t *testing.T) {
Comment on lines +95 to +116
// Test: ConfigMap exists
condition := `resources.exists(r, r.kind == "ConfigMap" && r.metadata.name == "test-config")`
eval, err := NewCELEvaluator(condition)
require.NoError(t, err)
result, err := eval.EvaluateCondition(context.Background(), resources)
require.NoError(t, err)
assert.True(t, result, "should find the ConfigMap")

// Test: ConfigMap with wrong name doesn't exist
condition = `resources.exists(r, r.kind == "ConfigMap" && r.metadata.name == "wrong-name")`
eval, err = NewCELEvaluator(condition)
require.NoError(t, err)
result, err = eval.EvaluateCondition(context.Background(), resources)
require.NoError(t, err)
assert.False(t, result, "should not find ConfigMap with wrong name")

// Test: Deployment exists
condition = `resources.exists(r, r.kind == "Deployment")`
eval, err = NewCELEvaluator(condition)
require.NoError(t, err)
result, err = eval.EvaluateCondition(context.Background(), resources)
require.NoError(t, err)
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

// Set condition; the shared CEL environment from opts is used at evaluation time.
if f.Condition != "" {
Comment on lines 67 to 78
func (opts *RunnerOptions) InitDefaults(defaultImagePrefix string) {
opts.ImagePullPolicy = IfNotPresentPull
opts.ResolveToImage = opts.ResolveToImageForCLIFunc(defaultImagePrefix)
celEnv, err := NewCELEnvironment()
if err != nil {
// CEL environment creation should never fail with the standard config;
// panic here surfaces misconfiguration immediately rather than silently skipping conditions.
panic(fmt.Sprintf("failed to initialise CEL environment: %v", err))
}
opts.CELEnvironment = celEnv
}

Comment on lines +19 to +20
// CELEvaluator is an alias for runneroptions.CELEnvironment so that runner.go
// can reference it within the fnruntime package without an import cycle.
Comment on lines +365 to +376
// `Condition` is an optional CEL expression that determines whether this
// function should be executed. The expression is evaluated against the KRM
// resources in the package and should return a boolean value.
// If omitted or evaluates to true, the function executes normally.
// If evaluates to false, the function is skipped.
//
// Example: Check if a specific ConfigMap exists:
// condition: "resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'my-config')"
//
// Example: Check resource count:
// condition: "resources.filter(r, r.kind == 'Deployment').size() > 0"
Condition string `yaml:"condition,omitempty" json:"condition,omitempty"`
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

SurbhiAgarwal1 and others added 9 commits March 21, 2026 22:30
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 CELEvaluator in internal/fnruntime/celeval.go with k8s CEL extensions
- 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 executor and runneroptions to support condition passing
- Add e2e and unit tests for conditional execution
- Add k8s.io/apiserver dependency for CEL library extensions

Signed-off-by: Surbhi <agarwalsurbhi1807@gmail.com>
Replace k8s apiserver CEL library functions with cel-go built-in
ext package equivalents. The k8s-specific functions (IP, CIDR,
Quantity, SemVer, etc.) are not needed for basic KRM resource
filtering and the heavy dependency was causing CI build failures.

Signed-off-by: Surbhi <agarwalsurbhi1807@gmail.com>
…valuateCondition call

- Move CELEvaluator/CELEnvironment to pkg/lib/runneroptions to avoid import cycle
- Rename NewCELEvaluator(condition) -> NewCELEnvironment() (no condition param)
- Remove pre-compiled prg field; compile program inside EvaluateCondition per call
- Add CELEnvironment field to RunnerOptions, populated in InitDefaults
- celeval.go now just type-aliases runneroptions.CELEnvironment
- Update runner.go to use opts.CELEnvironment and pass condition string at eval time
- Update unit tests to use new API
- Add e2e testdata under e2e/testdata/fn-render/condition/

Signed-off-by: Surbhi <agarwalsurbhi1807@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Surbhi <agarwalsurbhi1807@gmail.com>
Remove internal/fnruntime/conditional_e2e_test.go and replace with
testdata-driven e2e tests under e2e/testdata/fn-render/condition/:
- condition-met: function executes when CEL condition is true
- condition-not-met: function is skipped when CEL condition is false

Signed-off-by: Surbhi <agarwalsurbhi1807@gmail.com>
- Run go mod tidy to drop k8s.io/apiserver (was causing docker/podman CI failure)
- Fix celeval_test.go: correct import path to github.com/kptdev/kpt/pkg/lib/runneroptions
- Update tests to use new EvaluateCondition(ctx, condition, resources) API

Signed-off-by: Surbhi <agarwalsurbhi1807@gmail.com>
RunnerOptions.CELEnvironment is now populated by InitDefaults, so
nil it out before struct comparison just like ResolveToImage.

Signed-off-by: Surbhi <agarwalsurbhi1807@gmail.com>
Fix 1: ensure kind/apiVersion/metadata always present in CEL resource map
- resourceToMap now guarantees these keys exist so CEL expressions like
  r.kind == 'Deployment' return false instead of 'no such key' error

Fix 2: add .krmignore to condition test dirs
- .expected/ was being picked up by kpt fn render as a KRM resource
- .krmignore with '.expected' excludes it, matching all other test cases

Signed-off-by: Surbhi <agarwalsurbhi1807@gmail.com>
Signed-off-by: Surbhi <agarwalsurbhi1807@gmail.com>
@SurbhiAgarwal1
Copy link
Contributor Author

Hi @nagygergo, I've addressed all your feedback — CELEnvironment in RunnerOptions, InitDefaults creates it, program compilation moved to EvaluateCondition, e2e tests moved to e2e/testdata/fn-render/condition/, and k8s.io/apiserver removed from go.mod.

The CI is still showing some test failures. I verified locally that TestCmd_flagAndArgParsing_Symlink and TestFunctionConfig/file_config also fail on upstream/main directly, so they appear to be pre-existing. Could you confirm whether the remaining CI failures are blocking from your side, or if they're known flaky/pre-existing tests?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/fn-runtime KRM function runtime size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants