1
votes

I am attempting to create a CloudFormation template for an AWS lambda service and I'm running into a "chicken or the egg" scenario between the s3 bucket holding my lambda code, and the lambda function calling said bucket.

The intent is for our lambda code to be built to a jar, which will be hosted in an S3 Bucket, and our lambda function will reference that bucket. However when I run the template (using the CLI aws cloudformation create-stack --template-body "file://template.yaml"), I run into the following error creating the lambda function:

CREATE_FAILED Error occurred while GetObject. S3 Error Code: NoSuchKey. S3 Error Message: The specified key does not exist. (Service: AWSLambdaInternal; Status Code: 400; Error Code: InvalidParameterValueException; Request ID: ...; Proxy: null)

I believe this is happening because cloudformation is building both the bucket and lambda in the same transaction, and I can't stop it in the middle to push content into the brand new bucket.

I can't be the only one that has this problem, so I'm wondering if there's a common practice for tackling it? I'd like to keep all my configuration in a single template file if possible, but the only solutions I'm coming up with would require splitting the stack creation into multiple steps. (e.g. build the bucket first, deploy my code to it, then create the rest of the stack.) Is there a better way to do this?

template.yaml (the relevant bits)

...
  myS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "${AWS::StackName}"
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      AccessControl: Private
      PublicAccessBlockConfiguration:
          BlockPublicAcls: true
          BlockPublicPolicy: true
          IgnorePublicAcls: true
          RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled

 myLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub "${AWS::StackName}-dec"
      Handler: "lambda.Handler"
      Role: !GetAtt myLambdaExecutionRole.Arn
      Code:
        S3Bucket: !Ref myS3Bucket
        S3Key: "emy-lambda-fn.jar"
      Runtime: "java8"
      Timeout: 90
      MemorySize: 384
      Environment:
        Variables:
          stackName: !Sub "${AWS::StackName}"
...
1

1 Answers

0
votes

I'm coming up with would require splitting the stack creation into multiple steps. [...] Is there a better way to do this?

Splitting template into two is the most logical and easiest way of doing what you are trying to do.

There are some alternatives that would allow you to keep everything in one template, but they are more difficult to implement, manage and simply use. One alternative would be to develop a custom resources. The resource would be in the form of a lambda function that would get invoked after the bucket creation. The lambda would wait and check for existence of your emy-lambda-fn.jar in the bucket, and when the key is uploaded (within 15 min max), the function returns, and your stack creation continues. This means that your myLambdaFunction would be creating only after the custom resource returns, ensuring that emy-lambda-fn.jar exists.