From 26e59fe6078906bfbfa3aaa821f468b4a0a9d454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Fern=C3=A1ndez?= <7312236+fernandezcuesta@users.noreply.github.com> Date: Mon, 4 Nov 2024 12:17:07 +0100 Subject: [PATCH] feat: add the ability to override merged output from XR fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jesús Fernández <7312236+fernandezcuesta@users.noreply.github.com> --- fn.go | 9 + fn_test.go | 165 ++++++++++++++++-- input/v1beta1/composition_environment.go | 5 + input/v1beta1/zz_generated.deepcopy.go | 7 + ...onmentconfigs.fn.crossplane.io_inputs.yaml | 8 + 5 files changed, 182 insertions(+), 12 deletions(-) diff --git a/fn.go b/fn.go index 4dc5a0b..5a17957 100644 --- a/fn.go +++ b/fn.go @@ -116,6 +116,15 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ mergedData = mergeMaps(defaultData, mergedData) } + if in.Spec.DataOverrides != nil { + xr := req.Observed.Composite.Resource.AsMap() + for k, v := range in.Spec.DataOverrides { + if val, err := fieldpath.Pave(xr).GetValue(v); err == nil { + mergedData[k] = val + } + } + } + // build environment and return it in the response as context out := &unstructured.Unstructured{Object: mergedData} if out.GroupVersionKind().Empty() { diff --git a/fn_test.go b/fn_test.go index 51fd2a9..638c68e 100644 --- a/fn_test.go +++ b/fn_test.go @@ -59,15 +59,15 @@ func TestRunFunction(t *testing.T) { "kind": "Input", "spec": { "environmentConfigs": [ - { + { "type": "Reference", - "ref": { + "ref": { "name": "my-env-config" } }, - { + { "type": "Reference", - "ref": { + "ref": { "name": "my-second-env-config" } }, @@ -283,15 +283,15 @@ func TestRunFunction(t *testing.T) { "kind": "Input", "spec": { "environmentConfigs": [ - { + { "type": "Reference", - "ref": { + "ref": { "name": "my-env-config" } }, - { + { "type": "Reference", - "ref": { + "ref": { "name": "my-second-env-config" } }, @@ -427,9 +427,9 @@ func TestRunFunction(t *testing.T) { "kind": "Input", "spec": { "environmentConfigs": [ - { + { "type": "Reference", - "ref": { + "ref": { "name": "my-env-config" } } @@ -475,6 +475,144 @@ func TestRunFunction(t *testing.T) { "g": "overridden-from-env-config-2" } }`), + Input: resource.MustStructJSON(`{ + "apiVersion": "template.fn.crossplane.io/v1beta1", + "kind": "Input", + "spec": { + "defaultData": { + "b": "only-from-default", + "e": "overridden-from-input", + "f": "overridden-from-env-config-2" + }, + "environmentConfigs": [ + { + "type": "Reference", + "ref": { + "name": "my-env-config" + } + }, + { + "type": "Reference", + "ref": { + "name": "my-second-env-config" + } + } + ] + } + }`), + ExtraResources: map[string]*fnv1beta1.Resources{ + "environment-config-0": { + Items: []*fnv1beta1.Resource{ + { + Resource: resource.MustStructJSON(`{ + "apiVersion": "apiextensions.crossplane.io/v1alpha1", + "kind": "EnvironmentConfig", + "metadata": { + "name": "my-env-config" + }, + "data": { + "c": "only-from-env-config-1", + "f": "overridden-from-env-config-1-ok", + "h": "override-from-env-config-1" + } + }`), + }, + }, + }, + "environment-config-1": { + Items: []*fnv1beta1.Resource{ + { + Resource: resource.MustStructJSON(`{ + "apiVersion": "apiextensions.crossplane.io/v1alpha1", + "kind": "EnvironmentConfig", + "metadata": { + "name": "my-second-env-config" + }, + "data": { + "d": "only-from-env-config-1", + "g": "overridden-from-env-config-2-ok", + "h": "override-from-env-config-1-ok" + } + }`), + }, + }, + }, + }, + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Meta: &fnv1beta1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)}, + Results: []*fnv1beta1.Result{}, + Requirements: &fnv1beta1.Requirements{ + ExtraResources: map[string]*fnv1beta1.ResourceSelector{ + "environment-config-0": { + ApiVersion: "apiextensions.crossplane.io/v1alpha1", + Kind: "EnvironmentConfig", + Match: &fnv1beta1.ResourceSelector_MatchName{ + MatchName: "my-env-config", + }, + }, + "environment-config-1": { + ApiVersion: "apiextensions.crossplane.io/v1alpha1", + Kind: "EnvironmentConfig", + Match: &fnv1beta1.ResourceSelector_MatchName{ + MatchName: "my-second-env-config", + }, + }, + }, + }, + Context: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + FunctionContextKeyEnvironment: structpb.NewStructValue(resource.MustStructJSON(`{ + "apiVersion": "internal.crossplane.io/v1alpha1", + "kind": "Environment", + "a": "only-from-input", + "b": "only-from-default", + "c": "only-from-env-config-1", + "d": "only-from-env-config-1", + "e": "overridden-from-input-ok", + "f": "overridden-from-env-config-1-ok", + "g": "overridden-from-env-config-2-ok", + "h": "override-from-env-config-1-ok" + }`)), + }, + }, + }, + }, + }, + "DataOverrides": { + reason: "The Function should merge the provided EnvironmentConfigs and allow data overrides", + args: args{ + req: &fnv1beta1.RunFunctionRequest{ + Meta: &fnv1beta1.RequestMeta{Tag: "hello"}, + Context: resource.MustStructJSON(`{ + "` + FunctionContextKeyEnvironment + `": { + "apiVersion": "internal.crossplane.io/v1alpha1", + "kind": "Environment", + "a": "only-from-input", + "e": "overridden-from-input-ok", + "f": "overridden-from-env-config-1", + "g": "overridden-from-env-config-2" + } + }`), + Observed: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(`{ + "apiVersion": "test.crossplane.io/v1alpha1", + "kind": "XR", + "metadata": { + "name": "my-xr" + }, + "spec": { + "parameters": { + "override-g": "overriden-from-xr-parameters-ok", + "unused": "qux" + } + } + }`), + }, + }, Input: resource.MustStructJSON(`{ "apiVersion": "template.fn.crossplane.io/v1beta1", "kind": "Input", @@ -497,7 +635,10 @@ func TestRunFunction(t *testing.T) { "name": "my-second-env-config" } } - ] + ], + "dataOverrides": { + "g": "spec.parameters.override-g" + } } }`), ExtraResources: map[string]*fnv1beta1.Resources{ @@ -573,7 +714,7 @@ func TestRunFunction(t *testing.T) { "d": "only-from-env-config-1", "e": "overridden-from-input-ok", "f": "overridden-from-env-config-1-ok", - "g": "overridden-from-env-config-2-ok", + "g": "overriden-from-xr-parameters-ok", "h": "override-from-env-config-1-ok" }`)), }, diff --git a/input/v1beta1/composition_environment.go b/input/v1beta1/composition_environment.go index 53c776a..5794b7d 100644 --- a/input/v1beta1/composition_environment.go +++ b/input/v1beta1/composition_environment.go @@ -49,6 +49,11 @@ type InputSpec struct { // EnvironmentSourceReferences in EnvironmentConfigs list. // +optional Policy *Policy `json:"policy,omitempty"` + + // DataOverrides allows overriding the resulting environment data with + // static values. The keys are field paths in the environment data, and the + // values are references to where the data should be taken from (e.g. .spec.parameters.foo) + DataOverrides map[string]string `json:"dataOverrides,omitempty"` } // Policy represents the Resolution policy of Reference instance. diff --git a/input/v1beta1/zz_generated.deepcopy.go b/input/v1beta1/zz_generated.deepcopy.go index de2c8dc..2189c5c 100644 --- a/input/v1beta1/zz_generated.deepcopy.go +++ b/input/v1beta1/zz_generated.deepcopy.go @@ -160,6 +160,13 @@ func (in *InputSpec) DeepCopyInto(out *InputSpec) { *out = new(Policy) (*in).DeepCopyInto(*out) } + if in.DataOverrides != nil { + in, out := &in.DataOverrides, &out.DataOverrides + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InputSpec. diff --git a/package/input/environmentconfigs.fn.crossplane.io_inputs.yaml b/package/input/environmentconfigs.fn.crossplane.io_inputs.yaml index ce2d2b0..7a12a77 100644 --- a/package/input/environmentconfigs.fn.crossplane.io_inputs.yaml +++ b/package/input/environmentconfigs.fn.crossplane.io_inputs.yaml @@ -43,6 +43,14 @@ spec: An InputSpec specifies the environment for rendering composed resources. properties: + dataOverrides: + additionalProperties: + type: string + description: |- + DataOverrides allows overriding the resulting environment data with + static values. The keys are field paths in the environment data, and the + values are references to where the data should be taken from (e.g. .spec.parameters.foo) + type: object defaultData: additionalProperties: x-kubernetes-preserve-unknown-fields: true