Cloudformation DynamoDB-API Gateway Proxy - amazon-web-services

I was trying to implement a dynamodb proxy using apigateway. But when invoking it, the api is returning error,
Fri Mar 19 20:30:27 UTC 2021 : Execution failed due to configuration
error: Unable to transform request
Fri Mar 19 20:30:27 UTC 2021 : Method completed with status: 500
To me it looks like the issue is not with the requestTemplates transformation(?), but what else, any idea?
Cloudformation template.
AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Feedback Store; A hypothetical example to test direct proxy to Dynamodb table from api gateway.
Metadata:
cfn-lint:
config:
ignore_checks:
# Not useful at all.
- I1022
Parameters:
Environment:
Type: String
Description: Current environment
Default: "dev"
Resources:
EventsTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: feedback_events
AttributeDefinitions:
- AttributeName: UserID
AttributeType: S
- AttributeName: TimeUTC
AttributeType: S
KeySchema:
- AttributeName: UserID
KeyType: HASH
- AttributeName: TimeUTC
KeyType: RANGE
BillingMode: PAY_PER_REQUEST
Tags:
- Key: "Feedbacks"
Value: "true"
ApiGatewayDynamoRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "apigateway.amazonaws.com"
Action:
- "sts:AssumeRole"
Policies:
- PolicyName: "ApiGatewayDynamoRolePolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "dynamodb:DescribeTable"
- "dynamodb:BatchWriteItem"
- "dynamodb:PutItem"
- "dynamodb:Query"
- "dynamodb:UpdateItem"
Resource:
- !GetAtt EventsTable.Arn
- Effect: "Allow"
Action:
- "dynamodb:ListTables"
Resource: "*"
ApiGatewayApi:
Type: AWS::Serverless::Api
Properties:
DefinitionBody:
swagger: 2.0
info:
title: !Sub "${AWS::StackName}-backend-api"
version: 2021-03-21T22:37:24Z
basePath: /prod
schemes:
- https
paths:
/feedback:
post:
summary: Creates a new feedback
description: |
Creates a new feedback object in the datastore
consumes:
- application/json
produces:
- application/json
parameters:
- name: NewFeedback
in: body
description: New feedback details.
schema:
$ref: "#/definitions/Feedbacks"
tags:
- Feedback Store
x-amazon-apigateway-integration:
type: aws
uri: !Sub arn:aws:apigateway:${AWS::Region}:dynamodb:action/BatchWriteItem
credentials: !GetAtt ApiGatewayDynamoRole.Arn
httpMethod: POST
requestTemplates:
application/json: |
#set($inputRoot = $input.path('$'))
{
"RequestItems": {
"${self:resources.Resources.EventsTable.Properties.TableName}": [
#foreach($event in $inputRoot.event)
{
"PutRequest": {
"Item": {
"UserID" : { "S": "$event.userid" },
"TimeUTC" : { "S": "$event.time" },
"Lat": { "S": "$event.lat" },
"Lng": { "S": "$event.lng" },
"UUID" : { "S": "$event.uuid"},
"Sensor" : { "S": "$event.sensor_name" },
"Reading" : { "S": "$event.reading_value" },
"Active" : { "S": "$event.is_active" },
}
}
}#if($foreach.hasNext),#end
#end
]
},
"ReturnValues": "UPDATED_NEW",
"ReturnConsumedCapacity": "NONE",
"ReturnItemCollectionMetrics": "NONE"
}
responses:
"default":
statusCode: "200"
SelectionPattern: "2\\d{2}"
responseParameters:
method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST'"
method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Origin: "'*'"
"BAD.*":
statusCode: "400"
SelectionPattern: "4\\d{2}"
responseParameters:
method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST'"
method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Origin: "'*'"
"INT.*":
statusCode: "500"
SelectionPattern: "5\\d{2}"
responseParameters:
method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST'"
method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Origin: "'*'"
responses:
"200":
description: The unique identifier of the new feedback
headers:
Access-Control-Allow-Origin:
type: "string"
Access-Control-Allow-Methods:
type: string
Access-Control-Allow-Headers:
type: string
schema:
$ref: "#/definitions/NewFeedbackResponse"
"400":
description: Bad request
headers:
Access-Control-Allow-Origin:
type: "string"
Access-Control-Allow-Methods:
type: string
Access-Control-Allow-Headers:
type: string
schema:
$ref: "#/definitions/Error"
"500":
description: Internal error
headers:
Access-Control-Allow-Origin:
type: "string"
Access-Control-Allow-Methods:
type: string
Access-Control-Allow-Headers:
type: string
schema:
$ref: "#/definitions/Error"
definitions:
Empty:
type: object
title: Empty Schema
Feedbacks:
type: array
items:
$ref: FeedbackItem
FeedbackItem:
properties:
userid:
type: string
description: UserID of the author
time:
type: string
description: Feedback time
lat:
type: string
description: Latitude
lng:
type: string
description: Longitude
uuid:
type: string
description: Globaly unique id for the feedback
sensor_name:
type: string
description: Device the feedback coming from
reading_value:
type: string
description: Current reading
is_active:
type: string
description: Device status
NewFeedbackResponse:
properties:
feedbackId:
type: string
description: The generated unique identifier for the new feedback
Error:
properties:
code:
type: integer
format: int32
message:
type: string
fields:
type: string
StageName: prod
Variables:
StageVariableName: "TestAPI"
Payload:
{
"event": [
{
"userid": "21d6523137f6",
"time": "2020-06-16T15:22:33Z",
"lng": "-122.03053391",
"lat": "37.33180957",
"uuid": "96a6f48c-fe67-4cad-be24-21d6523137f6",
"sensor_name": "CYT523",
"reading_value": "72.9",
"is_active": "true"
},
{
"userid": "4354069ba6e5",
"time": "2020-06-16T15:22:33Z",
"lng": "-122.03053391",
"lat": "37.33180957",
"uuid": "512f2543-a458-424c-a141-4354069ba6e5",
"sensor_name": "JQR928",
"reading_value": "41.3",
"is_active": "true"
}
]
}

