diff --git a/Makefile b/Makefile index 3ffd048..6d5ebc8 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,17 @@ GO := go GOLANGCI_VERSION := 1.55.2 +generate: + $(GO) generate ./... + 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 \ No newline at end of file +reviewable: generate test lint + +# run a local process for crossplane render +run-local: + $(GO) run . --debug --insecure \ No newline at end of file diff --git a/condition.go b/condition.go index 1b8cbab..941b46e 100644 --- a/condition.go +++ b/condition.go @@ -8,8 +8,6 @@ import ( "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 @@ -30,8 +28,8 @@ func ToCELVars(req *fnv1beta1.RunFunctionRequest) map[string]any { } // EvaluateCondition will evaluate a CEL expression -func EvaluateCondition(cs v1beta1.ConditionSpec, req *fnv1beta1.RunFunctionRequest) (bool, error) { - if cs.Expression == "" { +func EvaluateCondition(expression *string, req *fnv1beta1.RunFunctionRequest) (bool, error) { + if expression == nil { return false, nil } @@ -40,7 +38,7 @@ func EvaluateCondition(cs v1beta1.ConditionSpec, req *fnv1beta1.RunFunctionReque return false, errors.Wrap(err, "CEL Env error") } - ast, iss := env.Parse(cs.Expression) + ast, iss := env.Parse(*expression) if iss.Err() != nil { return false, errors.Wrap(iss.Err(), "CEL Parse error") } @@ -56,7 +54,7 @@ func EvaluateCondition(cs v1beta1.ConditionSpec, req *fnv1beta1.RunFunctionReque 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, + *expression, checked.OutputType()) } diff --git a/condition_test.go b/condition_test.go index 18a7bef..eefab03 100644 --- a/condition_test.go +++ b/condition_test.go @@ -20,8 +20,8 @@ func TestEvaluateCondition(t *testing.T) { oxr := `{"apiVersion":"nopexample.org/v1alpha1","kind":"XNopResource","metadata":{"name":"test-resource"},"spec":{"env":"dev","render":true},"status":{"id":"123","ready":false} }` type args struct { - cs v1beta1.ConditionSpec - req *fnv1beta1.RunFunctionRequest + condition v1beta1.Condition + req *fnv1beta1.RunFunctionRequest } type want struct { ret bool @@ -35,7 +35,7 @@ func TestEvaluateCondition(t *testing.T) { }{ "CELParseError": { args: args{ - cs: v1beta1.ConditionSpec{Expression: "field = value"}, + condition: strPtr("field = value"), req: &fnv1beta1.RunFunctionRequest{ Input: resource.MustStructObject(&v1beta1.Resources{ Resources: []v1beta1.ComposedTemplate{ @@ -64,7 +64,7 @@ func TestEvaluateCondition(t *testing.T) { }, "CELTypeError": { args: args{ - cs: v1beta1.ConditionSpec{Expression: "size(desired.resources)"}, + condition: strPtr("size(desired.resources)"), req: &fnv1beta1.RunFunctionRequest{ Input: resource.MustStructObject(&v1beta1.Resources{ Resources: []v1beta1.ComposedTemplate{ @@ -93,7 +93,7 @@ func TestEvaluateCondition(t *testing.T) { }, "KeyError": { args: args{ - cs: v1beta1.ConditionSpec{Expression: "badkey"}, + condition: strPtr("badkey"), req: &fnv1beta1.RunFunctionRequest{ Input: resource.MustStructObject(&v1beta1.Resources{ Resources: []v1beta1.ComposedTemplate{ @@ -122,7 +122,7 @@ func TestEvaluateCondition(t *testing.T) { }, "TrueDesired": { args: args{ - cs: v1beta1.ConditionSpec{Expression: "desired.composite.resource.spec.env == \"dev\" "}, + condition: strPtr("desired.composite.resource.spec.env == \"dev\" "), req: &fnv1beta1.RunFunctionRequest{ Input: resource.MustStructObject(&v1beta1.Resources{ Resources: []v1beta1.ComposedTemplate{ @@ -151,7 +151,7 @@ func TestEvaluateCondition(t *testing.T) { }, "TrueDesiredBool": { args: args{ - cs: v1beta1.ConditionSpec{Expression: "desired.composite.resource.spec.render == true"}, + condition: strPtr("desired.composite.resource.spec.render == true"), req: &fnv1beta1.RunFunctionRequest{ Input: resource.MustStructObject(&v1beta1.Resources{ Resources: []v1beta1.ComposedTemplate{ @@ -180,7 +180,7 @@ func TestEvaluateCondition(t *testing.T) { }, "FalseDesiredBool": { args: args{ - cs: v1beta1.ConditionSpec{Expression: "desired.composite.resource.spec.render == false"}, + condition: strPtr("desired.composite.resource.spec.render == false"), req: &fnv1beta1.RunFunctionRequest{ Input: resource.MustStructObject(&v1beta1.Resources{ Resources: []v1beta1.ComposedTemplate{ @@ -209,7 +209,7 @@ func TestEvaluateCondition(t *testing.T) { }, "FalseObservedBool": { args: args{ - cs: v1beta1.ConditionSpec{Expression: "observed.composite.resource.status.ready == true"}, + condition: strPtr("observed.composite.resource.status.ready == true"), req: &fnv1beta1.RunFunctionRequest{ Input: resource.MustStructObject(&v1beta1.Resources{ Resources: []v1beta1.ComposedTemplate{ @@ -238,7 +238,7 @@ func TestEvaluateCondition(t *testing.T) { }, "FalseLengthResources": { args: args{ - cs: v1beta1.ConditionSpec{Expression: "size(desired.resources) == 0"}, + condition: strPtr("size(desired.resources) == 0"), req: &fnv1beta1.RunFunctionRequest{ Input: resource.MustStructObject(&v1beta1.Resources{ Resources: []v1beta1.ComposedTemplate{ @@ -272,7 +272,7 @@ func TestEvaluateCondition(t *testing.T) { }, "TrueResourceMapKeyExists": { args: args{ - cs: v1beta1.ConditionSpec{Expression: "\"test-resource\" in desired.resources"}, + condition: strPtr("\"test-resource\" in desired.resources"), req: &fnv1beta1.RunFunctionRequest{ Input: resource.MustStructObject(&v1beta1.Resources{ Resources: []v1beta1.ComposedTemplate{ @@ -306,7 +306,7 @@ func TestEvaluateCondition(t *testing.T) { }, "FalseResourceMapKeyExists": { args: args{ - cs: v1beta1.ConditionSpec{Expression: "\"bad-resource\" in desired.resources"}, + condition: strPtr("\"bad-resource\" in desired.resources"), req: &fnv1beta1.RunFunctionRequest{ Input: resource.MustStructObject(&v1beta1.Resources{ Resources: []v1beta1.ComposedTemplate{ @@ -342,7 +342,7 @@ func TestEvaluateCondition(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - ret, err := EvaluateCondition(tc.args.cs, tc.args.req) + ret, err := EvaluateCondition(tc.args.condition, tc.args.req) if diff := cmp.Diff(tc.want.ret, ret); diff != "" { t.Errorf("%s\nEvaluateCondition(...): -want ret, +got ret:\n%s", tc.reason, diff) @@ -357,3 +357,7 @@ func TestEvaluateCondition(t *testing.T) { }) } } + +func strPtr(str string) *string { + return &str +} diff --git a/example/functions.yaml b/example/functions.yaml index d8edb63..674cfa1 100644 --- a/example/functions.yaml +++ b/example/functions.yaml @@ -2,6 +2,6 @@ apiVersion: pkg.crossplane.io/v1beta1 kind: Function metadata: - name: function-patch-and-transform + name: function-conditional-patch-and-transform spec: - package: xpkg.upbound.io/crossplane-contrib/function-patch-and-transform:v0.1.4 + package: xpkg.upbound.io/crossplane-contrib/function-conditional-patch-and-transform:v0.1.4 diff --git a/examples/conditional-rendering/README.md b/examples/conditional-rendering/README.md index f540c1a..125f1b3 100644 --- a/examples/conditional-rendering/README.md +++ b/examples/conditional-rendering/README.md @@ -19,7 +19,7 @@ will not run. ## Running this example -- Until 1.14 is released, install a development version of Crossplane. See [install Crossplane master](https://docs.crossplane.io/latest/software/install/#install-the-crossplane-master-helm-chart) +- Install Crossplane version 1.14 or newer. See - Install the nop provider in `kubectl apply -f provider.yaml` - Install the XRD & Composition in `kubectl apply -f definition.yaml -f composition.yaml` - Install the Function `kubectl apply -f function.yaml` diff --git a/examples/conditional-rendering/composition.yaml b/examples/conditional-rendering/composition.yaml index 103ff0a..18e9c88 100644 --- a/examples/conditional-rendering/composition.yaml +++ b/examples/conditional-rendering/composition.yaml @@ -11,12 +11,11 @@ spec: pipeline: - step: conditional-patch-and-transform functionRef: - name: function-patch-and-transform + name: function-conditional-patch-and-transform input: - apiVersion: pt.fn.crossplane.io/v1beta1 + apiVersion: conditional-pt.fn.crossplane.io/v1beta1 kind: Resources - condition: - expression: observed.composite.resource.spec.env == "dev" && observed.composite.resource.spec.render == true + condition: observed.composite.resource.spec.env == "dev" && observed.composite.resource.spec.render == true resources: - name: test-resource base: diff --git a/examples/conditional-rendering/function.yaml b/examples/conditional-rendering/function.yaml index 7803bf9..c7da104 100644 --- a/examples/conditional-rendering/function.yaml +++ b/examples/conditional-rendering/function.yaml @@ -1,9 +1,9 @@ apiVersion: pkg.crossplane.io/v1beta1 kind: Function metadata: - name: function-patch-and-transform + name: function-conditional-patch-and-transform annotations: - xrender.crossplane.io/runtime: Development + render.crossplane.io/runtime: Development spec: - package: index.docker.io/steve/function-patch-and-transform:v0.2.0 + package: index.docker.io/steve/function-conditional-patch-and-transform:v0.2.0 packagePullPolicy: Always diff --git a/fn.go b/fn.go index ee5533e..5f9e8a5 100644 --- a/fn.go +++ b/fn.go @@ -48,7 +48,7 @@ func (f *Function) RunFunction(ctx context.Context, req *fnv1beta1.RunFunctionRe // Evaluate any Conditions using the values from the Observed XR if input.Condition != nil { // Evaluate the condition to see if we should run - run, err := EvaluateCondition(*input.Condition, req) + run, err := EvaluateCondition(input.Condition, req) if err != nil { response.Fatal(rsp, errors.Wrap(err, conditionError)) return rsp, nil @@ -159,7 +159,7 @@ func (f *Function) RunFunction(ctx context.Context, req *fnv1beta1.RunFunctionRe if t.Condition != nil { // Evaluate the condition to see if we should skip this template. - run, err := EvaluateCondition(*t.Condition, req) + run, err := EvaluateCondition(t.Condition, req) if err != nil { log.Info(err.Error()) response.Fatal(rsp, errors.Wrap(err, conditionError)) diff --git a/input/v1beta1/conditions.go b/input/v1beta1/conditions.go index 20736c3..47038c5 100644 --- a/input/v1beta1/conditions.go +++ b/input/v1beta1/conditions.go @@ -1,10 +1,6 @@ package v1beta1 -// ConditionSpec defines the condition for rendering. +// Condition defines the condition for rendering. // Conditions are defined using the Common Expression Language // For more information refer to https://github.com/google/cel-spec -type ConditionSpec struct { - // Expression is the CEL expression to be evaluated. If the Expression - // returns a true value, the function will render the resources - Expression string `json:"expression"` -} +type Condition *string diff --git a/input/v1beta1/resources.go b/input/v1beta1/resources.go index 247ca01..a943aaa 100644 --- a/input/v1beta1/resources.go +++ b/input/v1beta1/resources.go @@ -1,6 +1,6 @@ // Package v1beta1 contains the input type for the P&T Composition Function. // +kubebuilder:object:generate=true -// +groupName=pt.fn.crossplane.io +// +groupName=conditional-pt.fn.crossplane.io // +versionName=v1beta1 package v1beta1 @@ -20,8 +20,8 @@ type Resources struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Condition defines a CEL condition whether this function will render - Condition *ConditionSpec `json:"condition,omitempty"` + // If defines a CEL condition whether this function will render + Condition Condition `json:"condition,omitempty"` // PatchSets define a named set of patches that may be included by any // resource. PatchSets cannot themselves refer to other PatchSets. diff --git a/input/v1beta1/resources_common.go b/input/v1beta1/resources_common.go index 031a5ed..fd9f9e7 100644 --- a/input/v1beta1/resources_common.go +++ b/input/v1beta1/resources_common.go @@ -47,8 +47,8 @@ type ComposedTemplate struct { // +optional Base *runtime.RawExtension `json:"base,omitempty"` - // Condition defines a CEL condition whether this function will render - Condition *ConditionSpec `json:"condition,omitempty"` + // Condition defines a CEL condition whether this managed resource will render + Condition Condition `json:"condition,omitempty"` // Patches to and from the composed resource. // +optional diff --git a/input/v1beta1/zz_generated.deepcopy.go b/input/v1beta1/zz_generated.deepcopy.go index 4cec4a8..1bcbf6b 100644 --- a/input/v1beta1/zz_generated.deepcopy.go +++ b/input/v1beta1/zz_generated.deepcopy.go @@ -57,6 +57,11 @@ func (in *ComposedTemplate) DeepCopyInto(out *ComposedTemplate) { *out = new(runtime.RawExtension) (*in).DeepCopyInto(*out) } + if in.Condition != nil { + in, out := &in.Condition, &out.Condition + *out = new(string) + **out = **in + } if in.Patches != nil { in, out := &in.Patches, &out.Patches *out = make([]Patch, len(*in)) @@ -90,21 +95,6 @@ func (in *ComposedTemplate) DeepCopy() *ComposedTemplate { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ConditionSpec) DeepCopyInto(out *ConditionSpec) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConditionSpec. -func (in *ConditionSpec) DeepCopy() *ConditionSpec { - if in == nil { - return nil - } - out := new(ConditionSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConnectionDetail) DeepCopyInto(out *ConnectionDetail) { *out = *in @@ -424,7 +414,7 @@ func (in *Resources) DeepCopyInto(out *Resources) { in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) if in.Condition != nil { in, out := &in.Condition, &out.Condition - *out = new(ConditionSpec) + *out = new(string) **out = **in } if in.PatchSets != nil { diff --git a/package/input/pt.fn.crossplane.io_resources.yaml b/package/input/conditional-pt.fn.crossplane.io_resources.yaml similarity index 99% rename from package/input/pt.fn.crossplane.io_resources.yaml rename to package/input/conditional-pt.fn.crossplane.io_resources.yaml index 4dc2796..7c3ec29 100644 --- a/package/input/pt.fn.crossplane.io_resources.yaml +++ b/package/input/conditional-pt.fn.crossplane.io_resources.yaml @@ -4,9 +4,9 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.13.0 - name: resources.pt.fn.crossplane.io + name: resources.conditional-pt.fn.crossplane.io spec: - group: pt.fn.crossplane.io + group: conditional-pt.fn.crossplane.io names: categories: - crossplane @@ -27,17 +27,8 @@ spec: internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string condition: - description: Condition defines a CEL condition whether this function will - render - properties: - expression: - description: Expression is the CEL expression to be evaluated. If - the Expression returns a true value, the function will render the - resources - type: string - required: - - expression - type: object + description: If defines a CEL condition whether this function will render + type: string environment: description: "Environment represents the Composition environment. \n THIS IS AN ALPHA FIELD. Do not use it in production. It may be changed or @@ -682,6 +673,10 @@ spec: type: object x-kubernetes-embedded-resource: true x-kubernetes-preserve-unknown-fields: true + condition: + description: Condition defines a CEL condition whether this managed + resource will render + type: string connectionDetails: description: ConnectionDetails lists the propagation secret keys from this composed resource to the composition instance connection