Cloudformation Custom Resource Orchestration/precedence? - amazon-web-services

I have a Cloudformation Custom Resource, that I want to use the outputs from, and call another Custom Resource.
I tried exporting the Outputs, and tinkered with using DependsOn, hoping it would set some order of precedence.
Is this Possible:
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: Run Lambda1, then run Lambda2 w/ outpu from Lambda1
Outputs:
lambda1Output:
Value:
Fn::GetAtt:
- lambda1
- test
Export:
Name: lambda1Outputs
Resources:
lambda1:
Type: Custom::test
Properties:
ServiceToken: arn:aws:lambda:us-east-1:761861444952:function:runOnce
lambda2:
Type: Custom::test2
DependsOn: lambda1
Properties:
ServiceToken: arn:aws:lambda:us-east-1:761861444952:function:runOnce
myParameter: !ImportValue lambda1Outputs

There's an easier way to achieve what you want.
Resources:
lambda1:
Type: Custom::test
Properties:
ServiceToken: arn:aws:lambda:us-east-1:761861444952:function:runOnce
lambda2:
Type: Custom::test2
Properties:
ServiceToken: arn:aws:lambda:us-east-1:761861444952:function:runOnce
myParameter: !GetAtt lambda1.test
So no need for the Outputs block or DependsOn. The CloudFormation dependency engine will understand that lambda1 needs to be executed before lambda2.

Related

How to reuse Layers on AWS both locally using SAM and when they're deployed?

So far I have this template.yml:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
My Lambda for doing something
Resources:
FirstLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: FirstLayer
Description: First layer of dependencies
ContentUri: layers/first-layer/
CompatibleRuntimes:
- nodejs14.x
Metadata:
BuildMethod: nodejs14.x
SecondLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: SecondLayer
Description: Second layer of dependencies
ContentUri: layers/second-layer/
CompatibleRuntimes:
- nodejs14.x
Metadata:
BuildMethod: nodejs14.x
MyLambda:
Type: AWS::Serverless::Function
Properties:
FunctionName: "MyLambda"
Policies:
- AmazonS3FullAccess
CodeUri: src/
Handler: lambda.handler
Timeout: 30
MemorySize: 2048 # Chrome will require higher memory
Runtime: nodejs14.x
Layers:
- !Ref FirstLayer
- !Ref SecondLayer
With this template I am able to start and invoke MyLambda locally and also deploy it to AWS. The problem I have is that I would like to reuse these same layers on other Lambdas as well, so for doing that I could simply extract these layers to another yml file, deploy them separately and then include the layers ARNs in the Layers property of my Lambda, but then, how can I run it locally with sam? I wouldn't like to have 2 template.yml files for my Lambda, one including the Layers on the Resources (like the one I already have) to run locally and another one with the refs to the actual layers ARNs to deploy on AWS, but that's the only solution I am seeing now.
The first question you need to ask is if those lambdas belong to the same application. If that´s not the case, you should use different templates, in order to deploy different stacks, to have isolated environments.
However, if you want to share resources, you have to very similar options:
Configure the layer in the parent template and pass the ARN as a parameter.
template.yml
Resources:
SharedLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: shared_layer
Description: Some code to share with the other lambda functions
ContentUri: ./layer
CompatibleRuntimes:
- nodejs14.x
RetentionPolicy: Delete
Application:
Type: "AWS::Serverless::Application"
Properties:
Location: "./app.template.yml"
Parameters:
SharedLayer: !Ref SharedLayer
app.template.yml
Parameters:
SharedLayer:
Type: String
Description: ARN of the SharedLayer
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./
Handler: index.handler
Layers:
- !Ref SharedLayer
Configure the layers in a nested template, set the ARN as an output, and then pass its output as a parameter to the other templates.
layers.template.yml
Resources:
SharedLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: shared_layer
Description: Some code to share with the other lambda functions
ContentUri: ./layer
CompatibleRuntimes:
- nodejs14.x
RetentionPolicy: Delete
Outputs:
SharedLayerARN:
Description: ARN of the Shared Layer
Value: !Ref SharedLayer
template.yml
Layer:
Type: "AWS::Serverless::Application"
Properties:
Location: "./layers.template.yml"
Application:
Type: "AWS::Serverless::Application"
Properties:
Location: "./app.template.yml"
Parameters:
SharedLayer: !GetAtt Layer.Outputs.SharedLayerARN
Both scenarios are supported by AWS SAM.

Importing s3 Bucket in Cloudformation template