Three things I noticed which needs corrections.
Dynamo table name is not replaced correctly. We need to use !Sub with tableName: !Ref EventsTable
There is a comma after "Active" : { "S": "$event.is_active" } which shouldn't be there.
The three additional parameters are not all valid at root level in batch-write-item api, I removed them temporarily, we can add as needed. "ReturnValues": "UPDATED_NEW", "ReturnConsumedCapacity": "NONE", and "ReturnItemCollectionMetrics": "NONE"
Updated request template:
requestTemplates:
application/json: !Sub
- |
#set($inputRoot = $input.path('$'))
{
"RequestItems": {
"${tableName}": [
#foreach($event in $inputRoot.event)
{
"PutRequest": {
"Item": {
"UserID" : { "S": "$event.userid" },
"TimeUTC" : { "S": "$event.time" },
"Lat": { "S": "$event.lat" },
"Lng": { "S": "$event.lng" },
"UUID" : { "S": "$event.uuid"},
"Sensor" : { "S": "$event.sensor_name" },
"Reading" : { "S": "$event.reading_value" },
"Active" : { "S": "$event.is_active" }
}
}
}#if($foreach.hasNext),#end
#end
]
},
"ReturnValues": "UPDATED_NEW",
"ReturnConsumedCapacity": "NONE",
"ReturnItemCollectionMetrics": "NONE"
}
- {
tableName: !Ref EventsTable
}

I think this line is the issue:
"${self:resources.Resources.EventsTable.Properties.TableName}"
I am not sure that works in API Gateway VTL. My recommendation is to get it working with static code, then add the refs back in.

Related

Adding organization and app in serverless.ts breaks the application

i am experiencing very strange behaviour with my serverless application,
here is my serverless.ts file
import type { AWS } from '#serverless/typescript';
import {
hello,
} from '#functions';
const serverlessConfiguration: AWS = {
service: 'users',
frameworkVersion: '2',
// org: '<MY-ORG-NAME>',
// app: '<MY-APP-NAME>',
custom: {
esbuild: {
bundle: true,
minify: false,
sourcemap: true,
exclude: ['aws-sdk'],
target: 'node14',
define: { 'require.resolve': undefined },
platform: 'node',
},
'serverless-offline': {
httpPort: 4000,
},
ngrokTunnel: {
tunnels: [
{
port: 4000,
},
],
},
avatarUploadBucket: '<NAME>',
userReplicationTopic: '<NAME>',
replicatedUserRemovalTopic:
'<NAME>',
},
plugins: [
'serverless-esbuild',
'serverless-offline',
'serverless-ngrok-tunnel',
],
provider: {
name: 'aws',
runtime: 'nodejs14.x',
profile: '<MY-AWS-PROFILE>',
region: '<MY-REGION>',
stage: 'dev',
apiGateway: {
minimumCompressionSize: 1024,
shouldStartNameWithService: true,
},
iamRoleStatements: [
{
Effect: 'Allow',
Action: ['s3:*', 'sns:*'],
Resource: '*',
},
],
environment: {
AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
NODE_OPTIONS: '--enable-source-maps --stack-trace-limit=1000',
USER_REPLICATION_TOPIC_ARN: {
Ref: 'UserReplicationSNSTopic',
},
REPLICATED_USER_REMOVAL_TOPIC_ARN: {
Ref: 'ReplicatedUserRemovalSNSTopic',
},
},
lambdaHashingVersion: '20201221',
},
functions: {
hello
},
resources: {
Resources: {
AvatarUploadBucket: {
Type: 'AWS::S3::Bucket',
Properties: {
BucketName: '${self:custom.avatarUploadBucket}',
AccessControl: 'PublicRead',
},
},
UserReplicationSNSTopic: {
Type: 'AWS::SNS::Topic',
Properties: {
TopicName: '${self:custom.userReplicationTopic}',
},
},
ReplicatedUserRemovalSNSTopic: {
Type: 'AWS::SNS::Topic',
Properties: {
TopicName: '${self:custom.replicatedUserRemovalTopic}',
},
},
},
},
outputs: {
snsTopics: {
ReplicatedUserRemovalSNSTopicARN: '!Ref ReplicatedUserRemovalSNSTopic',
},
},
};
module.exports = serverlessConfiguration;
i currently have org and app commented out and it works but if i uncomment them, and send request to the api endpoint i get following error Runtime.ImportModuleError: Error: Cannot find module 's_hello'
it is not about the contents of file since i get this error for every microservice i have and for every function in that microservice, for this commented out version, this is the handler (in aws console)
but if i uncomment it, this is the result
why does this happen?
P.S: I can see the upload also happen in serverless dashboard.

