Skip to content

Commit 7726f83

Browse files
authored
Introduce spec.code.sha256 field to help with s3 code changes (#88)
**Issue #** [#1550](aws-controllers-k8s/community#1550) **Description** This PR adds the feature to update the `Spec.Code.S3` variables. The issue arises as Lambda API does not keep record of `Spec.Code` variables, because of which ACK is not being able to compare the changes in the state for `Spec.Code` (ex: change in `Spec.Code.S3Key`) and thus fails to recognize the changes. Thus we have added a new field `Spec.Code.SHA256`, the user has to manually calculate the SHA256 for their code, they can do this by running this command `sha256sum filename.zip |cut -f1 -d\ | xxd -r -p | base64`. This field will then be compared with current SHA256 taken from `Status.CodeSHA256` and will determine if the update has been made for deployment package. **Acknowledgment** By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 8779d3e commit 7726f83

File tree

13 files changed

+314
-99
lines changed

13 files changed

+314
-99
lines changed

apis/v1alpha1/ack-generate-metadata.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ api_directory_checksum: c67645b15db39980ba51ff6303c34c5aafc55a9e
77
api_version: v1alpha1
88
aws_sdk_go_version: v1.44.181
99
generator_config_info:
10-
file_checksum: f0fa26f0d3c577f5800eb183e0ec1cea865a75ee
10+
file_checksum: 4038e6b669d1d6966f8e729a3ed323bf03593680
1111
original_file_name: generator.yaml
1212
last_modification:
1313
reason: API generation

apis/v1alpha1/generator.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ resources:
3838
- path: Status.State
3939
in: [ "Active" ]
4040
fields:
41+
Code.SHA256:
42+
type: string
43+
compare:
44+
is_ignored: true
45+
set:
46+
- ignore: "to"
47+
method: Create
4148
Code.S3Bucket:
4249
references:
4350
resource: Bucket
@@ -72,7 +79,7 @@ resources:
7279
path: ReservedConcurrentExecutions
7380
Code:
7481
compare:
75-
is_ignored: true
82+
is_ignored: false
7683
set:
7784
- ignore: true
7885
operation: ReadOne

apis/v1alpha1/types.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apis/v1alpha1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/lambda.services.k8s.aws_functions.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ spec:
7070
type: string
7171
s3ObjectVersion:
7272
type: string
73+
sha256:
74+
type: string
7375
zipFile:
7476
format: byte
7577
type: string

generator.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ resources:
3838
- path: Status.State
3939
in: [ "Active" ]
4040
fields:
41+
Code.SHA256:
42+
type: string
43+
compare:
44+
is_ignored: true
45+
set:
46+
- ignore: "to"
47+
method: Create
4148
Code.S3Bucket:
4249
references:
4350
resource: Bucket
@@ -72,7 +79,7 @@ resources:
7279
path: ReservedConcurrentExecutions
7380
Code:
7481
compare:
75-
is_ignored: true
82+
is_ignored: false
7683
set:
7784
- ignore: true
7885
operation: ReadOne

helm/crds/lambda.services.k8s.aws_functions.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ spec:
7070
type: string
7171
s3ObjectVersion:
7272
type: string
73+
sha256:
74+
type: string
7375
zipFile:
7476
format: byte
7577
type: string

pkg/resource/function/delta.go

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/resource/function/hooks.go

Lines changed: 48 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -102,25 +102,15 @@ func (rm *resourceManager) customUpdateFunction(
102102
}
103103
}
104104
}
105-
if delta.DifferentAt("Spec.Architectures") {
106-
err = rm.updateFunctionArchitectures(ctx, desired, latest)
107-
if err != nil {
108-
return nil, err
109-
}
110-
}
111105

112106
// Only try to update Spec.Code or Spec.Configuration at once. It is
113107
// not correct to sequentially call UpdateFunctionConfiguration and
114108
// UpdateFunctionCode because both of them can put the function in a
115109
// Pending state.
116110
switch {
117-
case delta.DifferentAt("Spec.Code"):
118-
err = rm.updateFunctionCode(ctx, desired, delta)
111+
case delta.DifferentAt("Spec.Code.ImageURI") || delta.DifferentAt("Spec.Code.SHA256") || delta.DifferentAt("Spec.Architectures"):
112+
err = rm.updateFunctionCode(ctx, desired, delta, latest)
119113
if err != nil {
120-
// If the source image is not available, we get an error like this:
121-
// "InvalidParameterValueException: Source image 1234567890.dkr.ecr.us-east-2.amazonaws.com/my-lambda:my-tag does not exist. Provide a valid source image."
122-
// Because this may be recoverable (i.e. the image may be pushed once a build completes),
123-
// we requeue the function for reconciliation after one minute.
124114
if strings.Contains(err.Error(), "Provide a valid source image.") {
125115
return nil, requeueWaitWhileSourceImageDoesNotExist
126116
} else {
@@ -131,6 +121,7 @@ func (rm *resourceManager) customUpdateFunction(
131121
"Spec.Code",
132122
"Spec.Tags",
133123
"Spec.ReservedConcurrentExecutions",
124+
"Spec.FunctionEventInvokeConfig",
134125
"Spec.CodeSigningConfigARN"):
135126
err = rm.updateFunctionConfiguration(ctx, desired, delta)
136127
if err != nil {
@@ -377,16 +368,17 @@ func (rm *resourceManager) updateFunctionTags(
377368
return nil
378369
}
379370

380-
// updateFunctionArchitectures calls UpdateFunctionCode to update architecture for lambda
371+
// updateFunctionsCode calls UpdateFunctionCode to update a specific lambda
381372
// function code.
382-
func (rm *resourceManager) updateFunctionArchitectures(
373+
func (rm *resourceManager) updateFunctionCode(
383374
ctx context.Context,
384375
desired *resource,
376+
delta *ackcompare.Delta,
385377
latest *resource,
386378
) error {
387379
var err error
388380
rlog := ackrtlog.FromContext(ctx)
389-
exit := rlog.Trace("rm.updateFunctionArchitectures")
381+
exit := rlog.Trace("rm.updateFunctionCode")
390382
defer exit(err)
391383

392384
dspec := desired.ko.Spec
@@ -400,60 +392,30 @@ func (rm *resourceManager) updateFunctionArchitectures(
400392
input.Architectures = nil
401393
}
402394

403-
if latest.ko.Spec.Code != nil {
404-
if latest.ko.Spec.PackageType != nil && *latest.ko.Spec.PackageType == "Image" {
405-
input.ImageUri = latest.ko.Spec.Code.ImageURI
406-
} else if latest.ko.Spec.PackageType != nil && *latest.ko.Spec.PackageType == "Zip" {
407-
input.S3Bucket = latest.ko.Spec.Code.S3Bucket
408-
input.S3Key = latest.ko.Spec.Code.S3Key
409-
}
410-
}
411-
412-
_, err = rm.sdkapi.UpdateFunctionCodeWithContext(ctx, input)
413-
rm.metrics.RecordAPICall("UPDATE", "UpdateFunctionArchitectures", err)
414-
if err != nil {
415-
return err
416-
}
417-
418-
return nil
419-
}
420-
421-
// updateFunctionsCode calls UpdateFunctionCode to update a specific lambda
422-
// function code.
423-
func (rm *resourceManager) updateFunctionCode(
424-
ctx context.Context,
425-
desired *resource,
426-
delta *ackcompare.Delta,
427-
) error {
428-
var err error
429-
rlog := ackrtlog.FromContext(ctx)
430-
exit := rlog.Trace("rm.updateFunctionCode")
431-
defer exit(err)
432-
433-
if delta.DifferentAt("Spec.Code.S3Key") &&
434-
!delta.DifferentAt("Spec.Code.S3Bucket") &&
435-
!delta.DifferentAt("Spec.Code.S3ObjectVersion") &&
436-
!delta.DifferentAt("Spec.Code.ImageURI") {
437-
log := ackrtlog.FromContext(ctx)
438-
log.Info("updating code.s3Key field is not currently supported.")
439-
return nil
440-
}
441-
442-
dspec := desired.ko.Spec
443-
input := &svcsdk.UpdateFunctionCodeInput{
444-
FunctionName: aws.String(*dspec.Name),
445-
}
446-
447395
if dspec.Code != nil {
448-
switch {
449-
case dspec.Code.ImageURI != nil:
450-
input.ImageUri = dspec.Code.ImageURI
451-
case dspec.Code.S3Bucket != nil,
452-
dspec.Code.S3Key != nil,
453-
dspec.Code.S3ObjectVersion != nil:
454-
input.S3Bucket = dspec.Code.S3Bucket
455-
input.S3Key = dspec.Code.S3Key
456-
input.S3ObjectVersion = dspec.Code.S3ObjectVersion
396+
if delta.DifferentAt("Spec.Code.SHA256") && dspec.Code.SHA256 != nil {
397+
if dspec.Code.S3Key != nil {
398+
input.S3Key = aws.String(*dspec.Code.S3Key)
399+
}
400+
if dspec.Code.S3Bucket != nil {
401+
input.S3Bucket = aws.String(*dspec.Code.S3Bucket)
402+
}
403+
if dspec.Code.S3ObjectVersion != nil {
404+
input.S3ObjectVersion = aws.String(*dspec.Code.S3ObjectVersion)
405+
}
406+
} else if delta.DifferentAt("Spec.Code.ImageURI") && dspec.Code.ImageURI != nil {
407+
if dspec.Code.ImageURI != nil {
408+
input.ImageUri = aws.String(*dspec.Code.ImageURI)
409+
}
410+
411+
} else { // We need to pass the latest code to Update API call,
412+
//if there is change in architecture and no change in Code
413+
if latest.ko.Spec.PackageType != nil && *latest.ko.Spec.PackageType == "Image" {
414+
input.ImageUri = latest.ko.Spec.Code.ImageURI
415+
} else if latest.ko.Spec.PackageType != nil && *latest.ko.Spec.PackageType == "Zip" {
416+
input.S3Bucket = latest.ko.Spec.Code.S3Bucket
417+
input.S3Key = latest.ko.Spec.Code.S3Key
418+
}
457419
}
458420
}
459421

@@ -501,36 +463,27 @@ func customPreCompare(
501463
a *resource,
502464
b *resource,
503465
) {
466+
// No need to compare difference in S3 Key/Bucket/ObjectVersion. As in sdkFind() there is a copy 'ko := r.ko.DeepCopy()'
467+
// of S3 Key/Bucket/ObjectVersion passed. This 'ko' then stores the values of latest S3 fields which API returns
468+
// and compares it with desired field values. Since the API doesn't return values of S3 fields, it doesn't
469+
// notice any changes between desired and latest, hence fails to recognize the update in the values.
470+
471+
// To solve this we created a new field 'Code.SHA256' to store the hash value of deployment package. Any change
472+
// in hash value refers to change in S3 Key/Bucket/ObjectVersion and controller can recognize the change in
473+
// desired and latest value of 'Code.SHA256' and hence calls the update function.
474+
504475
if ackcompare.HasNilDifference(a.ko.Spec.Code, b.ko.Spec.Code) {
505476
delta.Add("Spec.Code", a.ko.Spec.Code, b.ko.Spec.Code)
506477
} else if a.ko.Spec.Code != nil && b.ko.Spec.Code != nil {
507-
if ackcompare.HasNilDifference(a.ko.Spec.Code.ImageURI, b.ko.Spec.Code.ImageURI) {
508-
delta.Add("Spec.Code.ImageURI", a.ko.Spec.Code.ImageURI, b.ko.Spec.Code.ImageURI)
509-
} else if a.ko.Spec.Code.ImageURI != nil && b.ko.Spec.Code.ImageURI != nil {
510-
if *a.ko.Spec.Code.ImageURI != *b.ko.Spec.Code.ImageURI {
511-
delta.Add("Spec.Code.ImageURI", a.ko.Spec.Code.ImageURI, b.ko.Spec.Code.ImageURI)
512-
}
513-
}
514-
//TODO(hialylmh) handle Spec.Code.S3bucket changes
515-
// if ackcompare.HasNilDifference(a.ko.Spec.Code.S3Bucket, b.ko.Spec.Code.S3Bucket) {
516-
// delta.Add("Spec.Code.S3Bucket", a.ko.Spec.Code.S3Bucket, b.ko.Spec.Code.S3Bucket)
517-
// } else if a.ko.Spec.Code.S3Bucket != nil && b.ko.Spec.Code.S3Bucket != nil {
518-
// if *a.ko.Spec.Code.S3Bucket != *b.ko.Spec.Code.S3Bucket {
519-
// delta.Add("Spec.Code.S3Bucket", a.ko.Spec.Code.S3Bucket, b.ko.Spec.Code.S3Bucket)
520-
// }
521-
// }
522-
if ackcompare.HasNilDifference(a.ko.Spec.Code.S3Key, b.ko.Spec.Code.S3Key) {
523-
delta.Add("Spec.Code.S3Key", a.ko.Spec.Code.S3Key, b.ko.Spec.Code.S3Key)
524-
} else if a.ko.Spec.Code.S3Key != nil && b.ko.Spec.Code.S3Key != nil {
525-
if *a.ko.Spec.Code.S3Key != *b.ko.Spec.Code.S3Key {
526-
delta.Add("Spec.Code.S3Key", a.ko.Spec.Code.S3Key, b.ko.Spec.Code.S3Key)
527-
}
528-
}
529-
if ackcompare.HasNilDifference(a.ko.Spec.Code.S3ObjectVersion, b.ko.Spec.Code.S3ObjectVersion) {
530-
delta.Add("Spec.Code.S3ObjectVersion", a.ko.Spec.Code.S3ObjectVersion, b.ko.Spec.Code.S3ObjectVersion)
531-
} else if a.ko.Spec.Code.S3ObjectVersion != nil && b.ko.Spec.Code.S3ObjectVersion != nil {
532-
if *a.ko.Spec.Code.S3ObjectVersion != *b.ko.Spec.Code.S3ObjectVersion {
533-
delta.Add("Spec.Code.S3ObjectVersion", a.ko.Spec.Code.S3ObjectVersion, b.ko.Spec.Code.S3ObjectVersion)
478+
if a.ko.Spec.PackageType != nil && *a.ko.Spec.PackageType == "Zip" {
479+
if a.ko.Spec.Code.SHA256 != nil {
480+
if ackcompare.HasNilDifference(a.ko.Spec.Code.SHA256, b.ko.Status.CodeSHA256) {
481+
delta.Add("Spec.Code.SHA256", a.ko.Spec.Code.SHA256, b.ko.Status.CodeSHA256)
482+
} else if a.ko.Spec.Code.SHA256 != nil && b.ko.Status.CodeSHA256 != nil {
483+
if *a.ko.Spec.Code.SHA256 != *b.ko.Status.CodeSHA256 {
484+
delta.Add("Spec.Code.SHA256", a.ko.Spec.Code.SHA256, b.ko.Status.CodeSHA256)
485+
}
486+
}
534487
}
535488
}
536489
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: lambda.services.k8s.aws/v1alpha1
2+
kind: Function
3+
metadata:
4+
name: $FUNCTION_NAME
5+
annotations:
6+
services.k8s.aws/region: $AWS_REGION
7+
spec:
8+
name: $FUNCTION_NAME
9+
architectures: [$ARCHITECTURES]
10+
code:
11+
s3Bucket: $BUCKET_NAME
12+
s3Key: $LAMBDA_FILE_NAME
13+
sha256: $HASH
14+
role: $LAMBDA_ROLE
15+
runtime: python3.9
16+
handler: main
17+
description: function created by ACK lambda-controller e2e tests
18+
reservedConcurrentExecutions: $RESERVED_CONCURRENT_EXECUTIONS
19+
codeSigningConfigARN: "$CODE_SIGNING_CONFIG_ARN"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
if __name__ == "__main__":
2+
print("Updated Hello ACK!")

test/e2e/service_bootstrap.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@
4444
LAMBDA_FUNCTION_FILE_PATH = f"./resources/lambda_function/{LAMBDA_FUNCTION_FILE}"
4545
LAMBDA_FUNCTION_FILE_PATH_ZIP = f"./resources/lambda_function/{LAMBDA_FUNCTION_FILE_ZIP}"
4646

47+
LAMBDA_FUNCTION_UPDATED_FILE = "updated_main.py"
48+
LAMBDA_FUNCTION_UPDATED_FILE_ZIP = "updated_main.zip"
49+
LAMBDA_FUNCTION_UPDATED_FILE_PATH = f"./resources/lambda_function/{LAMBDA_FUNCTION_UPDATED_FILE}"
50+
LAMBDA_FUNCTION_UPDATED_FILE_PATH_ZIP = f"./resources/lambda_function/{LAMBDA_FUNCTION_UPDATED_FILE_ZIP}"
51+
4752
AWS_SIGNING_PLATFORM_ID = "AWSLambda-SHA384-ECDSA"
4853

4954
def zip_function_file(src: str, dst: str):
@@ -139,6 +144,12 @@ def service_bootstrap() -> Resources:
139144
LAMBDA_FUNCTION_FILE_PATH_ZIP,
140145
resources.FunctionsBucket.name,
141146
)
147+
148+
zip_function_file(LAMBDA_FUNCTION_UPDATED_FILE_PATH, LAMBDA_FUNCTION_UPDATED_FILE_PATH_ZIP)
149+
upload_function_to_bucket(
150+
LAMBDA_FUNCTION_UPDATED_FILE_PATH_ZIP,
151+
resources.FunctionsBucket.name,
152+
)
142153
except BootstrapFailureException as ex:
143154
exit(254)
144155
return resources

0 commit comments

Comments
 (0)