Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional function execution using the Common Expression Language #26

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ linters-settings:
- default
- prefix(github.com/crossplane/crossplane-runtime)
- prefix(github.com/crossplane/function-sdk-go)
- prefix(github.com/crossplane-contrib/function-patch-and-transform)
- prefix(github.com/stevendborrelli/function-conditional-patch-and-transform)
- blank
- dot

Expand Down
13 changes: 13 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

DOCKER := docker
GO := go

GOLANGCI_VERSION := 1.55.2

test:
$(GO) test ./...

lint:
$(DOCKER) run --rm -v $(CURDIR):/app -v ~/.cache/golangci-lint/v$(GOLANGCI_VERSION):/root/.cache -w /app golangci/golangci-lint:v$(GOLANGCI_VERSION) golangci-lint run --fix

reviewable: test lint
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# function-patch-and-transform
[![CI](https://github.com/crossplane-contrib/function-patch-and-transform/actions/workflows/ci.yml/badge.svg)](https://github.com/crossplane-contrib/function-patch-and-transform/actions/workflows/ci.yml) ![GitHub release (latest SemVer)](https://img.shields.io/github/release/crossplane-contrib/function-patch-and-transform)
# function-conditional-patch-and-transform
[![CI](https://github.com/stevendborrelli/function-conditional-patch-and-transform/actions/workflows/ci.yml/badge.svg)](https://github.com/stevendborrelli/function-conditional-patch-and-transform/actions/workflows/ci.yml) ![GitHub release (latest SemVer)](https://img.shields.io/github/release/crossplane-contrib/function-conditional-patch-and-transform)

This composition function is a fork of the upstream [function-patch-and-transform](https://github.com/crossplane-contrib/function-patch-and-transform)
that adds support for Conditional invocation of the function and the rendering
of individual resources.

This [composition function][docs-functions] does everything Crossplane's
built-in [patch & transform][docs-pandt] (P&T) composition does. Instead of
Expand All @@ -18,7 +22,7 @@ spec:
pipeline:
- step: patch-and-transform
functionRef:
name: function-patch-and-transform
name: function-conditional-patch-and-transform
input:
apiVersion: pt.fn.crossplane.io/v1beta1
kind: Resources
Expand Down Expand Up @@ -152,7 +156,7 @@ $ crossplane xpkg build -f package --embed-runtime-image=runtime
[docs-composition]: https://docs.crossplane.io/v1.14/getting-started/provider-aws-part-2/#create-a-deployment-template
[docs-functions]: https://docs.crossplane.io/v1.14/concepts/composition-functions/
[docs-pandt]: https://docs.crossplane.io/v1.14/concepts/patch-and-transform/
[fn-go-templating]: https://github.com/crossplane-contrib/function-go-templating
[fn-go-templating]: https://github.com/stevendborrelli/function-go-templating
[#4617]: https://github.com/crossplane/crossplane/issues/4617
[#4746]: https://github.com/crossplane/crossplane/issues/4746
[go]: https://go.dev
Expand Down
84 changes: 84 additions & 0 deletions condition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package main

import (
"reflect"

"github.com/google/cel-go/cel"

"github.com/crossplane/crossplane-runtime/pkg/errors"

fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"

"github.com/stevendborrelli/function-conditional-patch-and-transform/input/v1beta1"
)

// NewCELEnvironment sets up the CEL Environment
func NewCELEnvironment() (*cel.Env, error) {
return cel.NewEnv(
cel.Types(&fnv1beta1.State{}),
cel.Variable("observed", cel.ObjectType("apiextensions.fn.proto.v1beta1.State")),
cel.Variable("desired", cel.ObjectType("apiextensions.fn.proto.v1beta1.State")),
)
}

// ToCELVars formats a RunFunctionRequest for CEL evaluation
func ToCELVars(req *fnv1beta1.RunFunctionRequest) map[string]any {
vars := make(map[string]any)
vars["desired"] = req.GetDesired()
vars["observed"] = req.GetObserved()
return vars
}

// EvaluateCondition will evaluate a CEL expression
func EvaluateCondition(cs v1beta1.ConditionSpec, req *fnv1beta1.RunFunctionRequest) (bool, error) {
if cs.Expression == "" {
return false, nil
}

env, err := NewCELEnvironment()
if err != nil {
return false, errors.Wrap(err, "CEL Env error")
}

ast, iss := env.Parse(cs.Expression)
if iss.Err() != nil {
return false, errors.Wrap(iss.Err(), "CEL Parse error")
}

// Type-check the expression for correctness.
checked, iss := env.Check(ast)
// Report semantic errors, if present.
if iss.Err() != nil {
return false, errors.Wrap(iss.Err(), "CEL TypeCheck error")
}

// Ensure the output type is a bool.
if !reflect.DeepEqual(checked.OutputType(), cel.BoolType) {
return false, errors.Errorf(
"CEL Type error: expression '%s' must return a boolean, got %v instead",
cs.Expression,
checked.OutputType())
}

// Plan the program.
program, err := env.Program(checked)
if err != nil {
return false, errors.Wrap(err, "CEL program plan")
}

// Convert our Function Request into map[string]any for CEL evaluation
vars := ToCELVars(req)

// Evaluate the program without any additional arguments.
result, _, err := program.Eval(vars)
if err != nil {
return false, errors.Wrap(err, "CEL program Evaluation")
}

ret, ok := result.Value().(bool)
if !ok {
return false, errors.Wrap(err, "CEL program did not return a bool")
}

return ret, nil
}
Loading