How to import swagger/?format=openapi to postman from django-rest-swagger without error of format not recognized

Our project use django-rest-swagger to manage API, and we would like to export all api and import Postman, I can get JSON by below url localhost:5000/swagger/?format=openapi, but when I import the file, postman says Error while importing: format not recognized, How to import swagger/?format=openapi to postman from django-rest-swagger without error of format not recognized?
Is there anyone who knows some easy way to solve it? Thanks so much for any advice!!!!!
{
swagger: "2.0",
info: {
title: "TestProjectAPI",
description: "",
version: ""
},
host: "localhost:5000",
schemes: [
"http"
],
paths: {
/api-token/: {
post: {
operationId: "api-token_post",
responses: {
201: {
description: ""
}
},
parameters: [
{
name: "data",
in: "body",
schema: {
type: "object",
properties: {
pic_id: {
description: "",
type: "string"
},
phonenumber: {
description: "",
type: "string"
},
checkcode: {
description: "",
type: "string"
},
user_phone: {
description: "",
type: "string"
},
phone_code: {
description: "",
type: "string"
},
username: {
description: "",
type: "string"
},
password: {
description: "",
type: "string"
}
}
}
}
],
description: "User Login",
summary: "User Login",
consumes: [
"application/json"
],
tags: [
"api-token"
]
}
},
/porject_management/: {
get: {
operationId: "porject_management_list",
responses: {
200: {
description: ""
}
},
parameters: [
{
name: "page",
required: false,
in: "query",
description: "A page number within the paginated result set.",
type: "integer"
},
{
name: "page_size",
required: false,
in: "query",
description: "Number of results to return per page.",
type: "integer"
},
{
name: "search",
required: false,
in: "query",
description: "A search term.",
type: "string"
},
{
name: "project",
required: false,
in: "query",
description: "",
type: "string"
},
{
name: "state",
required: false,
in: "query",
description: "",
type: "number"
},
{
name: "ordering",
required: false,
in: "query",
description: "Which field to use when ordering the results.",
type: "string"
}
],
description: "porject management",
summary: "porject management",
tags: [
"porject_management_post"
]
},
post: {
operationId: "porject_management_post",
responses: {
201: {
description: ""
}
},
parameters: [
{
name: "data",
in: "body",
schema: {
type: "object",
properties: {
project: {
description: "",
type: "string"
},
tc_code: {
description: "",
type: "string"
},
visitors_number: {
description: "",
type: "integer"
},
site_selection: {
description: "",
type: "string"
},
contact_name: {
description: "",
type: "string"
},
contact_number: {
description: "",
type: "string"
},
remark: {
description: "",
type: "string"
},
type: {
description: "",
type: "integer"
},
state: {
description: "",
type: "integer"
},
status: {
description: "",
type: "integer"
},
creater: {
description: "",
type: "string"
},
modifier: {
description: "",
type: "string"
}
}
}
}
],
description: "Porject management",
summary: "Porject management",
consumes: [
"application/json"
],
tags: [
"homemanager"
]
}
},
securityDefinitions: {
basic: {
type: "basic"
}
}
}
Have you tried using:
python3 manage.py generateschema --file openapi-schema.yml
in terminal? Then you can directly import the schema to your POSTMAN. You are providing JSON format, use yaml format for postman it should work.
Finally, I solved my problem by eolink.com,
Firstly, import JSON from localhost:5000/swagger/?format=openapi
Secondly, export Swagger by eolink.com, and then you can import that file to postman!!!

CloudFormation - Using - >- expression with !Sub

Can I use - >- with !Sub?
Properties:
DashboardBody: !Sub
- >-
'{
... too long json ...
'}
- {something else}
Will it work?
Yes, it will work. Here is an example:
Resources:
MyDashBoard:
Type: AWS::CloudWatch::Dashboard
Properties:
DashboardBody:
!Sub
- >-
{
"widgets": [
{
"type": "metric",
"width": 12,
"height": 6,
"properties": {
"metrics": [
["AWS/EC2","CPUUtilization","AutoScalingGroupName","<your-asg-name>"]
],
"region": "${AWS::Region}",
"period": 60,
"title": "${title}"
}
}
]
}
- title: "my-dasboard-title"
DashboardName: my-dashboard-name

Invalid template resource property 'ElbDomainName'

For the below cloudformation template:
AWSTemplateFormatVersion: "2010-09-09"
Description: "Todobackend Stack"
# Stack Parameters
Parameters:
VpcId:
Type: "AWS::EC2::VPC::Id"
Description: "The target VPC Id"
SubnetId:
Type: "AWS::EC2::Subnet::Id"
Description: "The target Subnet Id in availability zone a"
KeyPair:
Type: "String"
Description: "The key pair that is allowed SSH access"
InstanceCount:
Type: "Number"
Description: "The desired number of application instances"
DbSubnets:
Type: "List<AWS::EC2::Subnet::Id>"
Description: "The target DB Subnet Group subnet Ids"
DbAvailabilityZone:
Type: "String"
Description: "The target availability zone for the database instance"
DbUsername:
Type: "String"
Description: "The RDS database username"
DbPassword:
Type: "String"
Description: "The RDS database password"
NoEcho: "true"
# Stack Resources
Resources:
# Configure auto scaing group
AutoScalingGroup:
Type: "AWS::AutoScaling::AutoScalingGroup"
Properties:
VPCZoneIdentifier: [ { "Ref": "SubnetId" } ]
LaunchConfigurationName: { "Ref": "AutoScalingLaunchConfiguration" }
MinSize: 0
MaxSize: 2
DesiredCapacity: { "Ref": "InstanceCount" }
Tags:
- Key: "Name"
Value: { "Fn::Join": ["", [ { "Ref": "AWS::StackName" }, " -instance" ] ] }
PropagateAtLaunch: "true"
AutoScalingLaunchConfiguration:
Type: "AWS::AutoScaling::LaunchConfiguration"
Properties:
ImageId: ami-05958d7635caa4d04
InstanceType: t2.micro
keyName: { "Ref": "KeyPair" }
IamInstanceProfile: { "Ref": "EC2InstanceProfile" }
SecurityGroups:
- { "Ref": "EC2InstanceSecurityGroup" }
UserData: {
"Fn::Base64": { "Fn::Join": ["", [
"#!/bin/bash\n",
"echo ECS_CLUSTER=", { "Ref": "EcsCluster"}, " >> /etc/ecs/ecs.config\n"
] ] }
}
EC2InstanceSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "todobackend-sg"
VpcId: { "Ref": "VpcId" }
SecurityGroupIngress:
- IpProtocol: "tcp"
FromPort: "8080"
ToPort: "8080"
SourceSecurityGroupId: { "Ref": "ElbSecurityGroup" }
- IpProtocol: "tcp"
FromPort: "22"
ToPort: "22"
CidrIp: "0.0.0.0/0"
Tags:
- Key: "Name"
Value: { "Fn::Join": ["", [ { "Ref": "AWS::StackName" }, "-instance-sg" ] ] }
EC2InstanceProfile:
Type: "AWS::IAM::InstanceProfile"
Properties:
Path: "/"
Roles: [ { "Ref": "EC2InstanceRole" } ]
EC2InstanceRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument: {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": [ "ec2.amazonaws.com" ] },
"Action": [ "sts:AssumeRole"]
}
]
}
Path: "/"
ManagePolicyArns:
- "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceForEc2Role"
# Configure RDS
DbInstance:
Type: "AWS::RDS::DBInstance"
Properties:
DBSubnetGroupName: { "Ref": "DbSubnetGroup" }
MultiAZ: "false"
AvailabilityZone: { "Ref": "DBAvailabilityZone" }
AllocatedStorage: 8
StorageType: "gp2"
DBInstanceClass: "db.t2.micro"
DBName: "todobackend"
Engine: "MySQL"
EngineVersion: "5.6"
MasterUserName: { "Ref": "DbUsername" }
MasterUserPassword: { "Ref": "DbPassword" }
VPCSecurityGroups:
- { "Ref": "DbSecurityGroup" }
Tags:
- Key: "Name"
Value: { "Fn::Join": ["", [ { "Ref": "AWS::Stackname" }, "-db" ] ] }
DbSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "Todobackend DB Security Group"
VpcId: { "Ref": "VpcId" }
SecurityGroupIngress:
- IpProtocol: "tcp"
FromPort: "3306"
ToPort: "3306"
SourceSecurityGroupId: { "Ref": "EC2InstanceSecurityGroup" }
DbSubnetGroup:
Type: "AWS::RDS::DBSubnetGroup"
Properties:
DBSubnetGroupDescription: "Todobackend DB Subnet Group"
SubnetIds: { "Ref": "DbSubnets" }
Tags:
- Key: "Name"
Value: { "Fn::Join": ["", [ { "Ref": "AWS::StackName" }, "-db-subnet-group" ] ] }
# Configure ELB
ElasticLoadBalancer:
Type: "AWS::ElasticLoadBalancing::LoadBalancer"
Properties:
CrossZone: "false"
SecurityGroups: [ { "Ref": "ElbSecurityGroup" } ]
Listeners:
- LoadBalancerPort: "80"
InstancePort: "8000"
Protocol: "http"
HealthCheck:
Target: "HTTP:8000/todos"
HealthyThreshold: "2"
UnhealthyThreshold: "10"
Interval: "30"
Timeout: "5"
Subnets: [ { "Ref": "SubnetId" } ]
Tags:
- Key: "Name"
Value: { "Fn::Join": ["", [ { "Ref": "AWS::StackName" }, "-elb" ] ] }
ElbSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "Todobackend ELB Security Group"
VpcId: { "Ref": "VpcId" }
SecurityGroupIngress:
- IpProtocol: "tcp"
FromPort: "80"
ToPort: "80"
CidrIp: "0.0.0.0/0"
Tags:
- Key: "Name"
Value: { "Fn::Join": ["", [ { "Ref": "AWS::StackName" }, "-elb-sg" ] ] }
# Configure ECS
EcsCluster:
Type: "AWS::ECS::EcsCluster"
TodobackendTaskDefinition:
Type: "AWS::ECS::TaskDefinition"
Properties:
ContainerDefinitions:
- Name: todobackend
Image: shamdockerhub/todobackend
Memory: 450
Environment:
- Name: DJANGO_SETTINGS_MODULE
Value: todobackend.settings.release
- Name: MYSQL_HOST
Value: { "Fn::GetAtt": ["DbInstance", "Endpoint.Address"] }
- Name: MYSQL_USER
Value: { "Ref": "DbUsername" }
- Name: MYSQL_PASSWORD
Value: { "Ref": "DbPassword" }
MountPoints:
- ContainerPath: /var/www/todobackend
SourceVolume: webroot
Command:
- uwsgi
- "--socket /var/www/todobackend/todobackend.sock"
- "--chmod-socket=666"
- "--module todobackend.wsgi"
- "--master"
- "--die-on-term"
- Name: nginx
Image: shamdockerhub/todobackend-nginx
Memory: 300
PortMappings:
- ContainerPort: "8000"
HostPort: "8000"
MountPoints:
- ContainerPath: /var/www/todobackend
SourceVolume: webroot
Volumes:
- Name: webroot
Host:
SourcePath: /ecs/webroot
TodobackendService:
Type: "AWS::ECS::Service"
Properties:
TaskDefinition: { "Ref": "TodobackendTaskDefinition" }
Cluster: { "Ref": "EcsCluster" }
LoadBalancers:
- ContainerName: "nginx"
ContainerPort: "8000"
LoadBalancerName: { "Ref": "ElasticLoadBalancer" }
Role: { "Ref": "EcsServiceRole" }
DesiredCount: 0
EcsServiceRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument: {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [ "ecs.amazonaws.com" ]
},
"Action": [ "sts:AssumeRole"]
}
]
}
Path: "/"
ManagePolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole
TodobackendAdhocTaskDefinition: # Application management task
Type: "AWS::ECS::TaskDefinition"
Properties:
ContainerDefinitions:
- Name: todobackend
Image: shamdockerhub/todobackend
Memory: 245
Environment:
- Name: DJANGO_SETTINGS_MODULE
Value: todobackend.settings.release
- Name: MYSQL_HOST
Value: { "Fn::GetAtt": ["DbInstance", "Endpoint.Address"] }
- Name: MYSQL_USER
Value: { "Ref": "DbUsername" }
- Name: MYSQL_PASSWORD
Value: { "Ref": "DbPassword" }
MountPoints:
- ContainerPath: /var/www/todobackend
SourcePath: webroot
Volumes:
- Name: webroot
Host:
SourcePath: /ecs/webroot
# Stack outputs
Outputs:
ElbDomainName:
Description: "Public DNS name of Elastic Load Balancer"
Value: { "Fn::GetAtt": [ "ElasticLoadBalancer", "DNSName" ] }
EcsCluster:
Description: "Amazon resource name (ARN) of Todobackend Ecs Cluster"
Value: { "Ref": "EcsCluster" }
TodobackendTaskDefinition:
Description: "Amazon resource name (ARN) of Todobackend Task definition"
Value: { "Ref": "TodobackendTaskDefinition"}
TodobackendAdhocTaskDefinition:
Description: "Amazon resource name(ARN) of Todobackend Adhoc Task Definition"
Value: { "Ref": "TodobackendAdhocTaskDefinition" }
TodobackendService:
Description: "Amazon resource name (ARN) of Todobackend service"
Value: { "Ref": "TodobackendService" }
below is the error from Outputs block:
Invalid template resource property 'ElbDomainName'
We need dns name of elastic load balancer to be part of Outputs block of stack
Why 'ElbDomainName' an invalid property?
The entire Outputs: block is indented a level too far. Outputs: should not be underneath Resources: indentation-wise
Outputs documentation

