From 54607402a99d2e9f584f2e7b5aabf27b28f1f0ad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:15:52 +0000 Subject: [PATCH 01/12] chore(deps): update dependency ubuntu to v24 Signed-off-by: Steven Borrelli --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad6badd..4bce67e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ env: jobs: lint: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -57,7 +57,7 @@ jobs: version: ${{ env.GOLANGCI_VERSION }} unit-test: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -75,7 +75,7 @@ jobs: # those packages to GitHub as a build artifact. The push job downloads those # artifacts and pushes them as a single multi-platform package. build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: true matrix: @@ -130,7 +130,7 @@ jobs: # pushes them as a multi-platform package. We only push the package it the # XPKG_ACCESS_ID and XPKG_TOKEN secrets were provided. push: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: - build steps: From 176df6b48d14712fa1317389d2016c4eaece68fd Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Thu, 3 Oct 2024 11:31:55 +0200 Subject: [PATCH 02/12] initial Context support Signed-off-by: Steven Borrelli --- example/context/README.md | 70 +++++++++++++++++++++++++ example/context/environmentConfigs.yaml | 10 ++++ example/context/functions.yaml | 25 +++++++++ example/context/xr.yaml | 5 ++ fn.go | 47 +++++++++++++++++ 5 files changed, 157 insertions(+) create mode 100644 example/context/README.md create mode 100644 example/context/environmentConfigs.yaml create mode 100644 example/context/functions.yaml create mode 100644 example/context/xr.yaml diff --git a/example/context/README.md b/example/context/README.md new file mode 100644 index 0000000..25eab14 --- /dev/null +++ b/example/context/README.md @@ -0,0 +1,70 @@ +# Writing to the Function Context + +function-go-templating can write to the Function Context + +## Testing This Function Locally + +You can run your function locally and test it using [`crossplane render`](https://docs.crossplane.io/latest/cli/command-reference/#render) +with these example manifests. + +```shell +crossplane render \ + --extra-resources environmentConfigs.yaml \ + --include-context \ + xr.yaml composition.yaml functions.yaml +``` + +Will produce an output like: + +```shell +--- +apiVersion: example.crossplane.io/v1 +kind: XR +metadata: + name: example-xr +status: + conditions: + - lastTransitionTime: "2024-01-01T00:00:00Z" + reason: Available + status: "True" + type: Ready + fromEnv: e +--- +apiVersion: render.crossplane.io/v1beta1 +kind: Context +fields: + apiextensions.crossplane.io/environment: + kind: Environment + apiVersion: internal.crossplane.io/v1alpha1 + complex: + a: b + c: + d: e + f: "1" +``` + +## Debugging This Function + +First we need to run the command in debug mode. In a terminal Window Run: + +```shell +# Run the function locally +$ go run . --insecure --debug +``` + +Next, set the go-templating function `render.crossplane.io/runtime: Development` annotation so that +`crossplane render` communicates with the local process instead of downloading an image: + +```yaml +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: crossplane-contrib-function-go-templating + annotations: + render.crossplane.io/runtime: Development +spec: + package: xpkg.upbound.io/crossplane-contrib/function-go-templating:v0.6.0 +``` + +While the function is running in one terminal, open another terminal window and run `crossplane render`. +The function should output debug-level logs in the terminal. diff --git a/example/context/environmentConfigs.yaml b/example/context/environmentConfigs.yaml new file mode 100644 index 0000000..46dc62b --- /dev/null +++ b/example/context/environmentConfigs.yaml @@ -0,0 +1,10 @@ +apiVersion: apiextensions.crossplane.io/v1alpha1 +kind: EnvironmentConfig +metadata: + name: example-config +data: + complex: + a: b + c: + d: e + f: "1" \ No newline at end of file diff --git a/example/context/functions.yaml b/example/context/functions.yaml new file mode 100644 index 0000000..291a66d --- /dev/null +++ b/example/context/functions.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: crossplane-contrib-function-environment-configs +spec: + # This is ignored when using the Development runtime. + package: xpkg.upbound.io/crossplane-contrib/function-environment-configs:v0.0.7 +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: crossplane-contrib-function-go-templating + annotations: + # This tells crossplane beta render to connect to the function locally. + render.crossplane.io/runtime: Development +spec: + package: xpkg.upbound.io/crossplane-contrib/function-go-templating:v0.6.0 +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: crossplane-contrib-function-auto-ready +spec: + package: xpkg.upbound.io/crossplane-contrib/function-auto-ready:v0.2.1 diff --git a/example/context/xr.yaml b/example/context/xr.yaml new file mode 100644 index 0000000..35a7feb --- /dev/null +++ b/example/context/xr.yaml @@ -0,0 +1,5 @@ +apiVersion: example.crossplane.io/v1 +kind: XR +metadata: + name: example-xr +spec: {} \ No newline at end of file diff --git a/fn.go b/fn.go index bc22f3a..bb023b1 100644 --- a/fn.go +++ b/fn.go @@ -4,13 +4,16 @@ import ( "bytes" "context" "encoding/base64" + "fmt" "io" "io/fs" "os" "dario.cat/mergo" "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/structpb" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/yaml" @@ -48,6 +51,9 @@ const ( annotationKeyReady = "gotemplating.fn.crossplane.io/ready" metaApiVersion = "meta.gotemplating.fn.crossplane.io/v1alpha1" + + // Key used by function-environment-configs + functionContextKeyEnvironment = "apiextensions.crossplane.io/environment" ) // RunFunction runs the Function. @@ -189,6 +195,47 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ d, _ := base64.StdEncoding.DecodeString(v) //nolint:errcheck // k8s returns secret values encoded desiredComposite.ConnectionDetails[k] = d } + case "Context": + contextData := make(map[string]any) + if err = cd.Resource.GetValueInto("data", &contextData); err != nil { + response.Fatal(rsp, errors.Wrap(err, "cannot get contexts")) + return rsp, nil + } + for key, data := range contextData { + val, ok := data.(map[string]interface{}) + if !ok { + response.Fatal(rsp, errors.Wrap(err, "cannot convert Context")) + return rsp, nil + } + // Check if key is already defined in the context + var inputEnv *unstructured.Unstructured + if v, ok := request.GetContextKey(req, key); ok { + inputEnv = &unstructured.Unstructured{} + if err := resource.AsObject(v.GetStructValue(), inputEnv); err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot get Composition environment from %T context key %q", req, key)) + return rsp, nil + } + f.log.Debug("Loaded Existing Composition environment from Function context", "context-key", key) + if err := mergo.Merge(&inputEnv.Object, val); err != nil { + fmt.Println(err) + response.Fatal(rsp, errors.Wrapf(err, "cannot merge data %T at context key %q", req, key)) + return rsp, nil + } + } else { + inputEnv = &unstructured.Unstructured{Object: val} + } + + if inputEnv.GroupVersionKind().Empty() { + inputEnv.SetGroupVersionKind(schema.GroupVersionKind{Group: "internal.crossplane.io", Kind: "Environment", Version: "v1alpha1"}) + } + v, err := resource.AsStruct(inputEnv) + if err != nil { + response.Fatal(rsp, errors.Wrap(err, "cannot convert Context to protobuf Struct well-known type")) + return rsp, nil + } + //f.log.Debug("Updating Composition environment", "key", key, "with data", v) + response.SetContextKey(rsp, key, structpb.NewStructValue(v)) + } case "ExtraResources": // Set extra resources requirements. ers := make(ExtraResourcesRequirements) From 48540d4abf9b41a8d57d8e633c23a6017a8810d4 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Thu, 3 Oct 2024 11:34:43 +0200 Subject: [PATCH 03/12] add example composition Signed-off-by: Steven Borrelli --- example/context/composition.yaml | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 example/context/composition.yaml diff --git a/example/context/composition.yaml b/example/context/composition.yaml new file mode 100644 index 0000000..67e12c7 --- /dev/null +++ b/example/context/composition.yaml @@ -0,0 +1,51 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: go-template-context.example.crossplane.io +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: XR + mode: Pipeline + pipeline: + - step: environmentConfigs + functionRef: + name: crossplane-contrib-function-environment-configs + input: + apiVersion: environmentconfigs.fn.crossplane.io/v1beta1 + kind: Input + spec: + environmentConfigs: + - type: Reference + ref: + name: example-config + - step: go-templating-update-context + functionRef: + name: crossplane-contrib-function-go-templating + input: + apiVersion: gotemplating.fn.crossplane.io/v1beta1 + kind: GoTemplate + source: Inline + inline: + template: | + --- + apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1 + kind: Context + data: + "apiextensions.crossplane.io/environment": + update: environment + nestedEnvUpdate: + hello: world + # Examine what is happening here + "other-context-key": + complex: {{ index .context "apiextensions.crossplane.io/environment" "complex" | toYaml | nindent 4 }} + newkey: + hello: world + --- + apiVersion: example.crossplane.io/v1 + kind: XR + status: + fromEnv: {{ index .context "apiextensions.crossplane.io/environment" "complex" "c" "d" }} + - step: automatically-detect-ready-composed-resources + functionRef: + name: crossplane-contrib-function-auto-ready From 390d9eb349f26659fb830e2885cb10448d88b319 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Thu, 3 Oct 2024 11:38:11 +0200 Subject: [PATCH 04/12] fix indent Signed-off-by: Steven Borrelli --- example/context/composition.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/example/context/composition.yaml b/example/context/composition.yaml index 67e12c7..672fd5d 100644 --- a/example/context/composition.yaml +++ b/example/context/composition.yaml @@ -36,9 +36,8 @@ spec: update: environment nestedEnvUpdate: hello: world - # Examine what is happening here "other-context-key": - complex: {{ index .context "apiextensions.crossplane.io/environment" "complex" | toYaml | nindent 4 }} + complex: {{ index .context "apiextensions.crossplane.io/environment" "complex" | toYaml | nindent 6 }} newkey: hello: world --- From 552e8c5df572e476f1d9aafe6bfb3f1eef92b115 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Thu, 3 Oct 2024 11:46:27 +0200 Subject: [PATCH 05/12] minor cleanups Signed-off-by: Steven Borrelli --- fn.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/fn.go b/fn.go index bb023b1..cbaa7c6 100644 --- a/fn.go +++ b/fn.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/base64" - "fmt" "io" "io/fs" "os" @@ -51,9 +50,6 @@ const ( annotationKeyReady = "gotemplating.fn.crossplane.io/ready" metaApiVersion = "meta.gotemplating.fn.crossplane.io/v1alpha1" - - // Key used by function-environment-configs - functionContextKeyEnvironment = "apiextensions.crossplane.io/environment" ) // RunFunction runs the Function. @@ -204,7 +200,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ for key, data := range contextData { val, ok := data.(map[string]interface{}) if !ok { - response.Fatal(rsp, errors.Wrap(err, "cannot convert Context")) + response.Fatal(rsp, errors.Wrapf(err, "cannot convert Context from %T context key %q", req, key)) return rsp, nil } // Check if key is already defined in the context @@ -217,7 +213,6 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ } f.log.Debug("Loaded Existing Composition environment from Function context", "context-key", key) if err := mergo.Merge(&inputEnv.Object, val); err != nil { - fmt.Println(err) response.Fatal(rsp, errors.Wrapf(err, "cannot merge data %T at context key %q", req, key)) return rsp, nil } @@ -233,7 +228,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ response.Fatal(rsp, errors.Wrap(err, "cannot convert Context to protobuf Struct well-known type")) return rsp, nil } - //f.log.Debug("Updating Composition environment", "key", key, "with data", v) + f.log.Debug("Updating Composition environment", "key", key, "with data", v) response.SetContextKey(rsp, key, structpb.NewStructValue(v)) } case "ExtraResources": From 602b030290a599e01bf916d2fa3b5f170718452e Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Thu, 3 Oct 2024 12:24:27 +0200 Subject: [PATCH 06/12] move merge logic out of main function Signed-off-by: Steven Borrelli --- context.go | 30 ++++++++++++++++++++++++++++++ fn.go | 27 ++++++++------------------- 2 files changed, 38 insertions(+), 19 deletions(-) create mode 100644 context.go diff --git a/context.go b/context.go new file mode 100644 index 0000000..d8b86ba --- /dev/null +++ b/context.go @@ -0,0 +1,30 @@ +package main + +import ( + "dario.cat/mergo" + "github.com/crossplane/function-sdk-go/errors" + fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1" + "github.com/crossplane/function-sdk-go/request" + "github.com/crossplane/function-sdk-go/resource" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type Context map[string]any + +// MergeContextKey merges existing Context at a key with context data val +func (f *Function) MergeContextKey(key string, val map[string]interface{}, req *fnv1beta1.RunFunctionRequest) (*unstructured.Unstructured, error) { + // Check if key is already defined in the context and merge fields + var mergedContext *unstructured.Unstructured + if v, ok := request.GetContextKey(req, key); ok { + mergedContext = &unstructured.Unstructured{} + if err := resource.AsObject(v.GetStructValue(), mergedContext); err != nil { + return mergedContext, errors.Wrapf(err, "cannot get Composition environment from %T context key %q", req, key) + } + f.log.Debug("Loaded Existing Function Context", "context-key", key) + if err := mergo.Merge(&mergedContext.Object, val); err != nil { + return mergedContext, errors.Wrapf(err, "cannot merge data %T at context key %q", req, key) + } + return mergedContext, nil + } + return &unstructured.Unstructured{Object: val}, nil +} diff --git a/fn.go b/fn.go index cbaa7c6..6df38f6 100644 --- a/fn.go +++ b/fn.go @@ -194,7 +194,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ case "Context": contextData := make(map[string]any) if err = cd.Resource.GetValueInto("data", &contextData); err != nil { - response.Fatal(rsp, errors.Wrap(err, "cannot get contexts")) + response.Fatal(rsp, errors.Wrap(err, "cannot get Contexts from input")) return rsp, nil } for key, data := range contextData { @@ -203,27 +203,16 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ response.Fatal(rsp, errors.Wrapf(err, "cannot convert Context from %T context key %q", req, key)) return rsp, nil } - // Check if key is already defined in the context - var inputEnv *unstructured.Unstructured - if v, ok := request.GetContextKey(req, key); ok { - inputEnv = &unstructured.Unstructured{} - if err := resource.AsObject(v.GetStructValue(), inputEnv); err != nil { - response.Fatal(rsp, errors.Wrapf(err, "cannot get Composition environment from %T context key %q", req, key)) - return rsp, nil - } - f.log.Debug("Loaded Existing Composition environment from Function context", "context-key", key) - if err := mergo.Merge(&inputEnv.Object, val); err != nil { - response.Fatal(rsp, errors.Wrapf(err, "cannot merge data %T at context key %q", req, key)) - return rsp, nil - } - } else { - inputEnv = &unstructured.Unstructured{Object: val} + mergedCtx, err := f.MergeContextKey(key, val, req) + if err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot merge Context")) + return rsp, nil } - if inputEnv.GroupVersionKind().Empty() { - inputEnv.SetGroupVersionKind(schema.GroupVersionKind{Group: "internal.crossplane.io", Kind: "Environment", Version: "v1alpha1"}) + if mergedCtx.GroupVersionKind().Empty() { + mergedCtx.SetGroupVersionKind(schema.GroupVersionKind{Group: "internal.crossplane.io", Kind: "Environment", Version: "v1alpha1"}) } - v, err := resource.AsStruct(inputEnv) + v, err := resource.AsStruct(mergedCtx) if err != nil { response.Fatal(rsp, errors.Wrap(err, "cannot convert Context to protobuf Struct well-known type")) return rsp, nil From 9450fc00e4940083153754d1f6aff6bd6646924c Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Fri, 4 Oct 2024 08:16:56 +0200 Subject: [PATCH 07/12] add tests for merge Signed-off-by: Steven Borrelli --- context.go | 4 +- context_test.go | 164 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 context_test.go diff --git a/context.go b/context.go index d8b86ba..d0e8578 100644 --- a/context.go +++ b/context.go @@ -9,8 +9,6 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -type Context map[string]any - // MergeContextKey merges existing Context at a key with context data val func (f *Function) MergeContextKey(key string, val map[string]interface{}, req *fnv1beta1.RunFunctionRequest) (*unstructured.Unstructured, error) { // Check if key is already defined in the context and merge fields @@ -21,7 +19,7 @@ func (f *Function) MergeContextKey(key string, val map[string]interface{}, req * return mergedContext, errors.Wrapf(err, "cannot get Composition environment from %T context key %q", req, key) } f.log.Debug("Loaded Existing Function Context", "context-key", key) - if err := mergo.Merge(&mergedContext.Object, val); err != nil { + if err := mergo.Merge(&mergedContext.Object, val, mergo.WithOverride); err != nil { return mergedContext, errors.Wrapf(err, "cannot merge data %T at context key %q", req, key) } return mergedContext, nil diff --git a/context_test.go b/context_test.go new file mode 100644 index 0000000..14a1960 --- /dev/null +++ b/context_test.go @@ -0,0 +1,164 @@ +package main + +import ( + "testing" + + "github.com/crossplane-contrib/function-go-templating/input/v1beta1" + "github.com/crossplane/crossplane-runtime/pkg/logging" + fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1" + "github.com/crossplane/function-sdk-go/resource" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "google.golang.org/protobuf/testing/protocmp" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +const ( + environmentKey = "apiextensions.crossplane.io/environment" + contextFromEnvironment = `{"apiextensions.crossplane.io/environment":{"complex":{"a":"b","c":{"d":"e","f":"1","overWrite": "fromContext"}}}}` + malformedContextFromEnvironment = `{"apiextensions.crossplane.io/environment":{"badkey":,"complex":{"a":"b","c":{"d":"e","f":"1","overWrite": "fromContext"}}}}` + contextNew = `apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1 + kind: Context + data: + newKey: + hello: world + overWrite: fromFunction` + + contextWithMergeKey = `apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1 +kind: Context +data: + "apiextensions.crossplane.io/environment": + update: environment + nestedEnvUpdate: + hello: world + complex: + c: + overWrite: fromFunction + "other-context-key": + complex: {{ index .context "apiextensions.crossplane.io/environment" "complex" | toYaml | nindent 6 }} + newkey: + hello: world` +) + +func TestMergeContextKeys(t *testing.T) { + type args struct { + key string + val map[string]interface{} + req *fnv1beta1.RunFunctionRequest + } + type want struct { + us *unstructured.Unstructured + err error + } + + cases := map[string]struct { + reason string + args args + want want + }{ + "NoContextAtKey": { + reason: "When there is no existing context data at the key to merge, return the value", + args: args{ + key: "newkey", + req: &fnv1beta1.RunFunctionRequest{ + Input: resource.MustStructObject( + &v1beta1.GoTemplate{ + Source: v1beta1.InlineSource, + Inline: &v1beta1.TemplateSourceInline{Template: contextNew}, + }), + Observed: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + Resources: map[string]*fnv1beta1.Resource{ + "cool-cd": { + Resource: resource.MustStructJSON(cd), + }, + }, + }, + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + }, + val: map[string]interface{}{"hello": "world"}, + }, + want: want{ + us: &unstructured.Unstructured{ + Object: map[string]interface{}{"hello": "world"}, + }, + err: nil, + }, + }, + "SuccessfulMerge": { + reason: "Confirm that keys are merged with source overwriting destination", + args: args{ + key: environmentKey, + req: &fnv1beta1.RunFunctionRequest{ + Context: resource.MustStructJSON(contextFromEnvironment), + Input: resource.MustStructObject( + &v1beta1.GoTemplate{ + Source: v1beta1.InlineSource, + Inline: &v1beta1.TemplateSourceInline{Template: contextWithMergeKey}, + }), + Observed: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + Resources: map[string]*fnv1beta1.Resource{ + "cool-cd": { + Resource: resource.MustStructJSON(cd), + }, + }, + }, + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + }, + val: map[string]interface{}{ + "newKey": "newValue", + "complex": map[string]any{ + "c": map[string]any{ + "overWrite": "fromFunction", + }, + }, + }, + }, + want: want{ + us: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "complex": map[string]any{ + "a": "b", + "c": map[string]any{ + "d": "e", + "f": "1", + "overWrite": "fromFunction", + }, + }, + "newKey": "newValue"}, + }, + err: nil, + }, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + f := &Function{ + log: logging.NewNopLogger(), + } + rsp, err := f.MergeContextKey(tc.args.key, tc.args.val, tc.args.req) + + if diff := cmp.Diff(tc.want.us, rsp, protocmp.Transform()); diff != "" { + t.Errorf("%s\nf.MergeContextKey(...): -want rsp, +got rsp:\n%s", tc.reason, diff) + } + + if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" { + t.Errorf("%s\nf.RunFunction(...): -want err, +got err:\n%s", tc.reason, diff) + } + }) + } + +} From 2b715a9bb1382064887d41bed5e243cdffe50e11 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Fri, 4 Oct 2024 08:39:16 +0200 Subject: [PATCH 08/12] update readme Signed-off-by: Steven Borrelli --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index 120b70e..508d10c 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,42 @@ example: {{- end }} ``` +### Writing to the Context + +This function can write to the Composition [Context](https://docs.crossplane.io/latest/concepts/compositions/#function-pipeline-context). Subsequent pipeline steps will be able to access the data. + +```yaml +--- +apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1 +kind: Context +data: + region: {{ $spec.region }} + id: field + array: + - "1" + - "2" +``` + +To update Context data, match an existing key. For example, [function-environment-configs](https://github.com/crossplane-contrib/function-environment-configs) +stores data under the key `apiextensions.crossplane.io/environment`. + +In this case, Environment fields `update` and `nestedEnvUpdate.hello` would be updated with new values. + +```yaml +--- +apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1 +kind: Context +data: + "apiextensions.crossplane.io/environment": + update: environment + nestedEnvUpdate: + hello: world + otherContextData: + test: field +``` + +For more information, see the example in [context](example/context). + ## Additional functions | Name | Description | From f2c78c5df5cfbcfe73513fb487db9253f1f2fc54 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Fri, 4 Oct 2024 10:34:52 +0200 Subject: [PATCH 09/12] update tests Signed-off-by: Steven Borrelli --- fn.go | 6 ++-- fn_test.go | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/fn.go b/fn.go index 6df38f6..1cafd8c 100644 --- a/fn.go +++ b/fn.go @@ -192,7 +192,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ desiredComposite.ConnectionDetails[k] = d } case "Context": - contextData := make(map[string]any) + contextData := make(map[string]interface{}) if err = cd.Resource.GetValueInto("data", &contextData); err != nil { response.Fatal(rsp, errors.Wrap(err, "cannot get Contexts from input")) return rsp, nil @@ -200,7 +200,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ for key, data := range contextData { val, ok := data.(map[string]interface{}) if !ok { - response.Fatal(rsp, errors.Wrapf(err, "cannot convert Context from %T context key %q", req, key)) + response.Fatal(rsp, errors.Errorf("error parsing Context data from %T context key %q. Must be a map.", req, key)) return rsp, nil } mergedCtx, err := f.MergeContextKey(key, val, req) @@ -235,7 +235,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ requirements.ExtraResources[k] = v.ToResourceSelector() } default: - response.Fatal(rsp, errors.Errorf("invalid kind %q for apiVersion %q - must be CompositeConnectionDetails or ExtraResources", obj.GetKind(), metaApiVersion)) + response.Fatal(rsp, errors.Errorf("invalid kind %q for apiVersion %q - must be one of CompositeConnectionDetails, Context or ExtraResources", obj.GetKind(), metaApiVersion)) return rsp, nil } diff --git a/fn_test.go b/fn_test.go index 9d5b1b8..2bced54 100644 --- a/fn_test.go +++ b/fn_test.go @@ -29,8 +29,10 @@ var ( cdWithReadyWrong = `{"apiVersion":"example.org/v1","kind":"CD","metadata":{"annotations":{"gotemplating.fn.crossplane.io/composition-resource-name":"cool-cd","gotemplating.fn.crossplane.io/ready":"wrongValue"},"name":"cool-cd"}}` cdWithReadyTrue = `{"apiVersion":"example.org/v1","kind":"CD","metadata":{"annotations":{"gotemplating.fn.crossplane.io/composition-resource-name":"cool-cd","gotemplating.fn.crossplane.io/ready":"True"},"name":"cool-cd"}}` - metaResourceInvalid = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"InvalidMeta"}` - metaResourceConDet = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"CompositeConnectionDetails","data":{"key":"dmFsdWU="}}` // encoded string "value" + metaResourceInvalid = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"InvalidMeta"}` + metaResourceConDet = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"CompositeConnectionDetails","data":{"key":"dmFsdWU="}}` // encoded string "value" + metaResourceContextInvalid = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"Context","data":{"new": "value"}}` + metaResourceContext = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"Context","data":{"apiextensions.crossplane.io/environment":{ "new":"value"}}}` xr = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2}}` xrWithStatus = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2},"status":{"ready":"true"}}` @@ -549,7 +551,7 @@ func TestRunFunction(t *testing.T) { Results: []*fnv1beta1.Result{ { Severity: fnv1beta1.Severity_SEVERITY_FATAL, - Message: "invalid kind \"InvalidMeta\" for apiVersion \"" + metaApiVersion + "\" - must be CompositeConnectionDetails or ExtraResources", + Message: "invalid kind \"InvalidMeta\" for apiVersion \"" + metaApiVersion + "\" - must be one of CompositeConnectionDetails, Context or ExtraResources", }, }, Desired: &fnv1beta1.State{ @@ -593,6 +595,85 @@ func TestRunFunction(t *testing.T) { }, }, }, + "ContextInvalidData": { + reason: "The Function should return an error if he context data is invalid.", + args: args{ + req: &fnv1beta1.RunFunctionRequest{ + Input: resource.MustStructObject( + &v1beta1.GoTemplate{ + Source: v1beta1.InlineSource, + Inline: &v1beta1.TemplateSourceInline{Template: metaResourceContextInvalid}, + }), + Observed: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Meta: &fnv1beta1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)}, + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + Results: []*fnv1beta1.Result{ + { + Severity: fnv1beta1.Severity_SEVERITY_FATAL, + Message: "error parsing Context data from *v1beta1.RunFunctionRequest context key \"new\". Must be a map.", + }, + }, + }, + }, + }, + "Context": { + reason: "The Function should return the desired composite with updated context.", + args: args{ + req: &fnv1beta1.RunFunctionRequest{ + Input: resource.MustStructObject( + &v1beta1.GoTemplate{ + Source: v1beta1.InlineSource, + Inline: &v1beta1.TemplateSourceInline{Template: metaResourceContext}, + }), + Observed: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Meta: &fnv1beta1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)}, + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + Context: resource.MustStructJSON( + `{ + "apiextensions.crossplane.io/environment": { + "kind": "Environment", + "apiVersion": "internal.crossplane.io/v1alpha1", + "new": "value" + } + }`, + ), + }, + }, + }, "ExtraResources": { reason: "The Function should return the desired composite with extra resources.", args: args{ From 5b32ed30923b6918b35bb1de211a46d9cf2151da Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Fri, 4 Oct 2024 12:19:46 +0200 Subject: [PATCH 10/12] update example Signed-off-by: Steven Borrelli --- example/context/README.md | 20 +++++++++++++++++++- example/context/composition.yaml | 6 ++++++ example/context/xrd.yaml | 25 +++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 example/context/xrd.yaml diff --git a/example/context/README.md b/example/context/README.md index 25eab14..f6bcff7 100644 --- a/example/context/README.md +++ b/example/context/README.md @@ -31,16 +31,34 @@ status: fromEnv: e --- apiVersion: render.crossplane.io/v1beta1 -kind: Context fields: apiextensions.crossplane.io/environment: + apiVersion: internal.crossplane.io/v1alpha1 + array: + - "1" + - "2" + complex: + a: b + c: + d: e + f: "1" + kind: Environment + nestedEnvUpdate: + hello: world + update: environment + newkey: + apiVersion: internal.crossplane.io/v1alpha1 + hello: world kind: Environment + other-context-key: apiVersion: internal.crossplane.io/v1alpha1 complex: a: b c: d: e f: "1" + kind: Environment +kind: Context ``` ## Debugging This Function diff --git a/example/context/composition.yaml b/example/context/composition.yaml index 672fd5d..ac471bb 100644 --- a/example/context/composition.yaml +++ b/example/context/composition.yaml @@ -32,12 +32,18 @@ spec: apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1 kind: Context data: + # update existing EnvironmentConfig by using the "apiextensions.crossplane.io/environment" key "apiextensions.crossplane.io/environment": update: environment nestedEnvUpdate: hello: world + array: + - "1" + - "2" + # read existing context and move it to another key "other-context-key": complex: {{ index .context "apiextensions.crossplane.io/environment" "complex" | toYaml | nindent 6 }} + # Create a new Context key and populate it with data newkey: hello: world --- diff --git a/example/context/xrd.yaml b/example/context/xrd.yaml new file mode 100644 index 0000000..8955af2 --- /dev/null +++ b/example/context/xrd.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: apiextensions.crossplane.io/v1 +kind: CompositeResourceDefinition +metadata: + name: xrs.example.crossplane.io +spec: + group: example.crossplane.io + names: + kind: XR + plural: xrs + connectionSecretKeys: + - test + versions: + - name: v1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + status: + type: object + properties: + fromEnv: + type: string \ No newline at end of file From ed7d8f4703f7aaf6948bc4f43675703fe9cb46e2 Mon Sep 17 00:00:00 2001 From: Philippe Scorsolini Date: Tue, 8 Oct 2024 10:11:13 +0100 Subject: [PATCH 11/12] merge full context, avoid injecting keys, allow map[string]any as context Signed-off-by: Philippe Scorsolini Signed-off-by: Steven Borrelli --- context.go | 25 +++----- context_test.go | 103 +++++-------------------------- example/context/composition.yaml | 2 + fn.go | 29 +++------ fn_test.go | 6 +- 5 files changed, 38 insertions(+), 127 deletions(-) diff --git a/context.go b/context.go index d0e8578..ee90fdc 100644 --- a/context.go +++ b/context.go @@ -4,25 +4,16 @@ import ( "dario.cat/mergo" "github.com/crossplane/function-sdk-go/errors" fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1" - "github.com/crossplane/function-sdk-go/request" - "github.com/crossplane/function-sdk-go/resource" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -// MergeContextKey merges existing Context at a key with context data val -func (f *Function) MergeContextKey(key string, val map[string]interface{}, req *fnv1beta1.RunFunctionRequest) (*unstructured.Unstructured, error) { - // Check if key is already defined in the context and merge fields - var mergedContext *unstructured.Unstructured - if v, ok := request.GetContextKey(req, key); ok { - mergedContext = &unstructured.Unstructured{} - if err := resource.AsObject(v.GetStructValue(), mergedContext); err != nil { - return mergedContext, errors.Wrapf(err, "cannot get Composition environment from %T context key %q", req, key) - } - f.log.Debug("Loaded Existing Function Context", "context-key", key) - if err := mergo.Merge(&mergedContext.Object, val, mergo.WithOverride); err != nil { - return mergedContext, errors.Wrapf(err, "cannot merge data %T at context key %q", req, key) - } +// MergeContext merges existing Context with new values provided +func (f *Function) MergeContext(req *fnv1beta1.RunFunctionRequest, val map[string]interface{}) (map[string]interface{}, error) { + mergedContext := req.GetContext().AsMap() + if len(val) == 0 { return mergedContext, nil } - return &unstructured.Unstructured{Object: val}, nil + if err := mergo.Merge(&mergedContext, val, mergo.WithOverride); err != nil { + return mergedContext, errors.Wrapf(err, "cannot merge data %T", req) + } + return mergedContext, nil } diff --git a/context_test.go b/context_test.go index 14a1960..e058380 100644 --- a/context_test.go +++ b/context_test.go @@ -3,51 +3,21 @@ package main import ( "testing" - "github.com/crossplane-contrib/function-go-templating/input/v1beta1" "github.com/crossplane/crossplane-runtime/pkg/logging" fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1" "github.com/crossplane/function-sdk-go/resource" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/protobuf/testing/protocmp" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -const ( - environmentKey = "apiextensions.crossplane.io/environment" - contextFromEnvironment = `{"apiextensions.crossplane.io/environment":{"complex":{"a":"b","c":{"d":"e","f":"1","overWrite": "fromContext"}}}}` - malformedContextFromEnvironment = `{"apiextensions.crossplane.io/environment":{"badkey":,"complex":{"a":"b","c":{"d":"e","f":"1","overWrite": "fromContext"}}}}` - contextNew = `apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1 - kind: Context - data: - newKey: - hello: world - overWrite: fromFunction` - - contextWithMergeKey = `apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1 -kind: Context -data: - "apiextensions.crossplane.io/environment": - update: environment - nestedEnvUpdate: - hello: world - complex: - c: - overWrite: fromFunction - "other-context-key": - complex: {{ index .context "apiextensions.crossplane.io/environment" "complex" | toYaml | nindent 6 }} - newkey: - hello: world` -) - -func TestMergeContextKeys(t *testing.T) { +func TestMergeContext(t *testing.T) { type args struct { - key string val map[string]interface{} req *fnv1beta1.RunFunctionRequest } type want struct { - us *unstructured.Unstructured + us map[string]any err error } @@ -59,77 +29,36 @@ func TestMergeContextKeys(t *testing.T) { "NoContextAtKey": { reason: "When there is no existing context data at the key to merge, return the value", args: args{ - key: "newkey", req: &fnv1beta1.RunFunctionRequest{ - Input: resource.MustStructObject( - &v1beta1.GoTemplate{ - Source: v1beta1.InlineSource, - Inline: &v1beta1.TemplateSourceInline{Template: contextNew}, - }), - Observed: &fnv1beta1.State{ - Composite: &fnv1beta1.Resource{ - Resource: resource.MustStructJSON(xr), - }, - Resources: map[string]*fnv1beta1.Resource{ - "cool-cd": { - Resource: resource.MustStructJSON(cd), - }, - }, - }, - Desired: &fnv1beta1.State{ - Composite: &fnv1beta1.Resource{ - Resource: resource.MustStructJSON(xr), - }, - }, + Context: nil, }, val: map[string]interface{}{"hello": "world"}, }, want: want{ - us: &unstructured.Unstructured{ - Object: map[string]interface{}{"hello": "world"}, - }, + us: map[string]interface{}{"hello": "world"}, err: nil, }, }, "SuccessfulMerge": { reason: "Confirm that keys are merged with source overwriting destination", args: args{ - key: environmentKey, req: &fnv1beta1.RunFunctionRequest{ - Context: resource.MustStructJSON(contextFromEnvironment), - Input: resource.MustStructObject( - &v1beta1.GoTemplate{ - Source: v1beta1.InlineSource, - Inline: &v1beta1.TemplateSourceInline{Template: contextWithMergeKey}, - }), - Observed: &fnv1beta1.State{ - Composite: &fnv1beta1.Resource{ - Resource: resource.MustStructJSON(xr), - }, - Resources: map[string]*fnv1beta1.Resource{ - "cool-cd": { - Resource: resource.MustStructJSON(cd), - }, - }, - }, - Desired: &fnv1beta1.State{ - Composite: &fnv1beta1.Resource{ - Resource: resource.MustStructJSON(xr), - }, - }, + Context: resource.MustStructJSON(`{"apiextensions.crossplane.io/environment":{"complex":{"a":"b","c":{"d":"e","f":"1","overWrite": "fromContext"}}}}`), }, val: map[string]interface{}{ "newKey": "newValue", - "complex": map[string]any{ - "c": map[string]any{ - "overWrite": "fromFunction", + "apiextensions.crossplane.io/environment": map[string]any{ + "complex": map[string]any{ + "c": map[string]any{ + "overWrite": "fromFunction", + }, }, }, }, }, want: want{ - us: &unstructured.Unstructured{ - Object: map[string]interface{}{ + us: map[string]interface{}{ + "apiextensions.crossplane.io/environment": map[string]any{ "complex": map[string]any{ "a": "b", "c": map[string]any{ @@ -138,8 +67,8 @@ func TestMergeContextKeys(t *testing.T) { "overWrite": "fromFunction", }, }, - "newKey": "newValue"}, - }, + }, + "newKey": "newValue"}, err: nil, }, }, @@ -149,10 +78,10 @@ func TestMergeContextKeys(t *testing.T) { f := &Function{ log: logging.NewNopLogger(), } - rsp, err := f.MergeContextKey(tc.args.key, tc.args.val, tc.args.req) + rsp, err := f.MergeContext(tc.args.req, tc.args.val) if diff := cmp.Diff(tc.want.us, rsp, protocmp.Transform()); diff != "" { - t.Errorf("%s\nf.MergeContextKey(...): -want rsp, +got rsp:\n%s", tc.reason, diff) + t.Errorf("%s\nf.MergeContext(...): -want rsp, +got rsp:\n%s", tc.reason, diff) } if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" { diff --git a/example/context/composition.yaml b/example/context/composition.yaml index ac471bb..4b53355 100644 --- a/example/context/composition.yaml +++ b/example/context/composition.yaml @@ -34,6 +34,8 @@ spec: data: # update existing EnvironmentConfig by using the "apiextensions.crossplane.io/environment" key "apiextensions.crossplane.io/environment": + kind: Environment, + apiVersion: internal.crossplane.io/v1alpha1, update: environment nestedEnvUpdate: hello: world diff --git a/fn.go b/fn.go index 1cafd8c..e5588e8 100644 --- a/fn.go +++ b/fn.go @@ -12,7 +12,6 @@ import ( "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/structpb" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/yaml" @@ -197,28 +196,20 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ response.Fatal(rsp, errors.Wrap(err, "cannot get Contexts from input")) return rsp, nil } - for key, data := range contextData { - val, ok := data.(map[string]interface{}) - if !ok { - response.Fatal(rsp, errors.Errorf("error parsing Context data from %T context key %q. Must be a map.", req, key)) - return rsp, nil - } - mergedCtx, err := f.MergeContextKey(key, val, req) - if err != nil { - response.Fatal(rsp, errors.Wrapf(err, "cannot merge Context")) - return rsp, nil - } + mergedCtx, err := f.MergeContext(req, contextData) + if err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot merge Context")) + return rsp, nil + } - if mergedCtx.GroupVersionKind().Empty() { - mergedCtx.SetGroupVersionKind(schema.GroupVersionKind{Group: "internal.crossplane.io", Kind: "Environment", Version: "v1alpha1"}) - } - v, err := resource.AsStruct(mergedCtx) + for key, v := range mergedCtx { + vv, err := structpb.NewValue(v) if err != nil { - response.Fatal(rsp, errors.Wrap(err, "cannot convert Context to protobuf Struct well-known type")) + response.Fatal(rsp, errors.Wrap(err, "cannot convert value to structpb.Value")) return rsp, nil } - f.log.Debug("Updating Composition environment", "key", key, "with data", v) - response.SetContextKey(rsp, key, structpb.NewStructValue(v)) + f.log.Debug("Updating Composition environment", "key", key, "data", v) + response.SetContextKey(rsp, key, vv) } case "ExtraResources": // Set extra resources requirements. diff --git a/fn_test.go b/fn_test.go index 2bced54..05cc370 100644 --- a/fn_test.go +++ b/fn_test.go @@ -31,7 +31,7 @@ var ( metaResourceInvalid = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"InvalidMeta"}` metaResourceConDet = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"CompositeConnectionDetails","data":{"key":"dmFsdWU="}}` // encoded string "value" - metaResourceContextInvalid = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"Context","data":{"new": "value"}}` + metaResourceContextInvalid = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"Context","data": 1 }` metaResourceContext = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"Context","data":{"apiextensions.crossplane.io/environment":{ "new":"value"}}}` xr = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2}}` @@ -627,7 +627,7 @@ func TestRunFunction(t *testing.T) { Results: []*fnv1beta1.Result{ { Severity: fnv1beta1.Severity_SEVERITY_FATAL, - Message: "error parsing Context data from *v1beta1.RunFunctionRequest context key \"new\". Must be a map.", + Message: "cannot get Contexts from input: cannot unmarshal value from JSON: json: cannot unmarshal number into Go value of type map[string]interface {}", }, }, }, @@ -665,8 +665,6 @@ func TestRunFunction(t *testing.T) { Context: resource.MustStructJSON( `{ "apiextensions.crossplane.io/environment": { - "kind": "Environment", - "apiVersion": "internal.crossplane.io/v1alpha1", "new": "value" } }`, From f01d533e6827fa4275f4547c75e27fe15d0697c4 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Tue, 8 Oct 2024 14:28:08 +0200 Subject: [PATCH 12/12] Apply suggestions from code review Co-authored-by: Philippe Scorsolini Signed-off-by: Steven Borrelli --- README.md | 2 ++ example/context/README.md | 4 ---- example/context/composition.yaml | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 508d10c..8a4fba4 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,8 @@ apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1 kind: Context data: "apiextensions.crossplane.io/environment": + kind: Environment + apiVersion: internal.crossplane.io/v1alpha1 update: environment nestedEnvUpdate: hello: world diff --git a/example/context/README.md b/example/context/README.md index f6bcff7..21587cd 100644 --- a/example/context/README.md +++ b/example/context/README.md @@ -47,17 +47,13 @@ fields: hello: world update: environment newkey: - apiVersion: internal.crossplane.io/v1alpha1 hello: world - kind: Environment other-context-key: - apiVersion: internal.crossplane.io/v1alpha1 complex: a: b c: d: e f: "1" - kind: Environment kind: Context ``` diff --git a/example/context/composition.yaml b/example/context/composition.yaml index 4b53355..38e75fd 100644 --- a/example/context/composition.yaml +++ b/example/context/composition.yaml @@ -34,8 +34,8 @@ spec: data: # update existing EnvironmentConfig by using the "apiextensions.crossplane.io/environment" key "apiextensions.crossplane.io/environment": - kind: Environment, - apiVersion: internal.crossplane.io/v1alpha1, + kind: Environment + apiVersion: internal.crossplane.io/v1alpha1 update: environment nestedEnvUpdate: hello: world