In a Cloudformation template, I define two S3 Buckets.
Bucket1:
Type: AWS::S3::Bucket
Properties:
...
Bucket2:
Type: AWS::S3::Bucket
Properties:
...
Outputs:
Bucket1:
Description: S3 Bucket
Value: !Ref Bucket1
Export:
Name: !Sub "${AWS::StackName}:Bucket1"
Bucket2:
Description: S3 Bucket
Value: !Ref Bucket2
Export:
Name: !Sub "${AWS::StackName}:Bucket2"
I use these exported buckets in two different cloudformation templates.
Template 1
Parameters:
LoaderCodeBucket:
Type: String
Resources:
MyLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket:
Fn::ImportValue:
!Sub "${LoaderCodeBucket}:Bucket1"
Template 2
Parameters:
ProcessorCodeBucket:
Type: String
Resources:
MyOtherLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket:
Fn::ImportValue:
!Sub "${ProcessorCodeBucket}:Bucket2"
Template 1 passes aws cloudformation validate-template --template-body ... while Template 2 fails due to
Template error: the attribute in Fn::ImportValue must not depend on any resources, imported values, or Fn::GetAZs.
The only difference is the lambda function in template 2 is used in an aws analytics application that is also defined in template 2.
I know for sure it's the S3 Bucket causing issues because when I remove that section of code, it passes the validation check.
I have been using this site to try to debug this issue, but none of the questions seem to answer this particular issue.
This is in same region/same account.
My question is:
Why is this particular section of code (template 2) throwing a template error when template 1 passes with no error?
This is a working example.
Template 1:
AWSTemplateFormatVersion: "2010-09-09"
Description: "Test"
Resources:
MyBucketOne:
Type: "AWS::S3::Bucket"
Properties:
BucketName: bucket-one-12341234
MyBucketTwo:
Type: "AWS::S3::Bucket"
Properties:
BucketName: bucket-two-12341234
Outputs:
MyBucketOneOutput:
Description: "Bucket Name of BucketOne"
Value: !Ref MyBucketOne
Export:
Name: !Sub "${AWS::StackName}-BucketOne"
MyBucketTwoOutput:
Description: "Bucket Name of BucketTwo"
Value: !Ref MyBucketTwo
Export:
Name: !Sub "${AWS::StackName}-BucketTwo"
Template 2: we can import it as !ImportValue my-s3-BucketOne
AWSTemplateFormatVersion: "2010-09-09"
Description: "Test"
Resources:
MyLambda:
Type: "AWS::Lambda::Function"
Properties:
Handler: index.handler
Runtime: nodejs12.x
FunctionName: "test-s3-import"
Code:
S3Bucket: !ImportValue my-s3-BucketOne
S3Key: "index.zip"
Description: "Test Lambda"
MemorySize: 128
Timeout: 60
Role: test-role-arn
If you do want to use from Parameter, it will be Fn::ImportValue: !Sub ${BucketExportNamePrefix}-BucketOne
AWSTemplateFormatVersion: "2010-09-09"
Description: "Test"
Parameters:
BucketExportNamePrefix:
Type: String
Default: "my-s3"
Resources:
MyLambda:
Type: "AWS::Lambda::Function"
Properties:
Handler: index.handler
Runtime: nodejs12.x
FunctionName: "test-s3-import"
Code:
S3Bucket:
Fn::ImportValue: !Sub ${BucketExportNamePrefix}-BucketOne
S3Key: "index.zip"
Description: "Test Lambda"
MemorySize: 128
Timeout: 60
Role: test-role-arn

Cloudformation YAML custom variable

I am trying to achieve something similar to below in a AWS Cloudformation YAML file:
AWSTemplateFormatVersion: 2010-09-09
testAttribute = "test"
Resources:
Lambda:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.7
Role: !GetAtt iam.Arn
MemorySize: 128
Timeout: 10
Handler: lambda_function.lambda_handler
FunctionName: "testName"+${testAttribute}
Description: 'This is my lambda'
Code:
S3Bucket: myBucket
S3Key: "lambda/testName"+${testAttribute}+".zip"
I know that above isn't quite correct, but I cant find a good answer when searching how to achieve it. Anyone who have some guidance on this matter?
It depends on the use case but if the "variable" would be static and you don't need the change it when deploying the stack, I would suggest an alternative solution, to use the Mappings section.
This allows you to define some static values without sending them when deploying the stack (you will have much cleaner deploy commands, and the logic would be on the template side instead of the deploy side).
In this case, I'm using !Sub intrinsic function with a mapping (you can set multiple variables to be substituted using !Sub):
AWSTemplateFormatVersion: 2010-09-09
Mappings:
attributes:
lambda:
testAttribute: "test"
Resources:
Lambda:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.7
Role: !GetAtt iam.Arn
MemorySize: 128
Timeout: 10
Handler: lambda_function.lambda_handler
FunctionName: !Sub
- "testName${attr}"
- {attr: !FindInMap [attributes, lambda, testAttribute]}
Description: 'This is my lambda'
Code:
S3Bucket: myBucket
S3Key: !Sub
- "lambda/testName${attr}.zip"
- {attr: !FindInMap [attributes, lambda, testAttribute]}
Note: Mappings have a mandatory three-level nesting, take this into consideration while designing your solution
You could use Parameters with a default value, and Sub later in the template:
AWSTemplateFormatVersion: 2010-09-09
Parameters:
testAttribute:
Type: String
Default: test
Resources:
Lambda:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.7
Role: !GetAtt iam.Arn
MemorySize: 128
Timeout: 10
Handler: lambda_function.lambda_handler
FunctionName: !Sub "testName${testAttribute}"
Description: 'This is my lambda'
Code:
S3Bucket: myBucket
S3Key: !Sub "lambda/testName${testAttribute}.zip"
[Edited for typo]

