diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index 955a8411..486f38d9 100755 --- a/apis/v1alpha1/ack-generate-metadata.yaml +++ b/apis/v1alpha1/ack-generate-metadata.yaml @@ -1,13 +1,13 @@ ack_generate_info: - build_date: "2023-05-15T23:18:19Z" - build_hash: 8f3ba427974fd6e769926778d54834eaee3b81a3 + build_date: "2023-06-07T18:16:37Z" + build_hash: dcb9fd6c564ce817a48abd6f7b9ee4308aa1d13f go_version: go1.19 - version: v0.26.1 -api_directory_checksum: a9fcef68210dd72b4b2e37052f2c1a9e971326c6 + version: v0.26.1-4-gdcb9fd6 +api_directory_checksum: ca34c731b281f29e80984c872002e220ddee5133 api_version: v1alpha1 aws_sdk_go_version: v1.44.181 generator_config_info: - file_checksum: 095af1082df5c34cdc12296dc085bc6b2b7eadb9 + file_checksum: d53a6ae44af0a12af854385908c6e09355b3a126 original_file_name: generator.yaml last_modification: reason: API generation diff --git a/apis/v1alpha1/function.go b/apis/v1alpha1/function.go index 243bd103..f6662baf 100644 --- a/apis/v1alpha1/function.go +++ b/apis/v1alpha1/function.go @@ -46,7 +46,8 @@ type FunctionSpec struct { // but can be any whole number between 512 and 10,240 MB. EphemeralStorage *EphemeralStorage `json:"ephemeralStorage,omitempty"` // Connection settings for an Amazon EFS file system. - FileSystemConfigs []*FileSystemConfig `json:"fileSystemConfigs,omitempty"` + FileSystemConfigs []*FileSystemConfig `json:"fileSystemConfigs,omitempty"` + FunctionEventInvokeConfig *PutFunctionEventInvokeConfigInput `json:"functionEventInvokeConfig,omitempty"` // The name of the method within your code that Lambda calls to run your function. // Handler is required if the deployment package is a .zip file archive. The // format includes the file name. It can also include namespaces and other qualifiers, diff --git a/apis/v1alpha1/generator.yaml b/apis/v1alpha1/generator.yaml index 52018aec..467bd01e 100644 --- a/apis/v1alpha1/generator.yaml +++ b/apis/v1alpha1/generator.yaml @@ -61,6 +61,10 @@ resources: from: operation: GetFunction path: Configuration.Layers + FunctionEventInvokeConfig: + from: + operation: PutFunctionEventInvokeConfig + path: . renames: operations: CreateFunction: diff --git a/apis/v1alpha1/types.go b/apis/v1alpha1/types.go index 735bb4b5..a32c3620 100644 --- a/apis/v1alpha1/types.go +++ b/apis/v1alpha1/types.go @@ -279,9 +279,11 @@ type FunctionConfiguration struct { type FunctionEventInvokeConfig struct { // A configuration object that specifies the destination of an event after Lambda // processes it. - DestinationConfig *DestinationConfig `json:"destinationConfig,omitempty"` - FunctionARN *string `json:"functionARN,omitempty"` - LastModified *metav1.Time `json:"lastModified,omitempty"` + DestinationConfig *DestinationConfig `json:"destinationConfig,omitempty"` + FunctionARN *string `json:"functionARN,omitempty"` + LastModified *metav1.Time `json:"lastModified,omitempty"` + MaximumEventAgeInSeconds *int64 `json:"maximumEventAgeInSeconds,omitempty"` + MaximumRetryAttempts *int64 `json:"maximumRetryAttempts,omitempty"` } // Details about a Lambda function URL. @@ -388,6 +390,16 @@ type PutFunctionConcurrencyOutput struct { ReservedConcurrentExecutions *int64 `json:"reservedConcurrentExecutions,omitempty"` } +type PutFunctionEventInvokeConfigInput struct { + // A configuration object that specifies the destination of an event after Lambda + // processes it. + DestinationConfig *DestinationConfig `json:"destinationConfig,omitempty"` + FunctionName *string `json:"functionName,omitempty"` + MaximumEventAgeInSeconds *int64 `json:"maximumEventAgeInSeconds,omitempty"` + MaximumRetryAttempts *int64 `json:"maximumRetryAttempts,omitempty"` + Qualifier *string `json:"qualifier,omitempty"` +} + // (Amazon SQS only) The scaling configuration for the event source. To remove // the configuration, pass an empty value. type ScalingConfig struct { diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 98a7d068..b445da35 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -1608,6 +1608,16 @@ func (in *FunctionEventInvokeConfig) DeepCopyInto(out *FunctionEventInvokeConfig in, out := &in.LastModified, &out.LastModified *out = (*in).DeepCopy() } + if in.MaximumEventAgeInSeconds != nil { + in, out := &in.MaximumEventAgeInSeconds, &out.MaximumEventAgeInSeconds + *out = new(int64) + **out = **in + } + if in.MaximumRetryAttempts != nil { + in, out := &in.MaximumRetryAttempts, &out.MaximumRetryAttempts + *out = new(int64) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FunctionEventInvokeConfig. @@ -1707,6 +1717,11 @@ func (in *FunctionSpec) DeepCopyInto(out *FunctionSpec) { } } } + if in.FunctionEventInvokeConfig != nil { + in, out := &in.FunctionEventInvokeConfig, &out.FunctionEventInvokeConfig + *out = new(PutFunctionEventInvokeConfigInput) + (*in).DeepCopyInto(*out) + } if in.Handler != nil { in, out := &in.Handler, &out.Handler *out = new(string) @@ -2673,6 +2688,46 @@ func (in *PutFunctionConcurrencyOutput) DeepCopy() *PutFunctionConcurrencyOutput return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PutFunctionEventInvokeConfigInput) DeepCopyInto(out *PutFunctionEventInvokeConfigInput) { + *out = *in + if in.DestinationConfig != nil { + in, out := &in.DestinationConfig, &out.DestinationConfig + *out = new(DestinationConfig) + (*in).DeepCopyInto(*out) + } + if in.FunctionName != nil { + in, out := &in.FunctionName, &out.FunctionName + *out = new(string) + **out = **in + } + if in.MaximumEventAgeInSeconds != nil { + in, out := &in.MaximumEventAgeInSeconds, &out.MaximumEventAgeInSeconds + *out = new(int64) + **out = **in + } + if in.MaximumRetryAttempts != nil { + in, out := &in.MaximumRetryAttempts, &out.MaximumRetryAttempts + *out = new(int64) + **out = **in + } + if in.Qualifier != nil { + in, out := &in.Qualifier, &out.Qualifier + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PutFunctionEventInvokeConfigInput. +func (in *PutFunctionEventInvokeConfigInput) DeepCopy() *PutFunctionEventInvokeConfigInput { + if in == nil { + return nil + } + out := new(PutFunctionEventInvokeConfigInput) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ScalingConfig) DeepCopyInto(out *ScalingConfig) { *out = *in diff --git a/config/crd/bases/lambda.services.k8s.aws_functions.yaml b/config/crd/bases/lambda.services.k8s.aws_functions.yaml index 9c1a8042..d2dc8652 100644 --- a/config/crd/bases/lambda.services.k8s.aws_functions.yaml +++ b/config/crd/bases/lambda.services.k8s.aws_functions.yaml @@ -116,6 +116,37 @@ spec: type: string type: object type: array + functionEventInvokeConfig: + properties: + destinationConfig: + description: A configuration object that specifies the destination + of an event after Lambda processes it. + properties: + onFailure: + description: A destination for events that failed processing. + properties: + destination: + type: string + type: object + onSuccess: + description: A destination for events that were processed + successfully. + properties: + destination: + type: string + type: object + type: object + functionName: + type: string + maximumEventAgeInSeconds: + format: int64 + type: integer + maximumRetryAttempts: + format: int64 + type: integer + qualifier: + type: string + type: object handler: description: The name of the method within your code that Lambda calls to run your function. Handler is required if the deployment package diff --git a/generator.yaml b/generator.yaml index 52018aec..467bd01e 100644 --- a/generator.yaml +++ b/generator.yaml @@ -61,6 +61,10 @@ resources: from: operation: GetFunction path: Configuration.Layers + FunctionEventInvokeConfig: + from: + operation: PutFunctionEventInvokeConfig + path: . renames: operations: CreateFunction: diff --git a/helm/crds/lambda.services.k8s.aws_functions.yaml b/helm/crds/lambda.services.k8s.aws_functions.yaml index de8991b1..ec142a0b 100644 --- a/helm/crds/lambda.services.k8s.aws_functions.yaml +++ b/helm/crds/lambda.services.k8s.aws_functions.yaml @@ -116,6 +116,37 @@ spec: type: string type: object type: array + functionEventInvokeConfig: + properties: + destinationConfig: + description: A configuration object that specifies the destination + of an event after Lambda processes it. + properties: + onFailure: + description: A destination for events that failed processing. + properties: + destination: + type: string + type: object + onSuccess: + description: A destination for events that were processed + successfully. + properties: + destination: + type: string + type: object + type: object + functionName: + type: string + maximumEventAgeInSeconds: + format: int64 + type: integer + maximumRetryAttempts: + format: int64 + type: integer + qualifier: + type: string + type: object handler: description: The name of the method within your code that Lambda calls to run your function. Handler is required if the deployment package diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index 7504a614..4d087a52 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -18,10 +18,12 @@ spec: app.kubernetes.io/instance: {{ .Release.Name }} template: metadata: +{{- if .Values.deployment.annotations }} annotations: {{- range $key, $value := .Values.deployment.annotations }} {{ $key }}: {{ $value | quote }} {{- end }} +{{- end }} labels: app.kubernetes.io/name: {{ include "app.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} @@ -104,11 +106,19 @@ spec: value: {{ include "aws.credentials.path" . }} - name: AWS_PROFILE value: {{ .Values.aws.credentials.profile }} + {{- end }} + {{- if .Values.deployment.extraEnvVars -}} + {{ toYaml .Values.deployment.extraEnvVars | nindent 8 }} + {{- end }} volumeMounts: + {{- if .Values.aws.credentials.secretName }} - name: {{ .Values.aws.credentials.secretName }} mountPath: {{ include "aws.credentials.secret_mount_path" . }} readOnly: true {{- end }} + {{- if .Values.deployment.extraVolumeMounts -}} + {{ toYaml .Values.deployment.extraVolumeMounts | nindent 12 }} + {{- end }} securityContext: allowPrivilegeEscalation: false privileged: false @@ -133,9 +143,12 @@ spec: hostIPC: false hostNetwork: false hostPID: false - {{ if .Values.aws.credentials.secretName -}} volumes: + {{- if .Values.aws.credentials.secretName -}} - name: {{ .Values.aws.credentials.secretName }} secret: secretName: {{ .Values.aws.credentials.secretName }} {{ end -}} +{{- if .Values.deployment.extraVolumes }} +{{ toYaml .Values.deployment.extraVolumes | indent 8}} +{{- end }} diff --git a/helm/values.schema.json b/helm/values.schema.json index 79fd18ce..fb4437b7 100644 --- a/helm/values.schema.json +++ b/helm/values.schema.json @@ -58,6 +58,15 @@ }, "priorityClassName": { "type": "string" + }, + "extraVolumeMounts": { + "type": "array" + }, + "extraVolumes": { + "type": "array" + }, + "extraEnvVars": { + "type": "array" } }, "required": [ diff --git a/helm/values.yaml b/helm/values.yaml index 1c5375a6..a4828a31 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -28,6 +28,26 @@ deployment: # Which priorityClassName to set? # See: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/#pod-priority priorityClassName: "" + extraVolumes: [] + extraVolumeMounts: [] + + # Additional server container environment variables + # + # You specify this manually like you would a raw deployment manifest. + # This means you can bind in environment variables from secrets. + # + # e.g. static environment variable: + # - name: DEMO_GREETING + # value: "Hello from the environment" + # + # e.g. secret environment variable: + # - name: USERNAME + # valueFrom: + # secretKeyRef: + # name: mysecret + # key: username + extraEnvVars: [] + # If "installScope: cluster" then these labels will be applied to ClusterRole role: diff --git a/pkg/resource/function/delta.go b/pkg/resource/function/delta.go index 81d96b37..2a8f5cfb 100644 --- a/pkg/resource/function/delta.go +++ b/pkg/resource/function/delta.go @@ -97,6 +97,64 @@ func newResourceDelta( if !reflect.DeepEqual(a.ko.Spec.FileSystemConfigs, b.ko.Spec.FileSystemConfigs) { delta.Add("Spec.FileSystemConfigs", a.ko.Spec.FileSystemConfigs, b.ko.Spec.FileSystemConfigs) } + if ackcompare.HasNilDifference(a.ko.Spec.FunctionEventInvokeConfig, b.ko.Spec.FunctionEventInvokeConfig) { + delta.Add("Spec.FunctionEventInvokeConfig", a.ko.Spec.FunctionEventInvokeConfig, b.ko.Spec.FunctionEventInvokeConfig) + } else if a.ko.Spec.FunctionEventInvokeConfig != nil && b.ko.Spec.FunctionEventInvokeConfig != nil { + if ackcompare.HasNilDifference(a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig, b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig) { + delta.Add("Spec.FunctionEventInvokeConfig.DestinationConfig", a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig, b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig) + } else if a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig != nil && b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig != nil { + if ackcompare.HasNilDifference(a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure, b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure) { + delta.Add("Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure", a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure, b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure) + } else if a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure != nil && b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure != nil { + if ackcompare.HasNilDifference(a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination, b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination) { + delta.Add("Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination", a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination, b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination) + } else if a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination != nil && b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination != nil { + if *a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination != *b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination { + delta.Add("Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination", a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination, b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination) + } + } + } + if ackcompare.HasNilDifference(a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess, b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess) { + delta.Add("Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess", a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess, b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess) + } else if a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess != nil && b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess != nil { + if ackcompare.HasNilDifference(a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination, b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination) { + delta.Add("Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination", a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination, b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination) + } else if a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination != nil && b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination != nil { + if *a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination != *b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination { + delta.Add("Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination", a.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination, b.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination) + } + } + } + } + if ackcompare.HasNilDifference(a.ko.Spec.FunctionEventInvokeConfig.FunctionName, b.ko.Spec.FunctionEventInvokeConfig.FunctionName) { + delta.Add("Spec.FunctionEventInvokeConfig.FunctionName", a.ko.Spec.FunctionEventInvokeConfig.FunctionName, b.ko.Spec.FunctionEventInvokeConfig.FunctionName) + } else if a.ko.Spec.FunctionEventInvokeConfig.FunctionName != nil && b.ko.Spec.FunctionEventInvokeConfig.FunctionName != nil { + if *a.ko.Spec.FunctionEventInvokeConfig.FunctionName != *b.ko.Spec.FunctionEventInvokeConfig.FunctionName { + delta.Add("Spec.FunctionEventInvokeConfig.FunctionName", a.ko.Spec.FunctionEventInvokeConfig.FunctionName, b.ko.Spec.FunctionEventInvokeConfig.FunctionName) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds, b.ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds) { + delta.Add("Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds", a.ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds, b.ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds) + } else if a.ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds != nil && b.ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds != nil { + if *a.ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds != *b.ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds { + delta.Add("Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds", a.ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds, b.ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts, b.ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts) { + delta.Add("Spec.FunctionEventInvokeConfig.MaximumRetryAttempts", a.ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts, b.ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts) + } else if a.ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts != nil && b.ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts != nil { + if *a.ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts != *b.ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts { + delta.Add("Spec.FunctionEventInvokeConfig.MaximumRetryAttempts", a.ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts, b.ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.FunctionEventInvokeConfig.Qualifier, b.ko.Spec.FunctionEventInvokeConfig.Qualifier) { + delta.Add("Spec.FunctionEventInvokeConfig.Qualifier", a.ko.Spec.FunctionEventInvokeConfig.Qualifier, b.ko.Spec.FunctionEventInvokeConfig.Qualifier) + } else if a.ko.Spec.FunctionEventInvokeConfig.Qualifier != nil && b.ko.Spec.FunctionEventInvokeConfig.Qualifier != nil { + if *a.ko.Spec.FunctionEventInvokeConfig.Qualifier != *b.ko.Spec.FunctionEventInvokeConfig.Qualifier { + delta.Add("Spec.FunctionEventInvokeConfig.Qualifier", a.ko.Spec.FunctionEventInvokeConfig.Qualifier, b.ko.Spec.FunctionEventInvokeConfig.Qualifier) + } + } + } if ackcompare.HasNilDifference(a.ko.Spec.Handler, b.ko.Spec.Handler) { delta.Add("Spec.Handler", a.ko.Spec.Handler, b.ko.Spec.Handler) } else if a.ko.Spec.Handler != nil && b.ko.Spec.Handler != nil { diff --git a/pkg/resource/function/hooks.go b/pkg/resource/function/hooks.go index c56e6669..6c1a8860 100644 --- a/pkg/resource/function/hooks.go +++ b/pkg/resource/function/hooks.go @@ -16,6 +16,7 @@ package function import ( "context" "errors" + "fmt" "time" ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" @@ -79,6 +80,12 @@ func (rm *resourceManager) customUpdateFunction( return nil, err } } + if delta.DifferentAt("Spec.FunctionEventInvokeConfig") { + err = rm.updateFunctionEventInvokeConfig(ctx, desired) + if err != nil { + return nil, err + } + } if delta.DifferentAt("Spec.CodeSigningConfigARN") { if desired.ko.Spec.PackageType != nil && *desired.ko.Spec.PackageType == "Image" && desired.ko.Spec.CodeSigningConfigARN != nil && *desired.ko.Spec.CodeSigningConfigARN != "" { @@ -471,6 +478,53 @@ func (rm *resourceManager) updateFunctionConcurrency( return nil } +func (rm *resourceManager) updateFunctionEventInvokeConfig( + ctx context.Context, + desired *resource, +) error { + var err error + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.updateFunctionEventInvokeConfig") + defer exit(err) + + dspec := desired.ko.Spec + input := &svcsdk.PutFunctionEventInvokeConfigInput{ + FunctionName: aws.String(*dspec.Name), + } + + if desired.ko.Spec.FunctionEventInvokeConfig != nil { + if desired.ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds != nil { + input.MaximumEventAgeInSeconds = aws.Int64(*desired.ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds) + } + if desired.ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts != nil { + input.MaximumRetryAttempts = aws.Int64(*desired.ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts) + } + if desired.ko.Spec.FunctionEventInvokeConfig.DestinationConfig != nil { + destinations := &svcsdk.DestinationConfig{} + if desired.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure != nil { + destinations.OnFailure = &svcsdk.OnFailure{} + if desired.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination != nil { + destinations.OnFailure.Destination = aws.String(*desired.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination) + } + } + if desired.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess != nil { + destinations.OnSuccess = &svcsdk.OnSuccess{} + if desired.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination != nil { + destinations.OnSuccess.Destination = aws.String(*desired.ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination) + } + } + input.DestinationConfig = destinations + } + } + + _, err = rm.sdkapi.PutFunctionEventInvokeConfigWithContext(ctx, input) + rm.metrics.RecordAPICall("UPDATE", "PutFunctionEventInvokeConfig", err) + if err != nil { + return err + } + return nil +} + // updateFunctionCodeSigningConfig calls PutFunctionCodeSigningConfig to update // it code signing configuration func (rm *resourceManager) updateFunctionCodeSigningConfig( @@ -547,6 +601,43 @@ func (rm *resourceManager) setResourceAdditionalFields( } ko.Spec.ReservedConcurrentExecutions = getFunctionConcurrencyOutput.ReservedConcurrentExecutions + var getFunctionEventInvokeConfigOutput *svcsdk.GetFunctionEventInvokeConfigOutput + getFunctionEventInvokeConfigOutput, err = rm.sdkapi.GetFunctionEventInvokeConfigWithContext( + ctx, + &svcsdk.GetFunctionEventInvokeConfigInput{ + FunctionName: ko.Spec.Name, + }, + ) + rm.metrics.RecordAPICall("GET", "GetFunctionEventInvokeConfig", err) + if err != nil { + ko.Spec.FunctionEventInvokeConfig = nil + } else { + fmt.Println(ko.Spec.FunctionEventInvokeConfig.DestinationConfig) + if getFunctionEventInvokeConfigOutput.DestinationConfig != nil { + if getFunctionEventInvokeConfigOutput.DestinationConfig.OnFailure != nil { + if getFunctionEventInvokeConfigOutput.DestinationConfig.OnFailure.Destination != nil { + ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnFailure.Destination = getFunctionEventInvokeConfigOutput.DestinationConfig.OnFailure.Destination + } + } + if getFunctionEventInvokeConfigOutput.DestinationConfig.OnSuccess != nil { + if getFunctionEventInvokeConfigOutput.DestinationConfig.OnSuccess.Destination != nil { + ko.Spec.FunctionEventInvokeConfig.DestinationConfig.OnSuccess.Destination = getFunctionEventInvokeConfigOutput.DestinationConfig.OnSuccess.Destination + } + } + } else { + ko.Spec.FunctionEventInvokeConfig.DestinationConfig = nil + } + if getFunctionEventInvokeConfigOutput.MaximumEventAgeInSeconds != nil { + ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds = getFunctionEventInvokeConfigOutput.MaximumEventAgeInSeconds + } else { + ko.Spec.FunctionEventInvokeConfig.MaximumEventAgeInSeconds = nil + } + if getFunctionEventInvokeConfigOutput.DestinationConfig != nil { + ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts = getFunctionEventInvokeConfigOutput.MaximumRetryAttempts + } else { + ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts = nil + } + } if ko.Spec.PackageType != nil && *ko.Spec.PackageType == "Zip" { var getFunctionCodeSigningConfigOutput *svcsdk.GetFunctionCodeSigningConfigOutput getFunctionCodeSigningConfigOutput, err = rm.sdkapi.GetFunctionCodeSigningConfigWithContext( diff --git a/test/e2e/bootstrap_resources.py b/test/e2e/bootstrap_resources.py index a6396f67..ee1108d0 100644 --- a/test/e2e/bootstrap_resources.py +++ b/test/e2e/bootstrap_resources.py @@ -34,6 +34,9 @@ class BootstrapResources(Resources): ESMRole: Role ESMTable: Table ESMQueue: Queue + EICRole: Role + EICQueueOnSuccess: Queue + EICQueueOnFailure: Queue _bootstrap_resources = None diff --git a/test/e2e/resources/function_event_invoke_config.yaml b/test/e2e/resources/function_event_invoke_config.yaml new file mode 100644 index 00000000..9ad77289 --- /dev/null +++ b/test/e2e/resources/function_event_invoke_config.yaml @@ -0,0 +1,23 @@ +apiVersion: lambda.services.k8s.aws/v1alpha1 +kind: Function +metadata: + name: $FUNCTION_NAME + annotations: + services.k8s.aws/region: $AWS_REGION +spec: + name: $FUNCTION_NAME + code: + s3Bucket: $BUCKET_NAME + s3Key: $LAMBDA_FILE_NAME + functionEventInvokeConfig: + destinationConfig: + onSuccess: + destination: $ON_SUCCESS_DESTINATION + onFailure: + destination: $ON_FAILURE_DESTINATION + maximumEventAgeInSeconds: $MAXIMUM_EVENT_AGE_IN_SECONDS + maximumRetryAttempts: $MAXIMUM_RETRY_ATTEMPTS + role: $LAMBDA_ROLE + runtime: python3.9 + handler: main + description: function created by ACK lambda-controller e2e tests \ No newline at end of file diff --git a/test/e2e/service_bootstrap.py b/test/e2e/service_bootstrap.py index cfb46344..79dc82bf 100644 --- a/test/e2e/service_bootstrap.py +++ b/test/e2e/service_bootstrap.py @@ -37,6 +37,7 @@ BASIC_ROLE_POLICIES = [ LAMBDA_BASIC_EXECUTION_ARN ] ESM_ROLE_POLICIES = [ LAMBDA_BASIC_EXECUTION_ARN, LAMBDA_DYNAMODB_EXECUTION_ROLE, LAMBDA_SQS_QUEUE_EXECUTION_ROLE ] +EIC_ROLE_POLICIES = [LAMBDA_BASIC_EXECUTION_ARN, LAMBDA_SQS_QUEUE_EXECUTION_ROLE] LAMBDA_FUNCTION_FILE = "main.py" LAMBDA_FUNCTION_FILE_ZIP = "main.zip" @@ -116,8 +117,19 @@ def service_bootstrap() -> Resources: }, ), ESMQueue=Queue( - "ack-lambda-controller-queue" + "ack-lambda-controller-queue", ), + EICRole=Role( + "ack-lambda-controller-eic-role", + principal_service="lambda.amazonaws.com", + managed_policies=EIC_ROLE_POLICIES, + ), + EICQueueOnSuccess=Queue( + "ack-lambda-controller-function-queue-eic-onsuccess" + ), + EICQueueOnFailure=Queue( + "ack-lambda-controller-function-queue-eic-onfailure" + ) ) try: diff --git a/test/e2e/tests/helper.py b/test/e2e/tests/helper.py index bf91d663..107a543e 100644 --- a/test/e2e/tests/helper.py +++ b/test/e2e/tests/helper.py @@ -136,4 +136,17 @@ def list_layer_versions(self, layer_name:str) -> list: return resp except Exception as e: logging.debug(e) - return None \ No newline at end of file + return None + + def get_function_event_invoke_config(self, function_name:str) -> dict: + try: + resp = self.lambda_client.get_function_event_invoke_config( + FunctionName = function_name, + ) + return resp + except Exception as e: + logging.debug(e) + return None + + def function_event_invoke_config_exists(self, function_name: str) -> bool: + return self.get_function_event_invoke_config(function_name) is not None \ No newline at end of file diff --git a/test/e2e/tests/test_function.py b/test/e2e/tests/test_function.py index 23622864..419b9e25 100644 --- a/test/e2e/tests/test_function.py +++ b/test/e2e/tests/test_function.py @@ -536,5 +536,71 @@ def test_function_snapstart(self, lambda_client): time.sleep(DELETE_WAIT_AFTER_SECONDS) + # Check Lambda function doesn't exist + assert not lambda_validator.function_exists(resource_name) + + def test_function_event_invoke_config(self, lambda_client): + resource_name = random_suffix_name("lambda-function", 24) + + resources = get_bootstrap_resources() + logging.debug(resources) + + replacements = REPLACEMENT_VALUES.copy() + replacements["FUNCTION_NAME"] = resource_name + replacements["BUCKET_NAME"] = resources.FunctionsBucket.name + replacements["LAMBDA_ROLE"] = resources.EICRole.arn + replacements["LAMBDA_FILE_NAME"] = LAMBDA_FUNCTION_FILE_ZIP + replacements["AWS_REGION"] = get_region() + replacements["MAXIMUM_EVENT_AGE_IN_SECONDS"] = "100" + replacements["MAXIMUM_RETRY_ATTEMPTS"] = "1" + replacements["ON_SUCCESS_DESTINATION"] = resources.EICQueueOnSuccess.arn + replacements["ON_FAILURE_DESTINATION"] = resources.EICQueueOnFailure.arn + + # Load Lambda CR + resource_data = load_lambda_resource( + "function_event_invoke_config", + additional_replacements=replacements, + ) + logging.debug(resource_data) + + # Create k8s resource + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, + resource_name, namespace="default", + ) + k8s.create_custom_resource(ref, resource_data) + cr = k8s.wait_resource_consumed_by_controller(ref) + + assert cr is not None + assert k8s.get_resource_exists(ref) + + time.sleep(CREATE_WAIT_AFTER_SECONDS) + + cr = k8s.wait_resource_consumed_by_controller(ref) + + lambda_validator = LambdaValidator(lambda_client) + + # Check Lambda function exists + assert lambda_validator.function_exists(resource_name) + + # Update cr + cr["spec"]["functionEventInvokeConfig"]["maximumEventAgeInSeconds"] = 200 + cr["spec"]["functionEventInvokeConfig"]["maximumRetryAttempts"] = 2 + + #Patch k8s resource + k8s.patch_custom_resource(ref, cr) + time.sleep(UPDATE_WAIT_AFTER_SECONDS) + + #Check function_event_invoke_config update fields + function_event_invoke_config = lambda_validator.get_function_event_invoke_config(resource_name) + assert function_event_invoke_config["MaximumEventAgeInSeconds"] == 200 + assert function_event_invoke_config["MaximumRetryAttempts"] == 2 + + # Delete k8s resource + _, deleted = k8s.delete_custom_resource(ref) + assert deleted is True + + time.sleep(DELETE_WAIT_AFTER_SECONDS) + # Check Lambda function doesn't exist assert not lambda_validator.function_exists(resource_name) \ No newline at end of file