diff --git a/README.md b/README.md index e6deb00..d99436b 100644 --- a/README.md +++ b/README.md @@ -323,17 +323,19 @@ conditions: ## Additional functions -| Name | Description | -|------------------------------------------------------------------|--------------------------------------------------------------| -| [`randomChoice`](example/inline) | Randomly selects one of a given strings | -| [`toYaml`](example/functions/toYaml) | Marshals any object into a YAML string | -| [`fromYaml`](example/functions/fromYaml) | Unmarshals a YAML string into an object | -| [`getResourceCondition`](example/functions/getResourceCondition) | Helper function to retrieve conditions of resources | -| [`getComposedResource`](example/functions/getComposedResource) | Helper function to retrieve observed composed resources | -| [`getCompositeResource`](example/functions/getCompositeResource) | Helper function to retrieve the observed composite resource | -| [`getExtraResources`](example/functions/getExtraResources) | Helper function to retrieve extra resources | -| [`setResourceNameAnnotation`](example/inline) | Returns the special resource-name annotation with given name | -| [`include`](example/functions/include) | Outputs template as a string | +| Name | Description | +| -------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| [`randomChoice`](example/inline) | Randomly selects one of a given strings | +| [`toYaml`](example/functions/toYaml) | Marshals any object into a YAML string | +| [`fromYaml`](example/functions/fromYaml) | Unmarshals a YAML string into an object | +| [`getResourceCondition`](example/functions/getResourceCondition) | Helper function to retrieve conditions of resources | +| [`getComposedResource`](example/functions/getComposedResource) | Helper function to retrieve observed composed resources | +| [`getCompositeResource`](example/functions/getCompositeResource) | Helper function to retrieve the observed composite resource | +| [`getExtraResources`](example/functions/getExtraResources) | Helper function to retrieve extra resources | +| [`getCompositeResource`](example/functions/getCompositeResource) | Helper function to retrieve the observed composite resource | +| [`getCompositionEnvironment`](example/functions/getCompositionEnvironment) | Helper function to retrieve an environment variable from the request context | +| [`setResourceNameAnnotation`](example/inline) | Returns the special resource-name annotation with given name | +| [`include`](example/functions/include) | Outputs template as a string | ## Developing this function diff --git a/example/functions/getCompositionEnvironment/README.md b/example/functions/getCompositionEnvironment/README.md new file mode 100644 index 0000000..330383a --- /dev/null +++ b/example/functions/getCompositionEnvironment/README.md @@ -0,0 +1,2 @@ +# getCompositionEnvironment +The getCompositionEnvironment function is a helper function used to retrieve [environment variables](https://docs.crossplane.io/latest/composition/environment-configs) from the request context. Upon successful retrieval, the function returns the value. If no variable with the key exists, nil is returned. diff --git a/example/functions/getCompositionEnvironment/composition.yaml b/example/functions/getCompositionEnvironment/composition.yaml new file mode 100644 index 0000000..fee974c --- /dev/null +++ b/example/functions/getCompositionEnvironment/composition.yaml @@ -0,0 +1,31 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: example-function-get-composition-env-var +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1beta1 + kind: XR + mode: Pipeline + pipeline: + - step: render-templates + functionRef: + name: function-go-templating + input: + apiVersion: gotemplating.fn.crossplane.io/v1beta1 + kind: GoTemplate + source: Inline + inline: + template: | + --- + apiVersion: dbforpostgresql.azure.upbound.io/v1beta1 + kind: FlexibleServer + metadata: + annotations: + {{ setResourceNameAnnotation "flexserver" }} + gotemplating.fn.crossplane.io/ready: "False" + spec: + forProvider: + + # Retrieve AdminLogin from request context + adminLogin: {{ getCompositionEnvironment . "adminLogin" }} diff --git a/example/functions/getCompositionEnvironment/environment.yaml b/example/functions/getCompositionEnvironment/environment.yaml new file mode 100644 index 0000000..6fdeeee --- /dev/null +++ b/example/functions/getCompositionEnvironment/environment.yaml @@ -0,0 +1,6 @@ +apiVersion: apiextensions.crossplane.io/v1alpha1 +kind: EnvironmentConfig +metadata: + name: example-environment +data: + adminLogin: admin \ No newline at end of file diff --git a/example/functions/getCompositionEnvironment/functions.yaml b/example/functions/getCompositionEnvironment/functions.yaml new file mode 100644 index 0000000..6ce853e --- /dev/null +++ b/example/functions/getCompositionEnvironment/functions.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: function-go-templating +spec: + package: xpkg.upbound.io/crossplane-contrib/function-go-templating:v0.5.0 diff --git a/example/functions/getCompositionEnvironment/xr.yaml b/example/functions/getCompositionEnvironment/xr.yaml new file mode 100644 index 0000000..0afa08d --- /dev/null +++ b/example/functions/getCompositionEnvironment/xr.yaml @@ -0,0 +1,10 @@ +apiVersion: example.crossplane.io/v1beta1 +kind: XR +metadata: + name: example +spec: + environment: + environmentConfigs: + - type: Reference + ref: + name: example-environment diff --git a/function_maps.go b/function_maps.go index 6ce1b3b..54e78e8 100644 --- a/function_maps.go +++ b/function_maps.go @@ -27,6 +27,7 @@ var funcMaps = []template.FuncMap{ "getComposedResource": getComposedResource, "getCompositeResource": getCompositeResource, "getExtraResources": getExtraResources, + "getCompositionEnvironment": getCompositionEnvironment, }, } @@ -143,3 +144,14 @@ func getExtraResources(req map[string]any, name string) []any { return ers } + +func getCompositionEnvironment(req map[string]any, name string) (any, error) { + path := fmt.Sprintf(`context["apiextensions.crossplane.io/environment"]["%s"]`, name) + + env, err := fieldpath.Pave(req).GetValue(path) + if err != nil { + return nil, err + } + + return env, nil +} diff --git a/function_maps_test.go b/function_maps_test.go index 1bde121..9daa2e0 100644 --- a/function_maps_test.go +++ b/function_maps_test.go @@ -581,3 +581,123 @@ func Test_getExtraResources(t *testing.T) { }) } } + +func Test_getCompositionEnvironment(t *testing.T) { + type args struct { + req map[string]any + name string + } + + type want struct { + rsp any + err error + } + + cases := map[string]struct { + reason string + args args + want want + }{ + "RetrieveCompositionEnvVar": { + reason: "Should successfully retrieve a composition env var", + args: args{ + req: map[string]any{ + "context": map[string]any{ + "apiextensions.crossplane.io/environment": map[string]any{ + "test": "abc", + }, + }, + }, + name: "test", + }, + want: want{ + rsp: "abc", + err: nil, + }, + }, + "RetrieveCompositionEnvVarCaseInsensitive": { + reason: "Should successfully retrieve a composition env var case insensitive", + args: args{ + req: map[string]any{ + "context": map[string]any{ + "apiextensions.crossplane.io/environment": map[string]any{ + "TEST": "abc", + }, + }, + }, + name: "TEST", + }, + want: want{ + rsp: "abc", + err: nil, + }, + }, + "RetrieveCompositionEnvVarList": { + reason: "Should successfully retrieve a composition env var as a list", + args: args{ + req: map[string]any{ + "context": map[string]any{ + "apiextensions.crossplane.io/environment": map[string]any{ + "test": []string{"abc"}, + }, + }, + }, + name: "test", + }, + want: want{ + rsp: []string{"abc"}, + err: nil, + }, + }, + "RetrieveCompositionEnvVarMap": { + reason: "Should successfully retrieve a composition env var as a map", + args: args{ + req: map[string]any{ + "context": map[string]any{ + "apiextensions.crossplane.io/environment": map[string]any{ + "test": map[string]any{ + "key": "abc", + }, + }, + }, + }, + name: "test", + }, + want: want{ + rsp: map[string]any{ + "key": "abc", + }, + err: nil, + }, + }, + "NotExistingEnvVar": { + reason: "Should return nil when env var does not exist", + args: args{ + req: map[string]any{ + "context": map[string]any{ + "apiextensions.crossplane.io/environment": map[string]any{}, + }, + }, + name: "test", + }, + want: want{ + rsp: nil, + err: cmpopts.AnyError, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + rsp, err := getCompositionEnvironment(tc.args.req, tc.args.name) + + if diff := cmp.Diff(tc.want.rsp, rsp, protocmp.Transform()); diff != "" { + t.Errorf("%s\ngetCompositionEnvironment(...): -want rsp, +got rsp:\n%s", tc.reason, diff) + } + + if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" { + t.Errorf("%s\ntgetCompositionEnvironment(...): -want err, +got err:\n%s", tc.reason, diff) + } + }) + } +}