Cloudformation unable to create resource policy for apigateway

The resource policy is working fine when i directly pass it to the console.
Below is resource policy example :-
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:us-west-2:339159142535:ooxmwl6q4e/*",
"Condition": {
"IpAddress": {
"aws:SourceIp": [
""14.98.8.190/32""
]
}
}
}
]
}
Now how to create a cloudformation template for this to get created and get attached to the apigateway
I tried to create a policy but as per new policy "Principal" is depricated.
I created a role also but no help. Below is role snippet :-
{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"Apifirewall": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"apigateway.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"Policies": [
{
"PolicyName": "Apifirewall",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": [
"arn:aws:execute-api:us-west-2:339159142535:ooxmwl6q4e/*"
],
"Condition": {
"IpAddress": {
"aws:SourceIp": [
"14.98.8.190/32"
]
}
}
}
]
}
}
]
}
}
},
"Outputs": {
"Apifirewall": {
"Value": {
"Fn::GetAtt": [
"Apifirewall",
"Arn"
]
}
}
}
}
APIGateway resource policy is not binding to IAM Policy, it's different kind of resource.
So to implement it on your RestApi your should use the Policy parameter on AWS::ApiGateway::RestApi resource on
{
"Type" : "AWS::ApiGateway::RestApi",
"Properties" : {
"ApiKeySourceType" : String,
"BinaryMediaTypes" : [ String, ... ],
"Body" : JSON object,
"BodyS3Location" : S3Location,
"CloneFrom" : String,
"Description" : String,
"EndpointConfiguration" : EndpointConfiguration,
"FailOnWarnings" : Boolean,
"MinimumCompressionSize" : Integer,
"Name" : String,
"Parameters" : { String:String, ... },
"Policy" : JSON object
}
}
Below is the entire CFT for api deployment with lambda integration
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": {
"AppEnv": {
"Type": "String",
"Description": "Application environment, for this deployment"
},
"DeployTag": {
"Type": "String",
"Description": "Distinct deployment tag ex: BLUE, GREEN"
}
},
"Resources": {
"LambdaExecutionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/AWSLambdaFullAccess"
]
}
},
"RecommenderLambda": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Handler": "recommender_field_validation_lambda.lambda_handler",
"FunctionName": "recommenderlambda2",
"Role": {
"Fn::GetAtt": [
"LambdaExecutionRole",
"Arn"
]
},
"Environment": {
"Variables": {
"S3_BUCKET": "belcorp.recommender.test",
"REGION_NAME": "us-west-2",
"TOPIC_ARN": {
"Fn::ImportValue": "RecommenderTopicARN"
},
"TABLE_NAME": {
"Fn::ImportValue": "recommederrequestinfo"
}
}
},
"Code": {
"S3Bucket": "belcorp.recommender.lambdas",
"S3Key": "recommender_field_validation_lambda.zip"
},
"Runtime": "python3.6",
"Timeout": 25
}
},
"LambdaPermission": {
"DependsOn": "RecommenderLambda",
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:invokeFunction",
"FunctionName": "recommenderlambda2",
"Principal": "apigateway.amazonaws.com",
"SourceArn": {
"Fn::Join": [
"",
[
"arn:aws:execute-api:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":",
{
"Ref": "RecommenderApi"
},
"/*"
]
]
}
}
},
"RecommenderApi": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"EndpointConfiguration": {
"Types": [
"EDGE"
]
},
"Description": "RecommenderAPI",
"Name": {
"Fn::Sub": "RecommenderApi-${AppEnv}-${DeployTag}"
},
"Policy": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": {
"Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*"
},
"Condition": {
"IpAddress": {
"aws:SourceIp": [
"14.98.8.190/32"
]
}
}
}
]
}
}
},
"ApiGatewayAccount": {
"Type": "AWS::ApiGateway::Account",
"Properties": {
"CloudWatchRoleArn": {
"Fn::ImportValue": "cloudwatchRole"
}
}
},
"ApiDeployment": {
"Type": "AWS::ApiGateway::Deployment",
"DependsOn": [
"OfferPostMethod",
"OrderPostMethod"
],
"Properties": {
"RestApiId": {
"Ref": "RecommenderApi"
},
"StageName": "dev"
}
},
"ProcessInput": {
"Type": "AWS::ApiGateway::Resource",
"Properties": {
"RestApiId": {
"Ref": "RecommenderApi"
},
"ParentId": {
"Fn::GetAtt": [
"RecommenderApi",
"RootResourceId"
]
},
"PathPart": "process-input"
}
},
"OfferLevel": {
"Type": "AWS::ApiGateway::Resource",
"Properties": {
"RestApiId": {
"Ref": "RecommenderApi"
},
"ParentId": {
"Ref": "ProcessInput"
},
"PathPart": "offer-level"
}
},
"OrderLevel": {
"Type": "AWS::ApiGateway::Resource",
"Properties": {
"RestApiId": {
"Ref": "RecommenderApi"
},
"ParentId": {
"Ref": "ProcessInput"
},
"PathPart": "order-level"
}
},
"OfferPostMethod": {
"DependsOn": "RecommenderLambda",
"Type": "AWS::ApiGateway::Method",
"Properties": {
"RestApiId": {
"Ref": "RecommenderApi"
},
"ResourceId": {
"Ref": "OfferLevel"
},
"HttpMethod": "POST",
"AuthorizationType": "NONE",
"Integration": {
"Type": "AWS_PROXY",
"IntegrationHttpMethod": "POST",
"Uri": {
"Fn::Join": [
"",
[
"arn:aws:apigateway:",
{
"Ref": "AWS::Region"
},
":lambda:path/2015-03-31/functions/",
{
"Fn::GetAtt": [
"RecommenderLambda",
"Arn"
]
},
"/invocations"
]
]
},
"IntegrationResponses": [
{
"StatusCode": 200,
"ResponseTemplates": {
"application/json": "$input.json('$.body')"
}
}
]
}
}
},
"OrderPostMethod": {
"DependsOn": "RecommenderLambda",
"Type": "AWS::ApiGateway::Method",
"Properties": {
"RestApiId": {
"Ref": "RecommenderApi"
},
"ResourceId": {
"Ref": "OrderLevel"
},
"HttpMethod": "POST",
"AuthorizationType": "NONE",
"Integration": {
"Type": "AWS_PROXY",
"IntegrationHttpMethod": "POST",
"Uri": {
"Fn::Join": [
"",
[
"arn:aws:apigateway:",
{
"Ref": "AWS::Region"
},
":lambda:path/2015-03-31/functions/",
{
"Fn::GetAtt": [
"RecommenderLambda",
"Arn"
]
},
"/invocations"
]
]
},
"IntegrationResponses": [
{
"StatusCode": 200,
"ResponseTemplates": {
"application/json": "$input.json('$.body')"
}
}
]
}
}
}
},
"Outputs": {
"RootUrl": {
"Description": "Root URL of the API gateway",
"Value": {
"Fn::Join": [
"",
[
"https://",
{
"Ref": "RecommenderApi"
},
".execute-api.",
{
"Ref": "AWS::Region"
},
".amazonaws.com"
]
]
}
},
"OfferUrl": {
"Description": "Root URL of the API gateway",
"Value": {
"Fn::Join": [
"",
[
"https://",
{
"Ref": "RecommenderApi"
},
".execute-api.",
{
"Ref": "AWS::Region"
},
".amazonaws.com",
"/dev/process-input/offer-level"
]
]
}
},
"OrderUrl": {
"Description": "Root URL of the API gateway",
"Value": {
"Fn::Join": [
"",
[
"https://",
{
"Ref": "RecommenderApi"
},
".execute-api.",
{
"Ref": "AWS::Region"
},
".amazonaws.com",
"/dev/process-input/order-level"
]
]
}
}
}
}
Too long for a comment. This is the transformed YAML from this answer, which a commenter pointed out can be done in CloudFormation Designer:
AWSTemplateFormatVersion: 2010-09-09
Parameters:
AppEnv:
Type: String
Description: 'Application environment, for this deployment'
DeployTag:
Type: String
Description: 'Distinct deployment tag ex: BLUE, GREEN'
Resources:
LambdaExecutionRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/AWSLambdaFullAccess'
RecommenderLambda:
Type: 'AWS::Lambda::Function'
Properties:
Handler: recommender_field_validation_lambda.lambda_handler
FunctionName: recommenderlambda2
Role: !GetAtt
- LambdaExecutionRole
- Arn
Environment:
Variables:
S3_BUCKET: belcorp.recommender.test
REGION_NAME: us-west-2
TOPIC_ARN: !ImportValue RecommenderTopicARN
TABLE_NAME: !ImportValue recommederrequestinfo
Code:
S3Bucket: belcorp.recommender.lambdas
S3Key: recommender_field_validation_lambda.zip
Runtime: python3.6
Timeout: 25
LambdaPermission:
DependsOn: RecommenderLambda
Type: 'AWS::Lambda::Permission'
Properties:
Action: 'lambda:invokeFunction'
FunctionName: recommenderlambda2
Principal: apigateway.amazonaws.com
SourceArn: !Join
- ''
- - 'arn:aws:execute-api:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':'
- !Ref RecommenderApi
- /*
RecommenderApi:
Type: 'AWS::ApiGateway::RestApi'
Properties:
EndpointConfiguration:
Types:
- EDGE
Description: RecommenderAPI
Name: !Sub 'RecommenderApi-${AppEnv}-${DeployTag}'
Policy:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal: '*'
Action: 'execute-api:Invoke'
Resource: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*'
Condition:
IpAddress:
'aws:SourceIp':
- 14.98.8.190/32
ApiGatewayAccount:
Type: 'AWS::ApiGateway::Account'
Properties:
CloudWatchRoleArn: !ImportValue cloudwatchRole
ApiDeployment:
Type: 'AWS::ApiGateway::Deployment'
DependsOn:
- OfferPostMethod
- OrderPostMethod
Properties:
RestApiId: !Ref RecommenderApi
StageName: dev
ProcessInput:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref RecommenderApi
ParentId: !GetAtt
- RecommenderApi
- RootResourceId
PathPart: process-input
OfferLevel:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref RecommenderApi
ParentId: !Ref ProcessInput
PathPart: offer-level
OrderLevel:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref RecommenderApi
ParentId: !Ref ProcessInput
PathPart: order-level
OfferPostMethod:
DependsOn: RecommenderLambda
Type: 'AWS::ApiGateway::Method'
Properties:
RestApiId: !Ref RecommenderApi
ResourceId: !Ref OfferLevel
HttpMethod: POST
AuthorizationType: NONE
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Join
- ''
- - 'arn:aws:apigateway:'
- !Ref 'AWS::Region'
- ':lambda:path/2015-03-31/functions/'
- !GetAtt
- RecommenderLambda
- Arn
- /invocations
IntegrationResponses:
- StatusCode: 200
ResponseTemplates:
application/json: $input.json('$.body')
OrderPostMethod:
DependsOn: RecommenderLambda
Type: 'AWS::ApiGateway::Method'
Properties:
RestApiId: !Ref RecommenderApi
ResourceId: !Ref OrderLevel
HttpMethod: POST
AuthorizationType: NONE
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Join
- ''
- - 'arn:aws:apigateway:'
- !Ref 'AWS::Region'
- ':lambda:path/2015-03-31/functions/'
- !GetAtt
- RecommenderLambda
- Arn
- /invocations
IntegrationResponses:
- StatusCode: 200
ResponseTemplates:
application/json: $input.json('$.body')
Outputs:
RootUrl:
Description: Root URL of the API gateway
Value: !Join
- ''
- - 'https://'
- !Ref RecommenderApi
- .execute-api.
- !Ref 'AWS::Region'
- .amazonaws.com
OfferUrl:
Description: Root URL of the API gateway
Value: !Join
- ''
- - 'https://'
- !Ref RecommenderApi
- .execute-api.
- !Ref 'AWS::Region'
- .amazonaws.com
- /dev/process-input/offer-level
OrderUrl:
Description: Root URL of the API gateway
Value: !Join
- ''
- - 'https://'
- !Ref RecommenderApi
- .execute-api.
- !Ref 'AWS::Region'
- .amazonaws.com
- /dev/process-input/order-level
If you are using YAML for CloudFormation, the Policy can be in YAML. There is no need to use JSON for it. For example:
Parameters:
ApiAllowedIps:
Type: CommaDelimitedList
RestApi:
Type: AWS::ApiGateway::RestApi
Properties:
...
Policy:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: '*'
Principal: '*'
Resource: '*'
Condition:
IpAddress:
aws:SourceIp: !Ref ApiAllowedIps