diff --git a/README.md b/README.md index 37ada72..8b49743 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ spec: ``` - `shellEnvVars` - an array of environment variables with a -`key` and `value` each. +`key` and `value` each. Also supports reading from Composite fields with `fieldRef`. - `shellCommand` - a shell command line that can contain pipes and redirects and calling multiple programs. - `shellCommandField` - a reference to a field that contains @@ -72,9 +72,11 @@ standard error output should be written. ## Examples -This repository includes the following examples +This repository includes the following examples: + - echo - datadog-dashboard-ids +- fieldRef - ip-addr-validation ## Example: Obtain Dashboard Ids from Datadog diff --git a/env.go b/env.go index 55a21ed..75c4df4 100644 --- a/env.go +++ b/env.go @@ -28,10 +28,13 @@ func addShellEnvVarsFromRef(envVarsRef v1alpha1.ShellEnvVarsRef, shellEnvVars ma return shellEnvVars, nil } -func fromValueRef(req *fnv1.RunFunctionRequest, path string) (string, error) { +func fromFieldRef(req *fnv1.RunFunctionRequest, fieldRef v1alpha1.FieldRef) (string, error) { + if fieldRef.Path == "" { + return "", errors.New("path must be set") + } // Check for context key presence and capture context key and path contextRegex := regexp.MustCompile(`^context\[(.+?)].(.+)$`) - if match := contextRegex.FindStringSubmatch(path); match != nil { + if match := contextRegex.FindStringSubmatch(fieldRef.Path); match != nil { if v, ok := request.GetContextKey(req, match[1]); ok { context := &unstructured.Unstructured{} if err := resource.AsObject(v.GetStructValue(), context); err != nil { @@ -39,7 +42,14 @@ func fromValueRef(req *fnv1.RunFunctionRequest, path string) (string, error) { } value, err := fieldpath.Pave(context.Object).GetValue(match[2]) if err != nil { - return "", errors.Wrapf(err, "cannot get context value at %s", match[2]) + switch fieldRef.Policy { + case v1alpha1.FieldRefPolicyOptional: + return fieldRef.DefaultValue, nil + case v1alpha1.FieldRefPolicyRequired: + fallthrough + default: + return "", errors.Wrapf(err, "cannot get context value at %s", match[2]) + } } return fmt.Sprintf("%v", value), nil } @@ -49,12 +59,28 @@ func fromValueRef(req *fnv1.RunFunctionRequest, path string) (string, error) { if err != nil { return "", errors.Wrapf(err, "cannot get observed composite resource from %T", req) } - value, err := oxr.Resource.GetValue(path) + value, err := oxr.Resource.GetValue(fieldRef.Path) if err != nil { - return "", errors.Wrapf(err, "cannot get observed composite value at %s", path) + switch fieldRef.Policy { + case v1alpha1.FieldRefPolicyOptional: + return fieldRef.DefaultValue, nil + case v1alpha1.FieldRefPolicyRequired: + fallthrough + default: + return "", errors.Wrapf(err, "cannot get observed composite value at %s", fieldRef.Path) + } } return fmt.Sprintf("%v", value), nil } - return "", nil + return fieldRef.DefaultValue, nil +} + +// a valueRef behaves like a fieldRef with a Required Policy +func fromValueRef(req *fnv1.RunFunctionRequest, path string) (string, error) { + return fromFieldRef( + req, v1alpha1.FieldRef{ + Path: path, + Policy: v1alpha1.FieldRefPolicyRequired, + }) } diff --git a/env_test.go b/env_test.go index 6b8c7e0..620523c 100644 --- a/env_test.go +++ b/env_test.go @@ -3,6 +3,8 @@ package main import ( "testing" + "github.com/crossplane-contrib/function-shell/input/v1alpha1" + "github.com/crossplane/crossplane-runtime/pkg/errors" fnv1 "github.com/crossplane/function-sdk-go/proto/v1" "github.com/crossplane/function-sdk-go/resource" "github.com/google/go-cmp/cmp" @@ -84,3 +86,271 @@ func TestFromValueRef(t *testing.T) { } } + +func TestFromFieldRef(t *testing.T) { + + type args struct { + req *fnv1.RunFunctionRequest + fieldRef v1alpha1.FieldRef + } + + type want struct { + result string + err error + } + + cases := map[string]struct { + reason string + args args + want want + }{ + "FromCompositeValid": { + reason: "If composite path is valid, it should be returned.", + args: args{ + req: &fnv1.RunFunctionRequest{ + Observed: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(`{ + "apiVersion": "", + "kind": "", + "spec": { + "foo": "bar" + } + }`), + }, + }, + }, + fieldRef: v1alpha1.FieldRef{ + Path: "spec.foo", + }, + }, + want: want{ + result: "bar", + err: nil, + }, + }, + "FromCompositeMissingError": { + reason: "If composite path is invalid and Policy is required, return an error", + args: args{ + req: &fnv1.RunFunctionRequest{ + Observed: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(`{ + "apiVersion": "", + "kind": "", + "spec": { + "bad": "bar" + } + }`), + }, + }, + }, + fieldRef: v1alpha1.FieldRef{ + Path: "spec.foo", + Policy: v1alpha1.FieldRefPolicyRequired, + }, + }, + want: want{ + result: "", + err: errors.New("cannot get observed composite value at spec.foo: spec.foo: no such field"), + }, + }, + "FromCompositeMissingErrorDefaultPolicy": { + reason: "If composite path is invalid and Policy is not set, return an error", + args: args{ + req: &fnv1.RunFunctionRequest{ + Observed: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(`{ + "apiVersion": "", + "kind": "", + "spec": { + "bad": "bar" + } + }`), + }, + }, + }, + fieldRef: v1alpha1.FieldRef{ + Path: "spec.foo", + }, + }, + want: want{ + result: "", + err: errors.New("cannot get observed composite value at spec.foo: spec.foo: no such field"), + }, + }, + "FromCompositeMissingFieldOptionalPolicyDefaultValue": { + reason: "If composite path is invalid and Policy is set to Optional, return DefaultValue", + args: args{ + req: &fnv1.RunFunctionRequest{ + Observed: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(`{ + "apiVersion": "", + "kind": "", + "spec": { + "bad": "bar" + } + }`), + }, + }, + }, + fieldRef: v1alpha1.FieldRef{ + DefaultValue: "default", + Path: "spec.foo", + Policy: v1alpha1.FieldRefPolicyOptional, + }, + }, + want: want{ + result: "default", + err: nil, + }, + }, + "FromCompositeMissingFieldOptionalPolicyNoDefaultValue": { + reason: "If composite path is invalid, Policy is set to Optional, and no defaultValue return empty string", + args: args{ + req: &fnv1.RunFunctionRequest{ + Observed: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(`{ + "apiVersion": "", + "kind": "", + "spec": { + "bad": "bar" + } + }`), + }, + }, + }, + fieldRef: v1alpha1.FieldRef{ + Path: "spec.foo", + Policy: v1alpha1.FieldRefPolicyOptional, + }, + }, + want: want{ + result: "", + err: nil, + }, + }, + "FromContextValid": { + reason: "If context path is valid, it should be returned.", + args: args{ + req: &fnv1.RunFunctionRequest{ + Context: resource.MustStructJSON(`{ + "apiextensions.crossplane.io/foo": { + "bar": "baz" + } + }`), + }, + fieldRef: v1alpha1.FieldRef{ + Path: "context[apiextensions.crossplane.io/foo].bar", + }, + }, + want: want{ + result: "baz", + err: nil, + }, + }, + "FromContextMissingError": { + reason: "If context path is invalid and Policy is Required, return an error", + args: args{ + req: &fnv1.RunFunctionRequest{ + Context: resource.MustStructJSON(`{ + "apiextensions.crossplane.io/foo": { + "bar": "baz" + } + }`), + }, + fieldRef: v1alpha1.FieldRef{ + Path: "context[apiextensions.crossplane.io/foo].bad", + Policy: v1alpha1.FieldRefPolicyRequired, + }, + }, + want: want{ + result: "", + err: errors.New("cannot get context value at bad: bad: no such field"), + }, + }, + "FromContextMissingErrorDefaultPolicy": { + reason: "If context path is invalid and no Policy is defined, return an error", + args: args{ + req: &fnv1.RunFunctionRequest{ + Context: resource.MustStructJSON(`{ + "apiextensions.crossplane.io/foo": { + "bar": "baz" + } + }`), + }, + fieldRef: v1alpha1.FieldRef{ + Path: "context[apiextensions.crossplane.io/foo].bad", + }, + }, + want: want{ + result: "", + err: errors.New("cannot get context value at bad: bad: no such field"), + }, + }, + "FromContextMissingFieldOptionalPolicyNoDefaultValue": { + reason: "If context path is invalid, Policy is Optional, and no DefaultValue return empty string", + args: args{ + req: &fnv1.RunFunctionRequest{ + Context: resource.MustStructJSON(`{ + "apiextensions.crossplane.io/foo": { + "bar": "baz" + } + }`), + }, + fieldRef: v1alpha1.FieldRef{ + Path: "context[apiextensions.crossplane.io/foo].bad", + Policy: v1alpha1.FieldRefPolicyOptional, + }, + }, + want: want{ + result: "", + err: nil, + }, + }, + "FromContextMissingFieldOptionalPolicyDefaultValue": { + reason: "If context path is invalid, Policy is Optional, return DefaultValue", + args: args{ + req: &fnv1.RunFunctionRequest{ + Context: resource.MustStructJSON(`{ + "apiextensions.crossplane.io/foo": { + "bar": "baz" + } + }`), + }, + fieldRef: v1alpha1.FieldRef{ + DefaultValue: "default", + Path: "context[apiextensions.crossplane.io/foo].bad", + Policy: v1alpha1.FieldRefPolicyOptional, + }, + }, + want: want{ + result: "default", + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + result, err := fromFieldRef(tc.args.req, tc.args.fieldRef) + + if diff := cmp.Diff(tc.want.result, result, protocmp.Transform()); diff != "" { + t.Errorf("%s\nf.RunFunction(...): -want rsp, +got rsp:\n%s", tc.reason, diff) + } + + // deal with internal fieldpath errors that are generated by a private method + if tc.want.err != nil && err != nil { + if diff := cmp.Diff(tc.want.err.Error(), err.Error()); diff != "" { + t.Errorf("%s\nf.RunFunction(...): -want err message, +got err message:\n%s", tc.reason, diff) + } + } else 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) + } + }) + } + +} diff --git a/example/fieldRef/composition.yaml b/example/fieldRef/composition.yaml new file mode 100644 index 0000000..f1785e0 --- /dev/null +++ b/example/fieldRef/composition.yaml @@ -0,0 +1,40 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: shell-example +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: XR + mode: Pipeline + pipeline: + - step: shell + functionRef: + name: function-shell + input: + apiVersion: shell.fn.crossplane.io/v1alpha1 + kind: Parameters + shellEnvVars: + - key: ECHO + fieldRef: + path: spec.field + type: FieldRef + - key: OPTIONAL + type: FieldRef + fieldRef: + path: spec.optional + policy: Optional + defaultValue: "defaultValue from Composition" + - key: FN_STDOUT + type: FieldRef + fieldRef: + path: status.atFunction.shell.stdout + policy: Optional + defaultValue: "" + shellCommand: | + echo "echo: ${ECHO} optional: ${OPTIONAL}" && + if [ "$FN_STDOUT" != "" ]; then + echo "status.atFunction.shell.stdout" is populated + fi + stdoutField: status.atFunction.shell.stdout + stderrField: status.atFunction.shell.stderr diff --git a/example/fieldRef/definition.yaml b/example/fieldRef/definition.yaml new file mode 100644 index 0000000..cad591d --- /dev/null +++ b/example/fieldRef/definition.yaml @@ -0,0 +1,27 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: CompositeResourceDefinition +metadata: + name: xrs.example.crossplane.io +spec: + group: example.crossplane.io + names: + kind: XR + plural: xrs + versions: + - name: v1 + served: true + referenceable: true + schema: + openAPIV3Schema: + properties: + spec: + properties: + field: + type: string + optional: + type: string + status: + properties: + atFunction: + type: object + x-kubernetes-preserve-unknown-fields: true diff --git a/example/fieldRef/functions.yaml b/example/fieldRef/functions.yaml new file mode 100644 index 0000000..752c7f1 --- /dev/null +++ b/example/fieldRef/functions.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: function-shell + #annotations: + #This tells crossplane beta render to connect to the function locally. + #render.crossplane.io/runtime: Development +spec: + # This is ignored when using the Development runtime. + package: index.docker.io/steve/function-shell:fieldref diff --git a/example/fieldRef/xr.yaml b/example/fieldRef/xr.yaml new file mode 100644 index 0000000..3e764a2 --- /dev/null +++ b/example/fieldRef/xr.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: example.crossplane.io/v1 +kind: XR +metadata: + name: example-echo +spec: + field: "hello from the XR" + # Uncomment to test + # optional: "optional from the XR" diff --git a/fn.go b/fn.go index 92bd0d5..0c4e1b4 100644 --- a/fn.go +++ b/fn.go @@ -86,15 +86,26 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) var shellEnvVars = make(map[string]string) for _, envVar := range in.ShellEnvVars { - if envVar.ValueRef != "" { + switch t := envVar.GetType(); t { + case v1alpha1.ShellEnvVarTypeValue: + shellEnvVars[envVar.Key] = envVar.Value + case v1alpha1.ShellEnvVarTypeValueRef: envValue, err := fromValueRef(req, envVar.ValueRef) if err != nil { response.Fatal(rsp, errors.Wrapf(err, "cannot process contents of valueRef %s", envVar.ValueRef)) return rsp, nil } shellEnvVars[envVar.Key] = envValue - } else { - shellEnvVars[envVar.Key] = envVar.Value + case v1alpha1.ShellEnvVarTypeFieldRef: + envValue, err := fromFieldRef(req, *envVar.FieldRef) + if err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot process contents of fieldRef %s", envVar.ValueRef)) + return rsp, nil + } + shellEnvVars[envVar.Key] = envValue + default: + response.Fatal(rsp, errors.Errorf("shellEnvVars: unknown type %s for key %s", t, envVar.Key)) + return rsp, nil } } diff --git a/fn_test.go b/fn_test.go index 099e35f..3d34318 100644 --- a/fn_test.go +++ b/fn_test.go @@ -129,7 +129,7 @@ func TestRunFunction(t *testing.T) { Input: resource.MustStructJSON(`{ "apiVersion": "template.fn.crossplane.io/v1alpha1", "kind": "Parameters", - "shellCommand": "unkown-shell-command", + "shellCommand": "unknown-shell-command", "stdoutField": "spec.atFunction.shell.stdout", "stderrField": "spec.atFunction.shell.stderr" }`), @@ -141,7 +141,7 @@ func TestRunFunction(t *testing.T) { Results: []*fnv1.Result{ { Severity: fnv1.Severity_SEVERITY_FATAL, - Message: "shellCmd unkown-shell-command for failed: exit status 127", + Message: "shellCmd unknown-shell-command for failed: exit status 127", Target: fnv1.Target_TARGET_COMPOSITE.Enum(), }, }, @@ -190,6 +190,155 @@ func TestRunFunction(t *testing.T) { }, }, }, + "ResponseIsEchoShellEnvVarFieldPath": { + reason: "The Function should accept and use environment variables from a fieldPath", + args: args{ + req: &fnv1.RunFunctionRequest{ + Meta: &fnv1.RequestMeta{Tag: "hello"}, + Input: resource.MustStructJSON(`{ + "apiVersion": "template.fn.crossplane.io/v1alpha1", + "kind": "Parameters", + "shellEnvVars": [{"key": "TEST_ENV_VAR", "fieldRef":{"path": "spec.foo", "policy": "Required"}, "type": "FieldRef"}], + "shellCommand": "echo ${TEST_ENV_VAR}", + "stdoutField": "spec.atFunction.shell.stdout" + }`), + Observed: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(`{ + "apiVersion": "", + "kind": "", + "spec": { + "foo": "bar" + } + }`), + }, + }, + }, + }, + want: want{ + rsp: &fnv1.RunFunctionResponse{ + Meta: &fnv1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)}, + Desired: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(`{ + "apiVersion": "", + "kind": "", + "spec": { + "atFunction": { + "shell": { + "stdout": "bar" + } + } + }, + "status": { + "atFunction": { + "shell": { + "stderr": "" + } + } + } + }`), + }, + }, + }, + }, + }, + "ResponseIsEchoEnvVarFieldRefDefaultValue": { + reason: "The Function should accept and use environment variables from a default FieldRef ", + args: args{ + req: &fnv1.RunFunctionRequest{ + Meta: &fnv1.RequestMeta{Tag: "hello"}, + Input: resource.MustStructJSON(`{ + "apiVersion": "template.fn.crossplane.io/v1alpha1", + "kind": "Parameters", + "shellEnvVars": [{"key": "TEST_ENV_VAR", "fieldRef":{"path": "spec.bad", "policy": "Optional", "defaultValue": "default"}, "type": "FieldRef"}], + "shellCommand": "echo ${TEST_ENV_VAR}", + "stdoutField": "spec.atFunction.shell.stdout" + }`), + }, + }, + want: want{ + rsp: &fnv1.RunFunctionResponse{ + Meta: &fnv1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)}, + Desired: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(`{ + "apiVersion": "", + "kind": "", + "spec": { + "atFunction": { + "shell": { + "stdout": "default" + } + } + }, + "status": { + "atFunction": { + "shell": { + "stderr": "" + } + } + } + }`), + }, + }, + }, + }, + }, + "ResponseIsErrorWhenBadShellEnvVarTypeIsProvided": { + reason: "The Function should return an error when a bad shellEnVars type is provided", + args: args{ + req: &fnv1.RunFunctionRequest{ + Meta: &fnv1.RequestMeta{Tag: "hello"}, + Input: resource.MustStructJSON(`{ + "apiVersion": "template.fn.crossplane.io/v1alpha1", + "kind": "Parameters", + "shellEnvVars": [{"key": "TEST_ENV_VAR", "value": "foo", "type": "bad"}], + "shellCommand": "echo ${TEST_ENV_VAR}", + "stdoutField": "spec.atFunction.shell.stdout" + }`), + }, + }, + want: want{ + rsp: &fnv1.RunFunctionResponse{ + Meta: &fnv1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)}, + Results: []*fnv1.Result{ + { + Severity: fnv1.Severity_SEVERITY_FATAL, + Message: "shellEnvVars: unknown type bad for key TEST_ENV_VAR", + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), + }, + }, + }, + }, + }, + "ResponseIsErrorWhenShellEnvVarFieldPathIsEmpty": { + reason: "The Function should return an error when a shellEnvVars fieldRef.path is empty", + args: args{ + req: &fnv1.RunFunctionRequest{ + Meta: &fnv1.RequestMeta{Tag: "hello"}, + Input: resource.MustStructJSON(`{ + "apiVersion": "template.fn.crossplane.io/v1alpha1", + "kind": "Parameters", + "shellEnvVars": [{"key": "TEST_ENV_VAR", "fieldRef":{"policy": "Optional", "defaultValue": "default"}, "type": "FieldRef"}], + "shellCommand": "echo ${TEST_ENV_VAR}", + "stdoutField": "spec.atFunction.shell.stdout" + }`), + }, + }, + want: want{ + rsp: &fnv1.RunFunctionResponse{ + Meta: &fnv1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)}, + Results: []*fnv1.Result{ + { + Severity: fnv1.Severity_SEVERITY_FATAL, + Message: "cannot process contents of fieldRef : path must be set", + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), + }, + }, + }, + }, + }, } for name, tc := range cases { diff --git a/input/v1alpha1/parameters.go b/input/v1alpha1/parameters.go index 17484fc..339406a 100644 --- a/input/v1alpha1/parameters.go +++ b/input/v1alpha1/parameters.go @@ -44,10 +44,38 @@ type Parameters struct { StderrField string `json:"stderrField,omitempty"` } +// ShellEnvVarType is a type of ShellEnvVar +type ShellEnvVarType string + +const ( + ShellEnvVarTypeFieldRef ShellEnvVarType = "FieldRef" + ShellEnvVarTypeValue ShellEnvVarType = "Value" + ShellEnvVarTypeValueRef ShellEnvVarType = "ValueRef" +) + type ShellEnvVar struct { Key string `json:"key,omitempty"` Value string `json:"value,omitempty"` ValueRef string `json:"valueRef,omitempty"` + // FieldRef is a reference to a field in the Composition + FieldRef *FieldRef `json:"fieldRef,omitempty"` + Type ShellEnvVarType `json:"type,omitempty"` +} + +// GetType determines the ShellEnvVar type +func (sev *ShellEnvVar) GetType() ShellEnvVarType { + if sev.Type == "" { + if sev.Value != "" { + return ShellEnvVarTypeValue + } + if sev.ValueRef != "" { + return ShellEnvVarTypeValueRef + } + if sev.FieldRef != nil { + return ShellEnvVarTypeFieldRef + } + } + return sev.Type } type ShellEnvVarsRef struct { @@ -56,3 +84,30 @@ type ShellEnvVarsRef struct { // Name of the enviroment variable Name string `json:"name,omitempty"` } + +// FieldRefPolicy is a field path Policy +type FieldRefPolicy string + +// FieldRefPolicyOptional if the field is not available use the value of FieldRefDefault +const FieldRefPolicyOptional = "Optional" + +// FieldRefPolicyRequired will error if the field is not available +const FieldRefPolicyRequired = "Required" + +// FieldRefDefault optional value result returned for an Optional FieldRef. Defaults to an empty string +const FieldRefDefault = "" + +type FieldRef struct { + // Path is the field path of the field being referenced, i.e. spec.myfield, status.output + Path string `json:"path"` + // Policy when the field is not available. If set to "Required" will return + // an error if a field is missing. If set to "Optional" will return DefaultValue. + // +optional + // +kubebuilder:default:=Required + // +kubebuilder:validation:Enum=Optional;Required + Policy FieldRefPolicy `json:"policy,omitempty"` + // DefaultValue when Policy is Optional and field is not available defaults to "" + // +optional + // +kbuebuilder:default:="" + DefaultValue string `json:"defaultValue,omitempty"` +} diff --git a/input/v1alpha1/zz_generated.deepcopy.go b/input/v1alpha1/zz_generated.deepcopy.go index 4dee1ad..ce9f4f2 100644 --- a/input/v1alpha1/zz_generated.deepcopy.go +++ b/input/v1alpha1/zz_generated.deepcopy.go @@ -8,6 +8,21 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FieldRef) DeepCopyInto(out *FieldRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FieldRef. +func (in *FieldRef) DeepCopy() *FieldRef { + if in == nil { + return nil + } + out := new(FieldRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Parameters) DeepCopyInto(out *Parameters) { *out = *in @@ -17,7 +32,9 @@ func (in *Parameters) DeepCopyInto(out *Parameters) { if in.ShellEnvVars != nil { in, out := &in.ShellEnvVars, &out.ShellEnvVars *out = make([]ShellEnvVar, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } } @@ -42,6 +59,11 @@ func (in *Parameters) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ShellEnvVar) DeepCopyInto(out *ShellEnvVar) { *out = *in + if in.FieldRef != nil { + in, out := &in.FieldRef, &out.FieldRef + *out = new(FieldRef) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShellEnvVar. diff --git a/package/input/template.fn.crossplane.io_parameters.yaml b/package/input/template.fn.crossplane.io_parameters.yaml index cea0c50..340bd33 100644 --- a/package/input/template.fn.crossplane.io_parameters.yaml +++ b/package/input/template.fn.crossplane.io_parameters.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.18.0 name: parameters.template.fn.crossplane.io spec: group: template.fn.crossplane.io @@ -48,8 +48,34 @@ spec: description: shellEnvVars items: properties: + fieldRef: + description: FieldRef is a reference to a field in the Composition + properties: + defaultValue: + description: DefaultValue when Policy is Optional and field + is not available defaults to "" + type: string + path: + description: Path is the field path of the field being referenced, + i.e. spec.myfield, status.output + type: string + policy: + default: Required + description: |- + Policy when the field is not available. If set to "Required" will return + an error if a field is missing. If set to "Optional" will return DefaultValue. + enum: + - Optional + - Required + type: string + required: + - path + type: object key: type: string + type: + description: ShellEnvVarType is a type of ShellEnvVar + type: string value: type: string valueRef: