Describe the bug
When a Lambda Function has no layers attached in AWS, adding layers to spec.layers
has no effect. The controller never computes a Spec.Layers delta, so
UpdateFunctionConfiguration is called without the Layers parameter and the layers are
never attached. No error is reported and the resource is marked ACK.ResourceSynced=True.
The root cause is in the generated sdkFind for Function. It starts from a deep copy of
the desired resource and only overwrites Spec.Layers when the AWS response contains
layers:
// pkg/resource/function/sdk.go
ko := r.ko.DeepCopy() // starts from DESIRED state
...
if resp.Configuration.Layers != nil {
...
ko.Spec.Layers = layer // only assigned when AWS already has layers
ko.Status.LayerStatuses = f16
} else {
ko.Status.LayerStatuses = nil // clears STATUS only; Spec.Layers is left as-is
}
When AWS returns no layers, the else branch clears only Status.LayerStatuses. The
latest resource therefore keeps the desired Spec.Layers value inherited from the
DeepCopy. newResourceDelta then compares desired against latest:
// pkg/resource/function/delta.go
if len(a.ko.Spec.Layers) != len(b.ko.Spec.Layers) {
delta.Add("Spec.Layers", a.ko.Spec.Layers, b.ko.Spec.Layers)
} else if len(a.ko.Spec.Layers) > 0 {
if !ackcompare.SliceStringPEqual(a.ko.Spec.Layers, b.ko.Spec.Layers) {
delta.Add("Spec.Layers", a.ko.Spec.Layers, b.ko.Spec.Layers)
}
}
Both sides hold the same value, so no delta is added. updateFunctionConfiguration guards
the field with delta.DifferentAt("Spec.Layers"), which is never true, so input.Layers
is never set.
The bug is self-sealing: it only manifests in the exact case where the layers are missing
from AWS and therefore need to be added. If the function already has a different layer,
resp.Configuration.Layers != nil holds, Spec.Layers is correctly overwritten from the
response, the delta fires and the update works as expected.
Status.LayerStatuses is correctly reported as empty throughout, which is a useful signal:
the CR shows spec.layers populated and status.layerStatuses empty at the same time.
Steps to reproduce
-
Create a Lambda function without any layers (e.g. with Terraform, CloudFormation, or
the console), so that GetFunction returns no Configuration.Layers.
-
Adopt it with the controller:
apiVersion: lambda.services.k8s.aws/v1alpha1
kind: Function
metadata:
name: my-function
annotations:
services.k8s.aws/adoption-policy: adopt
services.k8s.aws/adoption-fields: |
{"name": "my-function"}
spec:
name: my-function
# ...
- Once adopted, add a layer to the spec:
kubectl patch function my-function --type=merge \
-p '{"spec":{"layers":["arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:88"]}}'
- Observe the result:
# The CR has the layer in spec, but status is empty
kubectl get function my-function \
-o jsonpath='spec.layers={.spec.layers}{"\n"}status.layerStatuses={.status.layerStatuses}{"\n"}'
# spec.layers=["arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:88"]
# status.layerStatuses=
# The condition reports success
kubectl get function my-function -o jsonpath='{.status.conditions[?(@.type=="ACK.ResourceSynced")].status}'
# True
# AWS still has no layers
aws lambda get-function-configuration --function-name my-function --query 'Layers'
# null
The controller logs show no Spec.Layers entry in any "desired resource state has changed" diff, and no error. In our environment, across the full controller log buffer,
diffs were emitted for Spec.Handler, Spec.MemorySize, Spec.Timeout, Spec.Environment,
Spec.Tags and Spec.Code — and never once for Spec.Layers.
CloudTrail confirms the field is absent from the API call. Both UpdateFunctionConfiguration
events issued by the controller in the same reconcile window carried other fields but no
layers key in requestParameters:
UpdateFunctionConfiguration by <controller-role>
handler : src/handler.handler
layers : <absent from requestParameters>
UpdateFunctionConfiguration by <controller-role>
memorySize : 512
timeout : 30
layers : <absent from requestParameters>
Expected outcome
Adding ARNs to spec.layers should attach those layers to the function, regardless of
whether the function currently has layers in AWS. Concretely, sdkFind should set
ko.Spec.Layers = nil in the else branch — mirroring what it already does for
ko.Status.LayerStatuses — so that latest reflects the actual AWS state and the delta can
be computed correctly.
This appears to be the same class of defect as #1413 (Spec.Timeout omitted from
UpdateFunctionConfiguration), which was fixed in lambda-controller#41.
Environment
- Kubernetes version: v1.34.9
- Using EKS (yes/no), if so version? yes, EKS 1.34
- AWS service targeted (S3, RDS, etc.): Lambda
Additional details:
- lambda-controller version: v1.5.8 (
public.ecr.aws/aws-controllers-k8s/lambda-controller:1.5.8)
- Feature gates:
ResourceAdoption: true, ReadOnlyResources: true
- The affected code is byte-identical in
v1.5.8, v1.16.0 and main, so the bug is
present in the latest release. Diffing the if resp.Configuration.Layers != nil { ... } else { ... } block across those three revisions produces no differences.
- Reproduced on three separate adopted functions in the same cluster; all three show
spec.layers populated, status.layerStatuses empty and no layers in AWS.
Describe the bug
When a Lambda
Functionhas no layers attached in AWS, adding layers tospec.layershas no effect. The controller never computes a
Spec.Layersdelta, soUpdateFunctionConfigurationis called without theLayersparameter and the layers arenever attached. No error is reported and the resource is marked
ACK.ResourceSynced=True.The root cause is in the generated
sdkFindforFunction. It starts from a deep copy ofthe desired resource and only overwrites
Spec.Layerswhen the AWS response containslayers:
When AWS returns no layers, the
elsebranch clears onlyStatus.LayerStatuses. Thelatestresource therefore keeps the desiredSpec.Layersvalue inherited from theDeepCopy.newResourceDeltathen compares desired against latest:Both sides hold the same value, so no delta is added.
updateFunctionConfigurationguardsthe field with
delta.DifferentAt("Spec.Layers"), which is never true, soinput.Layersis never set.
The bug is self-sealing: it only manifests in the exact case where the layers are missing
from AWS and therefore need to be added. If the function already has a different layer,
resp.Configuration.Layers != nilholds,Spec.Layersis correctly overwritten from theresponse, the delta fires and the update works as expected.
Status.LayerStatusesis correctly reported as empty throughout, which is a useful signal:the CR shows
spec.layerspopulated andstatus.layerStatusesempty at the same time.Steps to reproduce
Create a Lambda function without any layers (e.g. with Terraform, CloudFormation, or
the console), so that
GetFunctionreturns noConfiguration.Layers.Adopt it with the controller:
The controller logs show no
Spec.Layersentry in any"desired resource state has changed"diff, and no error. In our environment, across the full controller log buffer,diffs were emitted for
Spec.Handler,Spec.MemorySize,Spec.Timeout,Spec.Environment,Spec.TagsandSpec.Code— and never once forSpec.Layers.CloudTrail confirms the field is absent from the API call. Both
UpdateFunctionConfigurationevents issued by the controller in the same reconcile window carried other fields but no
layerskey inrequestParameters:Expected outcome
Adding ARNs to
spec.layersshould attach those layers to the function, regardless ofwhether the function currently has layers in AWS. Concretely,
sdkFindshould setko.Spec.Layers = nilin theelsebranch — mirroring what it already does forko.Status.LayerStatuses— so thatlatestreflects the actual AWS state and the delta canbe computed correctly.
This appears to be the same class of defect as #1413 (
Spec.Timeoutomitted fromUpdateFunctionConfiguration), which was fixed in lambda-controller#41.Environment
Additional details:
public.ecr.aws/aws-controllers-k8s/lambda-controller:1.5.8)ResourceAdoption: true,ReadOnlyResources: truev1.5.8,v1.16.0andmain, so the bug ispresent in the latest release. Diffing the
if resp.Configuration.Layers != nil { ... } else { ... }block across those three revisions produces no differences.spec.layerspopulated,status.layerStatusesempty and no layers in AWS.