Error understanding Apigateway and nested stack on AWS cloudformation - amazon-web-services

I working into decouple a big template with lambdas that use apigateway AWS. I resolve multiple errors about nested stack process, but currently error its not clear. Could you check whats its the problem into definitions?
Main stack show general error from api substack create, but api-substack show next error:
Template error: instance of Fn::Sub references invalid resource attribute CreateCatalogFunctionDev.Arn
Next show a code of templates:
Main Template
SubStackAPIDev:
Type: 'AWS::CloudFormation::Stack'
Properties:
TemplateURL: https://s3.awsroute.com/substack-api.yaml
TimeoutInMinutes: 5
Parameters:
CreateCatalogFunctionDev: !GetAtt CreateCatalogFunctionDev.Outputs.CreateCatalogFunctionDev
SubStackCreateCatalogDev:
Type: 'AWS::CloudFormation::Stack'
Properties:
TemplateURL: https://s3.awsroute.com/substack-create-catalog.yaml
TimeoutInMinutes: 5
Parameters:
APIDev: !GetAtt SubStackAPIDev.Outputs.APIGateway
SubStackCreateCatalogDev
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
SAM template for create catalog
Parameters:
SecretsManagerName:
Type: String
Description: "Secrets Manager Name"
SnsTopicArn:
Type: String
Default: arn:aws:sns:us-west-2:XXXXXX:SNS-errorNotification
Description: "Sns topic error handler email notification"
APIDev:
Type: AWS::Serverless::Api
Resources:
LayerDev:
Type: 'AWS::Serverless::LayerVersion'
Properties:
ContentUri: ../../layer
CompatibleRuntimes:
- python3.6
- python3.7
- python3.8
RetentionPolicy: Delete
CreateCatalogFunctionDev:
Type: AWS::Serverless::Function
Properties:
Description: Recieve api request and and process data.
CodeUri: ../../src/catalog/create_catalog/
Handler: create_catalog.lambda_handler
Runtime: python3.8
FunctionName: CreateCatalogFunctionDev
Role: arn:aws:iam::XXXXXXXX:role/lambda-essential-role
Timeout: 480
Environment:
Variables:
CREATE_CATALOG_SECRET_NAME: !Sub '${SecretsManagerName}'
SNS_ARN: !Sub '${SnsTopicArn}'
Layers:
- arn:aws:lambda:us-west-2:XXXXXXX:layer:requests:1
- arn:aws:lambda:us-west-2:XXXXXXX:layer:requests-oauthlib:1
- !Ref LayerDev
Events:
CreateCatalogApiEvent:
Type: Api
Properties:
Path: /api-channel/products/catalog
Method: POST
RestApiId: !Ref APIDev
SubStack API
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
sub_channels_appi
SAM template for API
Parameters:
SwaggerFile:
Type: String
# TODO dejar URL de S3 de gitla-cicd
Default: "s3://cf-templates-1hurrmgzyzoz3-ap-northeast-1/swagger_dev.yaml"
Description: "This swagger file Amazon S3 path"
SecretsManagerName:
Type: String
Default: "/channels/Dev"
Description: "Secrets Manager Name"
StageName:
Type: String
Default: "${stageVariables.alias}"
Description: "This is the alias to the swagger file"
UsaSupplyApiUrlDev:
Type: String
Default: "https://thecornercloud.com/developers/index.php/"
Description: "Corner Cloud Staging"
SnsTopicArn:
Type: String
Default: arn:aws:sns:us-west-2:000365055762:channels-errorNotification
Description: "Sns topic error handler email notification"
CreateCatalogFunctionDev:
Type: String
Resources:
#######################################
# Serverless LayerVersion
#######################################
LayerDev:
Type: 'AWS::Serverless::LayerVersion'
Properties:
ContentUri: ../../layer
CompatibleRuntimes:
- python3.6
- python3.7
- python3.8
RetentionPolicy: Delete
#######################################
# Serverless API
#######################################
APIDev:
Type: AWS::Serverless::Api
Properties:
Auth:
ApiKeyRequired: true
StageName: dev
EndpointConfiguration: REGIONAL
DefinitionBody:
Fn::Transform:
Name: AWS::Include
Parameters:
Location: !Ref SwaggerFile
Variables:
alias: dev
#######################################
# ApiGateway ApiKey
#######################################
APIKeyDev:
Type: AWS::ApiGateway::ApiKey
Properties:
Name: "APIKeyDev"
Description: "API Key Dev"
Enabled: true
GenerateDistinctId: false
StageKeys:
- RestApiId: !Ref APIDev
StageName: !Ref APIDev.Stage
#######################################
# ApiGateway UsagePlan
#######################################
APIUsagePlanDev:
Type: AWS::ApiGateway::UsagePlan
DependsOn: APIDev
Properties:
ApiStages:
- ApiId: !Ref APIDev
Stage: !Ref APIDev.Stage
Quota:
Limit: 5000
Period: MONTH
Throttle:
BurstLimit: 200
RateLimit: 100
UsagePlanName: APIUsagePlanDev
#######################################
# ApiGateway UsagePlanKey
#######################################
APIUsagePlanKeyDev:
Type: AWS::ApiGateway::UsagePlanKey
Properties:
KeyId: !Ref APIKeyDev
KeyType: API_KEY
UsagePlanId: !Ref APIUsagePlanDev
#######################################
# ApiGateway Deployment
#######################################
DeploymentApiIdDev:
Type: AWS::ApiGateway::Deployment
Properties:
RestApiId: !Ref APIDev
Outputs:
APIGateway:
Description: "API Gateway Reference"
Value: !Ref APIDev
Export:
Name: !Join [":", [!Ref "AWS::StackName", "APIDev"]]
And finally the swagger file (honestly, I didn't define the api with this method and think that I want remove swagger if is possible).
swagger-dev
swagger: "2.0"
info:
version: "1.0.0"
title: "APIDev"
tags:
- name: "Channels"
description: "Manage Channels process."
schemes:
- "https"
x-amazon-apigateway-api-key-source: "HEADER"
securityDefinitions:
APIKey:
type: apiKey
name: X-Api-Key
in: header
paths:
/channels/products/catalog:
post:
tags:
- "Channels"
summary: " products catalog post."
operationId: "ProductsCatalogPostDev"
produces:
- "application/json"
responses:
201:
description: "Successful Operation"
400:
description: "Invalid parameters"
401:
description: "Unauthorized"
405:
description: "Validation exception"
security:
- APIKey: []
x-amazon-apigateway-integration:
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${CreateCatalogFunctionDev.Arn}/invocations
responses:
default:
statusCode: "200"
passthroughBehavior: "when_no_match"
httpMethod: "POST"
contentHandling: "CONVERT_TO_TEXT"
type: "aws_proxy"

Your CreateCatalogFunctionDev is in a different sub-stack then APIDev. You can't reference resources directly across stacks. You either have to export/import their outputs, or pass the references as input parameters.

Related

AWS: Why I am unable to assign a custom domain to the nested stack?

I am trying to integrate a custom domain to the HTTP API I am developing with AWS API Gateway and AWS Lambda. I m using the AWS SAM template. There I have a root stack and nested stacks.
For this question I will use a code piece with a one nested stack. There, this is how I want the URL end points to be
root stack - api.example.com
nested stack - api.example.com/nested
Below is my code
Root stack
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
aws-restapi
Sample SAM Template for aws-restapi
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 5
VpcConfig:
SecurityGroupIds:
- sg-041f24xxxx921e8e
SubnetIds:
- subnet-0xxxxx2d
Parameters:
FirebaseProjectId:
Type: String
#Dont create this domain in the AWS Console manually, so it will fail here
DomainName:
Type: String
Default: api.example.com
Resources:
AuthGatewayHttpApi:
Type: AWS::Serverless::HttpApi
Properties:
Domain:
DomainName: !Ref DomainName
EndpointConfiguration: REGIONAL
CertificateArn: arn:aws:acm:us-east-1:xxxx:certificate/xxxx-420d-xxxx-b40d-xxxx
Route53:
HostedZoneId: xxxxxxxxxxx
Auth:
Authorizers:
FirebaseAuthorizer:
IdentitySource: $request.header.Authorization
JwtConfiguration:
audience:
- !Ref FirebaseProjectId
issuer: !Sub https://securetoken.google.com/${FirebaseProjectId}
DefaultAuthorizer: FirebaseAuthorizer
AuthFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: aws-restapi/
Handler: source/testfile.lambdaHandler
Runtime: nodejs14.x
Events:
Gateway:
Type: HttpApi
Properties:
ApiId: !Ref AuthGatewayHttpApi
Path: /hello
Method: get
NestedStackTwo:
DependsOn: AuthGatewayHttpApi
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: nested_stack.yaml
Parameters:
FirebaseProjectId: !Ref FirebaseProjectId
DomainName: !Ref DomainName
Nested stack
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
aws-restapi
Sample SAM Template for aws-restapi
Globals:
Function:
Timeout: 5
VpcConfig:
SecurityGroupIds:
- sg-xxxxxxxxxxxx
SubnetIds:
- subnet-xxxxxxxxxxx
Parameters:
FirebaseProjectId:
Type: String
DomainName:
Type: String
Resources:
AuthGatewayHttpApi2:
Type: AWS::Serverless::HttpApi
Properties:
Domain:
DomainName: !Ref DomainName
BasePath: two
EndpointConfiguration: REGIONAL
CertificateArn: arn:aws:acm:us-east-1:xxxxx:certificate/xxxxx-xxxx-xxxx-xxxx-xxxx
Route53:
HostedZoneId: xxxxxxxxxxxxx
Auth:
Authorizers:
FirebaseAuthorizer:
IdentitySource: $request.header.Authorization
JwtConfiguration:
audience:
- !Ref FirebaseProjectId
issuer: !Sub https://securetoken.google.com/${FirebaseProjectId}
DefaultAuthorizer: FirebaseAuthorizer
GetAllPromotionsFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: aws-restapi/
Handler: source/promotions/promotions-getall.getAllPromotions
Runtime: nodejs14.x
Events:
GetAllPromotionsAPIEvent:
Type: HttpApi # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /promotions/getall
Method: get
ApiId: !Ref AuthGatewayHttpApi2
However I cant get this to work because the nested stack fails to create. Below is the error
CREATE_FAILED AWS::CloudFormation::Stack NestedStackTwo
Embedded stack arn:aws:cloudformation:us-east-1 xxxxx:stack/aws-restapi-NestedStackTwo-8KBISZRAVYBX/a3fcc010-0ce4-11ec-9c90-0e8a861a6983 was not successfully created:
The following resource(s) failed to create: [ApiGatewayDomainNameV234ac706a57]
I believe this is happening because the root stack creates the domain and the nested stack is trying to re-create it instead of reusing the same.
But, here is the fun fact; if i use the AWS API GATEWAY web console, I can do this in no time.
How can I get this to work in aws-sam ?
UPDATE
Following the advice from the user LRutten, I came up with the following code for the nested stack.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
aws-restapi
Sample SAM Template for aws-restapi
Globals:
Function:
Timeout: 5
VpcConfig:
SecurityGroupIds:
- sg-xxxxxx
SubnetIds:
- subnet-xxxxx
Parameters:
FirebaseProjectId:
Type: String
DomainName:
Type: String
Resources:
AuthGatewayHttpApi2:
Type: AWS::Serverless::HttpApi
Properties:
Auth:
Authorizers:
FirebaseAuthorizer:
IdentitySource: $request.header.Authorization
JwtConfiguration:
audience:
- !Ref FirebaseProjectId
issuer: !Sub https://securetoken.google.com/${FirebaseProjectId}
DefaultAuthorizer: FirebaseAuthorizer
MyApiMapping:
Type: 'AWS::ApiGatewayV2::ApiMapping'
Properties:
DomainName: !Ref DomainName
ApiId: !Ref AuthGatewayHttpApi2
Stage: prod
MyDomainName:
Type: 'AWS::ApiGatewayV2::DomainName'
Properties:
DomainName: !Ref DomainName
DomainNameConfigurations:
- EndpointType: REGIONAL
CertificateArn: arn:aws:acm:us-east-1:716460586643:certificate/bac44716-420d-431b-b40d-01378f20432d
GetAllPromotionsFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: aws-restapi/
Handler: source/promotions/promotions-getall.getAllPromotions
Runtime: nodejs14.x
Events:
GetAllPromotionsAPIEvent:
Type: HttpApi # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /promotions/getall
Method: get
ApiId: !Ref AuthGatewayHttpApi2
This ended up with the following error
Embedded stack arn:aws:cloudformation:us-east-1:716460586643:stack/aws-restapi-NestedStackTwo-14SAYLRO1WD1D/62336020-xx-xx-a04e-x was not successfully created:
The following resource(s) failed to create: [MyDomainName, MyApiMapping].
Indeed SAM always creates the domain when you specify it's name there. It stated in the docs as well.
To get around this, you can omit the whole domain configuration in the AWS::Serverless::HttpApi resource and write the resources created by SAM yourself. So add a section with
Type: AWS::ApiGatewayV2::ApiMapping
Properties:
ApiId: String
ApiMappingKey: String
DomainName: String
Stage: String
Docs
Make sure the Stage property in the ApiMapping is also present in the StageName property of the API.
And a
Type: AWS::ApiGatewayV2::DomainName
Properties:
DomainName: String
DomainNameConfigurations:
- DomainNameConfiguration
MutualTlsAuthentication:
MutualTlsAuthentication
Tags: Json
Docs
Manually adding these without having the domain itself defined twice should do the trick.
Edit: woops wasn't really thinking straight. You should of course only have the mapping, not the domain name itself again :P.

How to attach authorization/api key to the sam cli generated api?

I used sam cli , to create a project. when I package this and deploy , it creates the lambda and also the api gateway with stage and prod stages, policy, roles e.t.c by default, without having to explicitly define in the cloudformation template ( see code below) . as it generates the api gateway automatically, how do i add/attach say if i wanted to add a api key or some kind of authorization for my api generated by template below?
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:
ServerlessHttpApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Auth:
ApiKeyRequired: true # sets for all methods
DefinitionBody:
swagger:2.0
paths:
"/myresource":
post:
x-amazon-apigateway-integration
httpMethod: post
type: aws_proxy
uri: ...
ApiKey:
Type: AWS::ApiGateway::ApiKey
Properties:
Name: !Join ["", [{"Ref": "AWS::StackName"}, "-apikey"]]
Description: "CloudFormation API Key V1"
Enabled: true
GenerateDistinctId: false
Value: abcdefg123456
StageKeys:
- RestApiId: !Ref ServerlessHttpApi
StageName: Prod
ApiUsagePlan:
Type: "AWS::ApiGateway::UsagePlan"
Properties:
ApiStages:
- ApiId: !Ref ServerlessHttpApi
Stage: Prod
Description: !Join [" ", [{"Ref": "AWS::StackName"}, "usage plan"]]
Quota:
Limit: 1000
Period: MONTH
UsagePlanName: !Join ["", [{"Ref": "AWS::StackName"}, "-usage-plan"]]
ApiUsagePlanKey:
Type: "AWS::ApiGateway::UsagePlanKey"
DependsOn:
- ServerlessHttpApi
Properties:
KeyId: !Ref ApiKey
KeyType: API_KEY
UsagePlanId: !Ref ApiUsagePlan
HelloWorldfunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: python3.7
Events:
HelloWorld:
Type: Api
Properties:
RestApiId: !Ref ServerlessHttpApi
Path: /hello
Method: get
Outputs:
ServerlessHttpApi:
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
I modified your code to use the API keys as shown here.
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:
ServerlessHttpApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Auth:
ApiKeyRequired: true # sets for all methods
ApiKey:
Type: AWS::ApiGateway::ApiKey
DependsOn: [ApiUsagePlan]
Properties:
Name: !Join ["", [{"Ref": "AWS::StackName"}, "-apikey"]]
Description: "CloudFormation API Key V1"
Enabled: true
GenerateDistinctId: false
Value: abcdefg123456665ffghsdghfgdhfgdh4565
StageKeys:
- RestApiId: !Ref ServerlessHttpApi
StageName: Prod
ApiUsagePlan:
Type: "AWS::ApiGateway::UsagePlan"
DependsOn:
- ServerlessHttpApiProdStage
Properties:
ApiStages:
- ApiId: !Ref ServerlessHttpApi
Stage: Prod
Description: !Join [" ", [{"Ref": "AWS::StackName"}, "usage plan"]]
Quota:
Limit: 1000
Period: MONTH
UsagePlanName: !Join ["", [{"Ref": "AWS::StackName"}, "-usage-plan"]]
ApiUsagePlanKey:
Type: "AWS::ApiGateway::UsagePlanKey"
DependsOn:
- ServerlessHttpApi
Properties:
KeyId: !Ref ApiKey
KeyType: API_KEY
UsagePlanId: !Ref ApiUsagePlan
HelloWorldfunction:
Type: AWS::Serverless::Function
Properties:
#CodeUri: hello-world/
CodeUri: ./
Handler: app.lambdaHandler
Runtime: python3.7
Events:
HelloWorld:
Type: Api
Properties:
RestApiId: !Ref ServerlessHttpApi
Path: /hello
Method: get
Outputs:
ServerlessHttpApi:
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
I commented out some parts so that I can run the code, and I can confirm that it deploys and the API auth is set and API key present:
You have to mention it in your AWS SAM template. Below is an example:
Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Auth:
ApiKeyRequired: true # sets for all methods
MyFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Handler: index.handler
Runtime: nodejs12.x
Events:
ApiKey:
Type: Api
Properties:
RestApiId: !Ref MyApi
Path: /
Method: get
Auth:
ApiKeyRequired: true
You can read more about it here

AWS CloudFormation stack: API Gateway resource with nested paths?

I have an API Gateway resource manually built that looks like:
GET
/assets/{items} - (points to S3 bucket)
/{proxy+} - points to Lambda function
I would like to mimic this setup in a Cloudformation YAML template but unsure how to do so. Here is the current template I'm working with (partially reduced for brevity):
AWSTemplateFormatVersion: 2010-09-09
Parameters:
apiGatewayStageName:
Type: String
AllowedPattern: '^[a-zA-Z0-9]+[a-zA-Z0-9-]+[a-zA-Z0-9]+$'
Default: call
lambdaFunctionName:
Type: String
AllowedPattern: '^[a-zA-Z0-9]+[a-zA-Z0-9-]+[a-zA-Z0-9]+$'
Default: my-function
s3BucketName:
Type: String
AllowedPattern: '^[a-zA-Z0-9]+[a-zA-Z0-9-]+[a-zA-Z0-9]+$'
Resources:
apiGateway:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Name: my-api
Description: My API
Metadata:
...
apiGatewayRootMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
AuthorizationType: NONE
HttpMethod: POST
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri: !Sub
- >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !GetAtt lambdaFunction.Arn
ResourceId: !GetAtt apiGateway.RootResourceId
RestApiId: !Ref apiGateway
Metadata:
...
apiGatewayDeployment:
Type: 'AWS::ApiGateway::Deployment'
DependsOn:
- apiGatewayRootMethod
- apiGatewayGETMethod
Properties:
RestApiId: !Ref apiGateway
StageName: !Ref apiGatewayStageName
Metadata:
...
lambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
...
lambdaApiGatewayInvoke:
...
lambdaIAMRole:
...
lambdaLogGroup:
...
apiGatewayGETMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri: !Sub
- >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !GetAtt lambdaFunction.Arn
ResourceId: !GetAtt apiGateway.RootResourceId
RestApiId: !Ref apiGateway
Metadata:
'AWS::CloudFormation::Designer':
id: 1a329c4d-9d18-499e-b852-0e361af324f4
s3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Ref s3BucketName
Metadata:
...
Outputs:
apiGatewayInvokeURL:
Value: !Sub >-
https://${apiGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGatewayStageName}
lambdaArn:
Value: !GetAtt lambdaFunction.Arn
That is the result of lots of tweaking and me not having any prior CloudFormation knowledge besides going over the official docs. When the stack behind that template gets created, its API Gateway resource looks like:
The POST action is unnecessary, and only there from trial and error. The GET resource is the only important one as application returned by the Lambda function isn't doing any post requests yet.
The GET one must be created from this portion of the Stack:
apiGatewayGETMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri: !Sub
- >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !GetAtt lambdaFunction.Arn
ResourceId: !GetAtt apiGateway.RootResourceId
RestApiId: !Ref apiGateway
What must be done to make it so that GET resource has the nested /assets/{items} path pointing to an S3 bucket and the {proxy+} path pointing to a Lambda? Do I need to specify separate sibling resources for those paths like apiGatewayAssets and apiGatewayLambdaProxy then connect them to apiGatewayGETMethod somehow?
2020-05-17 Update
The current part tripping me up is this resource:
apiGatewayAssetsItemsResourceMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
ResourceId: !Ref apiGatewayAssetsItemsResource
RestApiId: !Ref apiGateway
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS
Credentials: arn:aws:iam::XXXXXX:role/AnExistingRole
IntegrationHttpMethod: GET
PassthroughBehavior: WHEN_NO_MATCH
RequestParameters:
integration.request.path.item: 'method.request.path.item'
method.request.path.item: true
Uri: !Sub >-
arn:aws:apigateway:${AWS::Region}:s3:path/${s3BucketName}/{item}
That leads to a CloudFormation stack creation error with the status reason being Invalid mapping expression specified: Validation Result: warnings : [], errors : [Invalid mapping expression parameter specified: method.request.path.item] (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException; Request ID: XXXXXX)
However, if I attempt to create it with the exact same resource minus the RequestParameters entry, it gets created successfully. Although when viewing that API Gateway GET method in the console, it's missing the Paths: item line inside the Integration Request box. Full template I'm currently using:
AWSTemplateFormatVersion: 2010-09-09
Parameters:
apiGatewayStageName:
Type: String
AllowedPattern: '^[a-zA-Z0-9]+[a-zA-Z0-9-]+[a-zA-Z0-9]+$'
Default: call
lambdaFunctionName:
Type: String
AllowedPattern: '^[a-zA-Z0-9]+[a-zA-Z0-9-]+[a-zA-Z0-9]+$'
Default: my-function
s3BucketName:
Type: String
AllowedPattern: '^[a-zA-Z0-9]+[a-zA-Z0-9-]+[a-zA-Z0-9]+$'
Resources:
apiGateway:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Name: my-api
Description: My API
apiGatewayDeployment:
Type: 'AWS::ApiGateway::Deployment'
DependsOn:
- apiGatewayGETMethod
Properties:
RestApiId: !Ref apiGateway
StageName: !Ref apiGatewayStageName
lambdaFunction:
...
lambdaApiGatewayInvoke:
...
lambdaIAMRole:
...
lambdaLogGroup:
...
apiGatewayGETMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri: !Sub
- >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !GetAtt lambdaFunction.Arn
ResourceId: !GetAtt apiGateway.RootResourceId
RestApiId: !Ref apiGateway
s3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Ref s3BucketName
BucketPolicy:
...
apiGatewayAssetsResource:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref apiGateway
ParentId: !GetAtt
- apiGateway
- RootResourceId
PathPart: assets
apiGatewayAssetsItemsResource:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref apiGateway
PathPart: '{item}'
ParentId: !Ref apiGatewayAssetsResource
apiGatewayAssetsItemsResourceMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
ResourceId: !Ref apiGatewayAssetsItemsResource
RestApiId: !Ref apiGateway
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS
Credentials: arn:aws:iam::XXXXXX:role/AnExistingRole
IntegrationHttpMethod: GET
PassthroughBehavior: WHEN_NO_MATCH
Uri: !Sub >-
arn:aws:apigateway:${AWS::Region}:s3:path/${s3BucketName}/{item}
apiGatewayLambdaResource:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref apiGateway
PathPart: '{proxy+}'
ParentId: !GetAtt
- apiGateway
- RootResourceId
apiGatewayLambdaResourceMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
AuthorizationType: NONE
RestApiId: !Ref apiGateway
ResourceId: !Ref apiGatewayLambdaResource
HttpMethod: ANY
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: GET
Uri: !Sub
- >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !GetAtt lambdaFunction.Arn
Outputs:
apiGatewayInvokeURL:
Value: !Sub >-
https://${apiGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGatewayStageName}
lambdaArn:
Value: !GetAtt lambdaFunction.Arn
So what you need to do for this is the following:
Create a AWS::ApiGateway::Resource with a PathPart of assets, this will use a ParentId of the RootResourceId attr from your Rest API
Create a AWS::ApiGateway::Resource with a PathPart of {item}, this will use a ParentId of the assets Resource above.
Create a AWS::ApiGateway::Method for the ResourceId of the resource above. This will use the HTTP_PROXY and set the Uri to be the S3 bucket path, making sure to include the {{ item }} variable in the path.
Create a AWS::ApiGateway::Resource with a PathPart of {proxy+}, this will use a ParentId of the RootResourceId attr from your Rest API
Create a AWS::ApiGateway::Method for the ResourceId of the resource above. This will use the AWS_PROXY and set the uri to reference the Lambda function.
Hope this helps

Why does CloudFormation API Gateway fail stating 'The REST API doesn't contain any methods' even with DependsOn?

I have a CloudFormation template which I'm trying to deploy. This template is very similar to another template I have which works without issue however when deploying this template it keeps failing stating:
APIGatewayDeployment5332c373d4 CREATE_FAILED The REST API doesn't contain any methods (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException; Request ID: 02c0352f-b23b-44d5-a0af-2f3f3ecd7273)
I had this issue previously on my other template and added the DependsOn property on the deployment which fixed the issue however even with this property this template is failing.
See cut down version of my template below:
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Environment:
Variables:
FOO: bah
Api:
EndpointConfiguration: REGIONAL
Resources:
APIGateway:
Type: AWS::Serverless::Api
Properties:
Name: Some API
StageName: !Sub ${EnvironmentTagName}
ApiFooResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt APIGateway.RootResourceId
PathPart: foo
RestApiId: !Ref APIGateway
ApiFooNotificationResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !Ref ApiFooResource
PathPart: notification
RestApiId: !Ref APIGateway
ApiFooNotificationMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: POST
AuthorizationType: NONE
ResourceId: !Ref ApiFooNotificationResource
RestApiId: !Ref APIGateway
MethodResponses:
- StatusCode: '200'
Integration:
Credentials: !GetAtt SQSRole.Arn
IntegrationHttpMethod: POST
IntegrationResponses:
- StatusCode: 200
Type: AWS
Uri: !Sub arn:aws:apigateway:${AWS::Region}:sqs:action/SendMessage
RequestParameters:
integration.request.querystring.QueueUrl: !Sub '''${FooNotificationQueue}'''
integration.request.querystring.MessageBody: method.request.body
ApiTestResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt APIGateway.RootResourceId
PathPart: test
RestApiId: !Ref APIGateway
ApiTestMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: POST
AuthorizationType: NONE
ResourceId: !Ref ApiTestResource
RestApiId: !Ref APIGateway
MethodResponses:
- StatusCode: '200'
Integration:
Credentials: !GetAtt SQSRole.Arn
IntegrationHttpMethod: POST
IntegrationResponses:
- StatusCode: 200
Type: AWS
Uri: !Sub arn:aws:apigateway:${AWS::Region}:sqs:action/SendMessage
RequestParameters:
integration.request.querystring.QueueUrl: !Sub '''${TestQueue}'''
integration.request.querystring.MessageBody: method.request.body
ApiDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn: ApiTestMethod
Properties:
RestApiId: !Ref APIGateway
Description: !Sub Api Deployment for ${EnvironmentTagName}
StageName: !Sub ${EnvironmentTagName}
...**Removed the rest for brevity**...
Parameters:
StackTagName:
Type: String
Description: Stack Name (injected by Stackery at deployment time)
EnvironmentTagName:
Type: String
Description: Environment Name (injected by Stackery at deployment time)

AWS Cloudformation Link API Key to API Gateway

I have the following Cloudformation template I am trying to deploy via SAM. This template correctly creates the DynamoDB table, an API Key, a Lambda function and the API Gateway, but I cannot figure out what I need to specify in the template to associate the API KEY with the API Gateway.
I have found plenty of snippets showing partial examples, but I am struggling to piece it all together.
Thank you in advance,
Denny
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Parameters:
TableName:
Type: String
Default: 'influencetabletest'
Description: (Required) The name of the new DynamoDB table Minimum 3 characters
MinLength: 3
MaxLength: 50
AllowedPattern: ^[A-Za-z-]+$
ConstraintDescription: 'Required parameter. Must be characters only. No numbers allowed.'
CorsOrigin:
Type: String
Default: '*'
Description: (Optional) Cross-origin resource sharing (CORS) Origin. You can specify a single origin, all "*" or leave empty and no CORS will be applied.
MaxLength: 250
Conditions:
IsCorsDefined: !Not [!Equals [!Ref CorsOrigin, '']]
Resources:
ApiKey:
Type: AWS::ApiGateway::ApiKey
DependsOn:
- ApiGetter
Properties:
Name: "TestApiKey"
Description: "CloudFormation API Key V1"
Enabled: "true"
ApiGetter:
Type: AWS::Serverless::Api
Properties:
StageName: prd
DefinitionBody:
swagger: 2.0
info:
title:
Ref: AWS::StackName
paths:
/getdynamicprice:
post:
responses: {}
x-amazon-apigateway-integration:
httpMethod: POST
type: aws_proxy
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaGetter.Arn}/invocations
LambdaGetter:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./index.js
Handler: index.handler
Runtime: nodejs8.10
Environment:
Variables:
TABLE_NAME: !Ref TableName
IS_CORS: IsCorsDefined
CORS_ORIGIN: !Ref CorsOrigin
PRIMARY_KEY: !Sub ${TableName}Id
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref TableName
Events:
Api:
Type: Api
Properties:
Path: /getdynamicprice
Method: POST
RestApiId: !Ref ApiGetter
DynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Ref TableName
AttributeDefinitions:
-
AttributeName: !Sub "${TableName}Id"
AttributeType: "S"
KeySchema:
-
AttributeName: !Sub "${TableName}Id"
KeyType: "HASH"
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
StreamSpecification:
StreamViewType: NEW_AND_OLD_IMAGES
Outputs:
ApiKeyID:
Value: !Ref ApiKey
ApiUrl:
Value: !Sub https://${ApiGetter}.execute-api.${AWS::Region}.amazonaws.com/prod/getdynamicprice
Description: The URL of the API Gateway you invoke to get your dynamic pricing result.
DynamoDBTableArn:
Value: !GetAtt DynamoDBTable.Arn
Description: The ARN of your DynamoDB Table
DynamoDBTableStreamArn:
Value: !GetAtt DynamoDBTable.StreamArn
Description: The ARN of your DynamoDB Table Stream
Edit (04/22/2020): there now seems to do all this using AWS SAM. Please see answer below
Here's a sample template where I have connected my API to a API key. But that's only been possible because I am using usage plans. I believe that is the primary purpose of an API key. API gateway usage plan
ApiKey:
Type: AWS::ApiGateway::ApiKey
Properties:
Name: !Join ["", [{"Ref": "AWS::StackName"}, "-apikey"]]
Description: "CloudFormation API Key V1"
Enabled: true
GenerateDistinctId: false
ApiUsagePlan:
Type: "AWS::ApiGateway::UsagePlan"
Properties:
ApiStages:
- ApiId: !Ref <API resource name>
Stage: !Ref <stage resource name>
Description: !Join [" ", [{"Ref": "AWS::StackName"}, "usage plan"]]
Quota:
Limit: 2000
Period: MONTH
Throttle:
BurstLimit: 10
RateLimit: 10
UsagePlanName: !Join ["", [{"Ref": "AWS::StackName"}, "-usage-plan"]]
ApiUsagePlanKey:
Type: "AWS::ApiGateway::UsagePlanKey"
Properties:
KeyId: !Ref <API key>
KeyType: API_KEY
UsagePlanId: !Ref ApiUsagePlan
There does not seem to be a way to do this without a usage plan.
I did try the suggestion from ASR but ended up with a simpler approach.
The AWS SAM (Serverless Application Model) contains prepackaged handling that doesn't necessitate the use of resources of the ApiGateway type.
To create an API Gateway with a stage that requires an authorization token in the header the following simplified code should do it for you :
Resources:
ApiGatewayEndpoint:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Auth:
ApiKeyRequired: true
UsagePlan:
CreateUsagePlan: PER_API
UsagePlanName: GatewayAuthorization [any name you see fit]
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
Handler: lambda.handler
Runtime: python3.7
Timeout: 30
CodeUri: .
Events:
PostEvent:
Type: Api
Properties:
Path: /content
Method: POST
RequestParameters:
- method.request.header.Authorization:
Required: true
Caching: true
RestApiId:
Ref: ApiGatewayEndpoint [The logical name of your gateway endpoint above]
The elements:
Auth:
ApiKeyRequired: true
UsagePlan:
CreateUsagePlan: PER_API
is what does the trick.
Cloudformation handles the plumbing for you, ie. the Api Key, UsagePlan and UsagePlanKey is automatically created and binded.
Although the docs are definitely not best in class they do provide some additional information: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-resources-and-properties.html