how to wire up lambda and api gateway in cloud formation template?

I am learning working with AWS lambda and api gateway. I started with sam cli, initialized a hello world template ( code below). when deploy.yaml file is generated via - sam package ... command and stack is generated via cloud formation , it looks like this already generates an api end point. I wanted to create an api gateway with resource, methods, usage plan , api key and so forth. so I started adding following resources to the original template. How can i wire up my api gateway with the lambda function.
ApiGatewayRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
ApiKeySourceType: HEADER
Description: An API Gateway with a Lambda Integration
EndpointConfiguration:
Types:
- EDGE
Name: lambda-api
ApiGatewayResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt ApiGatewayRestApi.RootResourceId
PathPart: 'lambda'
RestApiId: !Ref ApiGatewayRestApi
ApiGatewayMethod:
Type: AWS::ApiGateway::Method
Properties:
ApiKeyRequired: false
AuthorizationType: NONE
HttpMethod: GET
ResourceId: !Ref ApiGatewayResource
RestApiId: !Ref ApiGatewayRestApi
original template
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
simple-node-api
Sample SAM Template for simple-node-api
Globals:
Function:
Timeout: 3
Resources:
HelloWorldfunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: python3.7
Events:
HelloWorld:
Type: Api
Properties:
Path: /{proxy+}
Method: get
Outputs:
HelloWorldApi:
Description: API Gateway endpoint URL for Prod stage for Hello World function
Value:
Fn::Sub: https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldfunction:
Description: Express Backend Lambda Function ARN
Value: !Sub HelloWorldfunction.Arn
HelloWorldFunctionIamRole:
Description: Implicit IAM Role created for Hello World function
Value: !Sub HelloWorldFunctionRole.Arn

Connect specific AWS API Gateway stage to specific Lambda alias in CloudFormation template

I create CloudFormation template for my AWS API Gateway and Lambda function and I need to connect specific API Gateway stage to specific Lambda alias. I have two aliases - QA and Prod, and two API stages (QA & Prod too), in CloudFormation template it looks like:
AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Lambda function configuration
Resources:
EndpointLambda:
Type: "AWS::Lambda::Function"
Properties:
FunctionName: "endpoint-lambda"
Handler: "com.test.aws.RequestHandler::handleRequest"
Runtime: java8
Code:
S3Bucket: "lambda-functions"
S3Key: "test-endpoint-lambda-0.0.1.jar"
Description: Test Lambda function
MemorySize: 256
Timeout: 60
Environment:
Variables:
ES_HOST: test-es-host-url
ES_ON: true
ES_PORT: 443
ES_PROTOCOL: https
REDIS_URL: test-redis-host-url
QaLambdaAlias:
Type: "AWS::Lambda::Alias"
Properties:
FunctionName: !Ref EndpointLambda
FunctionVersion: 1
Name: "QA"
Description: "QA alias"
ProdLambdaAlias:
Type: "AWS::Lambda::Alias"
Properties:
FunctionName: !Ref EndpointLambda
FunctionVersion: 1
Name: "Prod"
Description: "Production alias"
RestApi:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "test-rest-api"
Description: "Test REST API"
RestApiResource:
Type: "AWS::ApiGateway::Resource"
Properties:
RestApiId: !Ref "RestApi"
ParentId: !GetAtt "RestApi.RootResourceId"
PathPart: "/test"
RestApiDeployment:
Type: "AWS::ApiGateway::Deployment"
Properties:
RestApiId: !Ref "RestApi"
QaRestApiStage:
Type: "AWS::ApiGateway::Stage"
Properties:
DeploymentId: !Ref "RestApiDeployment"
RestApiId: !Ref "RestApi"
StageName: "qa"
ProdRestApiStage:
Type: "AWS::ApiGateway::Stage"
Properties:
DeploymentId: !Ref "RestApiDeployment"
RestApiId: !Ref "RestApi"
StageName: "prod"
How can I describe in template that QA API stage should call QA alias of Lambda function, and Prod stage - Prod alias?
To begin with find out how to do it using the GUI. There some documentation about what you want to do here. Theres some extra permissions you'll need to add aswell if this is the first time you've set this up which are included here -
https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html
But for a quick answer what your looking for is $:{stageVariables.stage} what this does is links the alias of the lambda you want to trigger. In the GUI it'd look something like this:
What this will do is allow your lambda to trigger a certain alias. Once this is entered you'll be able to see a new option when using the Testing feature in the API gateway. So here you'd specify QA.
So, to reflect this in Cloudformation we need to do something similar -
RestApi:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "test-rest-api"
Description: "Test REST API"
paths:
/ExamplePath:
put:
#Here will go all the configuration setting you want
#Such as security, httpMethod, amd passthroughBehavior
#But what you need is
uri: 'arn:aws:apigateway:${AWS:Region}:lambda:path/2-15/03/31/functions/${LambdaARN}:${!stageVariables.stage}/invocations'
More info on this can be found here:
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-method-integration.html what you'll want to see is at the very bottom of the page.
Hope this helps!