I'm trying to creating Cognito user pool with a custom domain name through AWS CDK. I manage to get everyting working untill to the point where I needed to create an A record in the Rout53 hosted zone. I searched through all the documents but coudn't find a way to do that. Following is my code. Any help would be much appriciated.
const cfnUserPool = new CfnUserPool(this, 'MyCognitoUserPool', {
userPoolName: 'MyCognitoUserPool',
adminCreateUserConfig: {
allowAdminCreateUserOnly: false
},
policies: {
passwordPolicy: {
minimumLength: 8,
requireLowercase: true,
requireNumbers: true,
requireSymbols: true,
requireUppercase: true,
temporaryPasswordValidityDays: 30
}
},
usernameAttributes: [
UserPoolAttribute.EMAIL
],
schema: [
{
attributeDataType: 'String',
name: UserPoolAttribute.EMAIL,
mutable: true,
required: true
},
{
attributeDataType: 'String',
name: UserPoolAttribute.FAMILY_NAME,
mutable: false,
required: true
},
{
attributeDataType: 'String',
name: UserPoolAttribute.GIVEN_NAME,
mutable: false,
required: true
}
]
});
const cognitoAppDomain = new CfnUserPoolDomain(this, "PigletAuthDomainName", {
domain: authDomainName,
userPoolId: cfnUserPool.ref,
customDomainConfig: {
certificateArn: 'ACM Certificate arn'
}
});
/*
TODO: Create an A record from the created cnfUserPoolDomain
*/
Everything works up untill to this point. Now the question is how to create an A record using the CfnUserPoolDomain
Any help is much appriciated.
Update May 2020
The UserPoolDomain construct has been extended and a UserPoolDomainTarget was added to provide this functionality.
Now, all you need to do is the following:
const userPoolDomain = new cognito.UserPoolDomain(this, 'UserPoolDomain', {
userPool,
customDomain: {
domainName: authDomainName,
certificate,
},
});
new route53.ARecord(this, 'UserPoolCloudFrontAliasRecord', {
zone: hostedZone,
recordName: authDomainName,
target: route53.RecordTarget.fromAlias(new route53_targets.UserPoolDomainTarget(userPoolDomain)),
});
I had the same Problem, It looks like CloudFormation does not have a return parameter for the CfnUserPoolDomain AliasTarget. Which means the cdk can not provide this parameter either.
I ended up implementing it using the AWS SDK (npm install aws-sdk) and getting the value using the APIs:
Update: The better solution is to use the AwsCustomResource. You can see a detailed example in aws/aws-cdk (#6787):
const userPoolDomainDescription = new customResources.AwsCustomResource(this, 'user-pool-domain-description', {
onCreate: {
physicalResourceId: 'user-pool-domain-description',
service: 'CognitoIdentityServiceProvider',
action: 'describeUserPoolDomain',
parameters: {
Domain: userPoolDomain.domain
}
}
});
const dnsName = userPoolDomainDescription.getData('DomainDescription.CloudFrontDistribution').toString();
// Route53 alias record for the UserPoolDomain CloudFront distribution
new route53.ARecord(this, 'UserPoolDomainAliasRecord', {
recordName: userPoolDomain.domain,
target: route53.RecordTarget.fromAlias({
bind: _record => ({
hostedZoneId: 'Z2FDTNDATAQYW2', // CloudFront Zone ID
dnsName: dnsName,
}),
}),
zone,
})
Here's how to get around it. Assuming you have a stack.yaml that you deploy with a CI tool, say through bash:
THE_STACK_NAME="my-cognito-stack"
THE_DOMAIN_NAME="auth.yourveryowndomain.org"
# get the alias target
# notice that it will be empty upon first launch (chicken and the egg problem)
ALIAS_TARGET=$(aws cognito-idp describe-user-pool-domain --domain ${THE_DOMAIN_NAME} | grep CloudFrontDistribution | cut -d \" -f4)
# create/update the deployment CloudFormation stack
# notice the AliasTarget parameter (which can be empty, it's okay!)
aws cloudformation deploy --stack-name ${THE_STACK_NAME} --template-file stack.yaml --parameter-overrides AliasTarget=${ALIAS_TARGET} DomainName=${THE_DOMAIN_NAME}
The stack.yaml minimal version (remember to fill the UserPool config):
---
AWSTemplateFormatVersion: 2010-09-09
Parameters:
DomainName:
Type: String
Default: auth.yourveryowndomain.org
Description: The domain name to use to serve this project.
ZoneName:
Type: String
Default: yourveryowndomain.org
Description: The hosted zone name coming along with the DomainName used.
AliasTarget: # no default value, can be empty
Type: String
Description: The UserPoolDomain alias target.
Conditions: # here's "the trick"
HasAliasTarget: !Not [!Equals ['', !Ref AliasTarget]]
Resources:
Certificate:
Type: "AWS::CertificateManager::Certificate"
Properties:
DomainName: !Ref ZoneName
DomainValidationOptions:
- DomainName: !Ref ZoneName
ValidationDomain: !Ref ZoneName
SubjectAlternativeNames:
- !Ref DomainName
UserPool:
Type: AWS::Cognito::UserPool
Properties:
[... fill that with your configuration! ...]
UserPoolDomain:
Type: AWS::Cognito::UserPoolDomain
Properties:
UserPoolId: !Ref UserPool
Domain: !Ref DomainName
CustomDomainConfig:
CertificateArn: !Ref Certificate
DnsRecord: # if AliasTarget parameter is empty, well we just can't do that one!
Condition: HasAliasTarget # and here's how we don't do it when we can't
Type: AWS::Route53::RecordSet
Properties:
HostedZoneName: !Sub "${ZoneName}."
AliasTarget:
DNSName: !Ref AliasTarget
EvaluateTargetHealth: false
# HostedZoneId value for CloudFront is always this one
# see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html
HostedZoneId: Z2FDTNDATAQYW2
Name: !Ref DomainName
Type: A
Be aware CloudFormation conditions are not "a trick" at all: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html. We simply use it as a trick along with the "first launch won't do it all" to get around our scenario.
Kinda weird, but only for the first run! Launch it again: everything is fine.
PS: can't wait to avoid all that by simply having the CloudFrontDistribution alias target directly in the AWS::Cognito::UserPoolDomain return values!!
Related
We are trying create a AWS::WAFv2::IPSet in our SAM template.
WhitelistedIPAddressesIPSet:
Type: AWS::WAFv2::IPSet
Properties:
Description: 'Merchant IPs'
Scope: REGIONAL
IPAddressVersion: IPV4
Addresses: [0.0.0.0/32, 0.0.10.0/32]
The creation of the IP sets is done successfully.
Once creating the AWS::WAFv2::WebACLAssociation.
WAFApiAssociation:
Type: AWS::WAFv2::WebACLAssociation
DependsOn:
- ApiGateway
- WAFWebAcl
Properties:
ResourceArn: !Sub 'arn:aws:apigateway:${AWS::Region}::/restapis/${ApiGateway}/stages/${EnvType}'
WebACLArn: !GetAtt WAFWebAcl.Arn
The CloudFormation failes and does a rollback. Error displayed is as follows:
Resource handler returned
ion message: "AWS WAF couldn?t
perform the operation
because your resource
doesn?t exist. (Service:
Wafv2, Status Code: 400,
Request ID: e337720a-e32c-
4c29-acde-1896855405c9,
Extended Request ID:
null)" (RequestToken: f24d
0488-3016-4030-3a3b-bbb246
66f130, HandlerErrorCode:
NotFound)
We tried different formatting the SAM template of the IP set, to see if that causes the issues, without any success.
Anyone that could share some helpful insights to this issue?
A) You don't need DependsOn if your resource already directly depends on those other resources. In this case it does, so you can remove this property.
B) You'll need to share your whole stack here, not just what is shared because there is likely a problem with your APIGW configuration. Because that failed to be created, it's possible you get this subsequent problem showing up.
Creating the APIGW isn't enough, you need to make sure to actually attach the WAF after the APIGW stage was created and not just the APIGW. In this case replace the ResourceArn with one that references the APIGW Stage. (And further you might need to wait for the stage deployment to finish.)
This is the APIGW template Warren Parad
CDEAPI:
Type: AWS::Serverless::Api
Properties:
# Domain:
# DomainName: !Ref CDEAPIDomainName
# SecurityPolicy: TLS_1_2
# CertificateArn: !Sub 'arn:aws:acm:us-east-1:${AWS::AccountId}:certificate/${CDEAPICertificateArn}'
# EndpointConfiguration: EDGE
# Route53:
# HostedZoneId: !Ref CDEAPIHostedZoneId
AccessLogSetting:
DestinationArn: !GetAtt CDEAPIAccessLogGroup.Arn
Format: >-
{ "requestId":"$context.requestId",
"ip":"$context.identity.sourceIp",
"caller":"$context.identity.caller",
"user":"$context.identity.user",
"userAgent":"$context.identity.userAgent",
"userArn":"$context.identity.userArn",
"requestTime":"$context.requestTime",
"requestTimeEpoch":"$context.requestTimeEpoch",
"httpMethod":"$context.httpMethod",
"resourcePath":"$context.resourcePath",
"path":"$context.path",
"status":"$context.status",
"protocol":"$context.protocol",
"responseLength":"$context.responseLength",
"responseLatency":"$context.responseLatency",
"authorizerLatency":"$context.authorizer.integrationLatency",
"integrationLatency":"$context.integrationLatency",
"integrationStatus":"$context.integrationStatus",
"xrayTraceId":"$context.xrayTraceId",
"errorMessage":"$context.error.message",
"domainName":"$context.domainName",
"domainPrefix":"$context.domainPrefix",
"tokenScopes":"$context.authorizer.claims.scope",
"tokenIat":"$context.authorizer.claims.iat",
"tokenExp":"$context.authorizer.claims.exp",
"cognitoIdentityId":"$context.identity.cognitoIdentityId",
"awsEndpointRequestId":"$context.awsEndpointRequestId",
"arn":"$context.identity.userArn",
"account":"$context.identity.accountId",
"claims-sub":"$context.authorizer.claims.sub",
"waf-error":"$context.waf.error",
"waf-status":"$context.waf.status",
"waf-latency":"$context.waf.latency",
"waf-response":"$context.waf.wafResponseCode",
"authenticate-error":"$context.authenticate.error",
"authenticate-status":"$context.authenticate.status",
"authenticate-latency":"$context.authenticate.latency",
"integration-error":"$context.integration.error",
"integration-status":"$context.integration.status",
"integration-latency":"$context.integration.latency",
"integration-requestId":"$context.integration.requestId",
"integration-integrationStatus":"$context.integration.integrationStatus",
"response-latency":"$context.responseLatency" }
StageName: !Ref EnvType
Auth:
DefaultAuthorizer: CognitoAuthorizer
AddDefaultAuthorizerToCorsPreflight: false
Authorizers:
CognitoAuthorizer:
AuthType: COGNITO_USER_POOLS
UserPoolArn: !Sub 'arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${CognitoUserPoolArn}'
I have setup an API Gateway (v1, not v2) REST API resource using CloudFormation template. Recently I have noticed that the default execute-api endpoint is also created, which I can disable in the settings.
The type of this API is AWS::ApiGateway::RestApi.
Naturally, I would like this to be done through the template, so the question is: can this setting be defined in the CloudFormation template, rather than havign to be clicked manually in the AWS Console? This option is available for the APIGateway V2 API resource (AWS::ApiGatewayV2::Api) but not the APIGateway V1 REST API resource (AWS::ApiGateway::RestApi) in the CloudFormation templates, even though it can be changed manuall for the APIGateway V1 REST API in the console.
There is also a CLI way of doing this for the AWS::ApiGateway::RestApi.
Here are some links I have used to search for this setting:
AWS::ApiGatewayV2::API
AWS::ApiGateway::RestApi
Disabling default api-execute endpoint via CLI
Support for disabling the default execute-api endpoint has recently been added to AWS::ApiGateway::RestApi cloudformation: DisableExecuteApiEndpoint
MyRestApi:
Type: 'AWS::ApiGateway::RestApi'
Properties:
DisableExecuteApiEndpoint: true
You can disable it though a simple custom resource. Below is an example of such a fully working template that does that:
Resources:
MyRestApi:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Description: A test API
Name: MyRestAPI
LambdaBasicExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonAPIGatewayAdministrator
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
MyCustomResource:
Type: Custom::DisableDefaultApiEndpoint
Properties:
ServiceToken: !GetAtt 'MyCustomFunction.Arn'
APIId: !Ref 'MyRestApi'
MyCustomFunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.lambda_handler
Description: "Disable default API endpoint"
Timeout: 30
Role: !GetAtt 'LambdaBasicExecutionRole.Arn'
Runtime: python3.7
Code:
ZipFile: |
import json
import logging
import cfnresponse
import boto3
logger = logging.getLogger()
logger.setLevel(logging.INFO)
client = boto3.client('apigateway')
def lambda_handler(event, context):
logger.info('got event {}'.format(event))
try:
responseData = {}
if event['RequestType'] in ["Create"]:
APIId = event['ResourceProperties']['APIId']
response = client.update_rest_api(
restApiId=APIId,
patchOperations=[
{
'op': 'replace',
'path': '/disableExecuteApiEndpoint',
'value': 'True'
}
]
)
logger.info(str(response))
cfnresponse.send(event, context,
cfnresponse.SUCCESS, responseData)
else:
logger.info('Unexpected RequestType!')
cfnresponse.send(event, context,
cfnresponse.SUCCESS, responseData)
except Exception as err:
logger.error(err)
responseData = {"Data": str(err)}
cfnresponse.send(event,context,
cfnresponse.FAILED,responseData)
return
In case anyone stumbles across this answer that is using CDK, this can be done concisely (without defining a Lambda function) using the AwsCustomResource construct:
const restApi = new apigw.RestApi(...);
const executeApiResource = new cr.AwsCustomResource(this, "execute-api-resource", {
functionName: "disable-execute-api-endpoint",
onCreate: {
service: "APIGateway",
action: "updateRestApi",
parameters: {
restApiId: restApi.restApiId,
patchOperations: [{
op: "replace",
path: "/disableExecuteApiEndpoint",
value: "True"
}]
},
physicalResourceId: cr.PhysicalResourceId.of("execute-api-resource")
},
policy: cr.AwsCustomResourcePolicy.fromStatements([new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["apigateway:PATCH"],
resources: ["arn:aws:apigateway:*::/*"],
})])
});
executeApiResource.node.addDependency(restApi);
You can disable it in AWS CDK. This is done by finding the CloudFormation resource and setting it to true.
const api = new apigateway.RestApi(this, 'api', );
(api.node.children[0] as apigateway.CfnRestApi).addPropertyOverride('DisableExecuteApiEndpoint','true')
Here is a Python variant of the answer provided by snorberhuis.
rest_api = apigateway.RestApi(self,...)
cfn_apigw = rest_api.node.default_child
cfn_apigw.add_property_override('DisableExecuteApiEndpoint', True)
Amazon's docs on "Abstractions and Escape Hatches" is very good for understanding what's going on here.
I'm trying to create a CloudFront Web Distribution using aws-cdk. I'm able to successfully create the web distribution but I haven't been able to figure out how to associate a lambda function yet.
Below is a snippet of my Typescript aws-cdk code to create the CloudFront Web Distribution. I removed some of the code that's not relevant.
new cloudfront.CloudFrontWebDistribution(this, 'RetsFilesCDN', {
originConfigs: [
{
s3OriginSource: {
originAccessIdentity: cfAccess, /* A CfnCloudFrontOriginAccessIdentity object created in earlier code */
s3BucketSource: files /* S3 bucket created in earlier code */
},
behaviors: [
{
compress: true,
defaultTtlSeconds: 172800,
isDefaultBehavior: true,
maxTtlSeconds: 31536000,
minTtlSeconds: 0
}
]
}
]
});
The CloudFormation code that I'm trying to get it to generate is something like this:
RetsFilesCDNCFDistribution6F414E1A:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
CacheBehaviors:
[]
Comment: CDN for files from the Real Estate RETS services that BranchCMS
integrates with
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
CachedMethods:
- GET
- HEAD
Compress: true
DefaultTTL: 172800
ForwardedValues:
Cookies:
Forward: none
QueryString: false
MaxTTL: 259200
MinTTL: 172800
LambdaFunctionAssociations:
- EventType: origin-response
LambdaFunctionARN: lambdaFunctionArnHere
TargetOriginId: origin1
ViewerProtocolPolicy: redirect-to-https
DefaultRootObject: index.html
Enabled: true
HttpVersion: http2
IPV6Enabled: true
Origins:
- DomainName:
Fn::GetAtt:
- RetsFilesC9F78E92
- DomainName
Id: origin1
S3OriginConfig:
OriginAccessIdentity:
Fn::Join:
- ""
- - origin-access-identity/cloudfront/
- Ref: RetsFilesAccess
PriceClass: PriceClass_100
ViewerCertificate:
AcmCertificateArn: arn:aws:acm:us-east-1:666445282096:certificate/25d4967c-c29a-4d11-983f-86d709769372
SslSupportMethod: sni-only
The exact part that I can't seem to generate is:
LambdaFunctionAssociations:
- EventType: origin-response
LambdaFunctionARN: lambdaFunctionArnHere
Thank you in advance for your help.
I'm not sure if this is the best technique, but the following worked for me.
import cdk = require('#aws-cdk/cdk');
import cloudfront = require('#aws-cdk/aws-cloudfront');
import lambda = require('#aws-cdk/aws-lambda');
export class MyStack extends cdk.Stack {
constructor(parent: cdk.App, name: string, props?: cdk.StackProps) {
super(parent, name, props);
// Create the Lambda function
const lambdaFunc = new lambda.Function(this, 'MyLambda', {
YOUR_LAMBDA_PROPERTIES: HERE
});
// Create the CloudFront Web Distribution
const cf = new cloudfront.CloudFrontWebDistribution(this, 'MyCDN', {
YOUR_CLOUDFRONT_PROPERTIES: HERE
});
/**
* THIS IS THE BEGINNING OF THE SOLUTION
*/
// Get the CloudFront Distribution object to add the LambdaFunctionAssociations to
const cfDist = cf.findChild('CFDistribution') as cloudfront.CfnDistribution;
// Manually add the LambdaFunctionAssociations by adding an override
cfDist.addOverride('Properties.DistributionConfig.DefaultCacheBehavior.LambdaFunctionAssociations', [{
EventType: 'origin-response',
LambdaFunctionARN: lambdaFunc.functionArn + ':2'
}]);
/**
* END OF SOLUTION
*/
}
}
Is it possible to associate a newly created function (lamda#edge / cloudfront function) with an existing cloudfront distribution?
The above code only seems to work for a newly created distribution.
you can use this property for the assosiation of lambda function to your cloudfront cache behaviour
import { CfnDistribution.CacheBehaviorProperty } from '#aws-cdk/aws-cloudfront';
CfnDistribution.CacheBehaviorProperty.LambdaFunctionAssociations
for more information click on this link
So I'm trying to run a lambda on amazon and narrowed down the error finally by testing the lambda in amazons testing console.
The error I got is this.
{
"errorMessage": "Please install mysql2 package manually",
"errorType": "Error",
"stackTrace": [
"new MysqlDialect (/var/task/node_modules/sequelize/lib/dialects/mysql/index.js:14:30)",
"new Sequelize (/var/task/node_modules/sequelize/lib/sequelize.js:234:20)",
"Object.exports.getSequelizeConnection (/var/task/src/twilio/twilio.js:858:20)",
"Object.<anonymous> (/var/task/src/twilio/twilio.js:679:25)",
"__webpack_require__ (/var/task/src/twilio/twilio.js:20:30)",
"/var/task/src/twilio/twilio.js:63:18",
"Object.<anonymous> (/var/task/src/twilio/twilio.js:66:10)",
"Module._compile (module.js:570:32)",
"Object.Module._extensions..js (module.js:579:10)",
"Module.load (module.js:487:32)",
"tryModuleLoad (module.js:446:12)",
"Function.Module._load (module.js:438:3)",
"Module.require (module.js:497:17)",
"require (internal/module.js:20:19)"
]
}
Easy enough, so I have to install mysql2. So I added it to my package.json file.
{
"name": "test-api",
"version": "1.0.0",
"description": "",
"main": "handler.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 0"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"aws-sdk": "^2.153.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-3": "^6.24.1",
"serverless-domain-manager": "^1.1.20",
"serverless-dynamodb-autoscaling": "^0.6.2",
"serverless-webpack": "^4.0.0",
"webpack": "^3.8.1",
"webpack-node-externals": "^1.6.0"
},
"dependencies": {
"babel-runtime": "^6.26.0",
"mailgun-js": "^0.13.1",
"minimist": "^1.2.0",
"mysql": "^2.15.0",
"mysql2": "^1.5.1",
"qs": "^6.5.1",
"sequelize": "^4.31.2",
"serverless": "^1.26.0",
"serverless-plugin-scripts": "^1.0.2",
"twilio": "^3.10.0",
"uuid": "^3.1.0"
}
}
I noticed when I do sls deploy however, it seems to only be packaging some of the modules?
Serverless: Package lock found - Using locked versions
Serverless: Packing external modules: babel-runtime#^6.26.0, twilio#^3.10.0, qs#^6.5.1, mailgun-js#^0.13.1, sequelize#^4.31.2, minimi
st#^1.2.0, uuid#^3.1.0
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
................................
Serverless: Stack update finished...
I think this is why it's not working. In short, how do I get mysql2 library to be packaged correctly with serverless so my lambda function will work with the sequelize library?
Please note that when I test locally my code works fine.
My serverless file is below
service: testapi
# Use serverless-webpack plugin to transpile ES6/ES7
plugins:
- serverless-webpack
- serverless-plugin-scripts
# - serverless-domain-manager
custom:
#Define the Stage or default to Staging.
stage: ${opt:stage, self:provider.stage}
webpackIncludeModules: true
#Define Databases Here
databaseName: "${self:service}-${self:custom.stage}"
#Define Bucket Names Here
uploadBucket: "${self:service}-uploads-${self:custom.stage}"
#Custom Script setup
scripts:
hooks:
#Script below will run schema changes to the database as neccesary and update according to stage.
'deploy:finalize': node database-schema-update.js --stage ${self:custom.stage}
#Domain Setup
# customDomain:
# basePath: "/"
# domainName: "api-${self:custom.stage}.test.com"
# stage: "${self:custom.stage}"
# certificateName: "*.test.com"
# createRoute53Record: true
provider:
name: aws
runtime: nodejs6.10
stage: staging
region: us-east-1
environment:
DOMAIN_NAME: "api-${self:custom.stage}.test.com"
DATABASE_NAME: ${self:custom.databaseName}
DATABASE_USERNAME: ${env:RDS_USERNAME}
DATABASE_PASSWORD: ${env:RDS_PASSWORD}
UPLOAD_BUCKET: ${self:custom.uploadBucket}
TWILIO_ACCOUNT_SID: ""
TWILIO_AUTH_TOKEN: ""
USER_POOL_ID: ""
APP_CLIENT_ID: ""
REGION: "us-east-1"
IDENTITY_POOL_ID: ""
RACKSPACE_API_KEY: ""
#Below controls permissions for lambda functions.
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:UpdateTable
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: "arn:aws:dynamodb:us-east-1:*:*"
functions:
create_visit:
handler: src/visits/create.main
events:
- http:
path: visits
method: post
cors: true
authorizer: aws_iam
get_visit:
handler: src/visits/get.main
events:
- http:
path: visits/{id}
method: get
cors: true
authorizer: aws_iam
list_visit:
handler: src/visits/list.main
events:
- http:
path: visits
method: get
cors: true
authorizer: aws_iam
update_visit:
handler: src/visits/update.main
events:
- http:
path: visits/{id}
method: put
cors: true
authorizer: aws_iam
delete_visit:
handler: src/visits/delete.main
events:
- http:
path: visits/{id}
method: delete
cors: true
authorizer: aws_iam
twilio_send_text_message:
handler: src/twilio/twilio.send_text_message
events:
- http:
path: twilio/sendtextmessage
method: post
cors: true
authorizer: aws_iam
#This function handles incoming calls and where to route it to.
twilio_incoming_call:
handler: src/twilio/twilio.incoming_calls
events:
- http:
path: twilio/calls
method: post
twilio_failure:
handler: src/twilio/twilio.twilio_failure
events:
- http:
path: twilio/failure
method: post
twilio_statuschange:
handler: src/twilio/twilio.statuschange
events:
- http:
path: twilio/statuschange
method: post
twilio_incoming_message:
handler: src/twilio/twilio.incoming_message
events:
- http:
path: twilio/messages
method: post
twilio_whisper:
handler: src/twilio/twilio.whisper
events:
- http:
path: twilio/whisper
method: post
- http:
path: twilio/whisper
method: get
twilio_start_call:
handler: src/twilio/twilio.start_call
events:
- http:
path: twilio/startcall
method: post
- http:
path: twilio/startcall
method: get
resources:
Resources:
uploadBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.uploadBucket}
RDSDatabase:
Type: AWS::RDS::DBInstance
Properties:
Engine : mysql
MasterUsername: ${env:RDS_USERNAME}
MasterUserPassword: ${env:RDS_PASSWORD}
DBInstanceClass : db.t2.micro
AllocatedStorage: '5'
PubliclyAccessible: true
#TODO: The Value of Stage is also available as a TAG automatically which I may use to replace this manually being put here..
Tags:
-
Key: "Name"
Value: ${self:custom.databaseName}
DeletionPolicy: Snapshot
DNSRecordSet:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneName: test.com.
Name: database-${self:custom.stage}.test.com
Type: CNAME
TTL: '300'
ResourceRecords:
- {"Fn::GetAtt": ["RDSDatabase","Endpoint.Address"]}
DependsOn: RDSDatabase
UPDATE:: So I confirmed that running sls package --stage dev seems to create this in the zip folder that would eventually upload to AWS. This confirms that serverless is not creating the package correctly with the mysql2 reference for some reason? Why is this?
webpack config file as requested
const slsw = require("serverless-webpack");
const nodeExternals = require("webpack-node-externals");
module.exports = {
entry: slsw.lib.entries,
target: "node",
// Since 'aws-sdk' is not compatible with webpack,
// we exclude all node dependencies
externals: [nodeExternals()],
// Run babel on all .js files and skip those in node_modules
module: {
rules: [
{
test: /\.js$/,
loader: "babel-loader",
include: __dirname,
exclude: /node_modules/
}
]
}
};
Thanks to dashmugs comment after some investigation on this page (https://github.com/serverless-heaven/serverless-webpack), there is a section on Forced Inclusion. I'll paraphrase it here.
Forced inclusion Sometimes it might happen that you use dynamic
requires in your code, i.e. you require modules that are only known at
runtime. Webpack is not able to detect such externals and the compiled
package will miss the needed dependencies. In such cases you can force
the plugin to include certain modules by setting them in the
forceInclude array property. However the module must appear in your
service's production dependencies in package.json.
# serverless.yml
custom:
webpackIncludeModules:
forceInclude:
- module1
- module2
So I simply did this...
webpackIncludeModules:
forceInclude:
- mysql
- mysql2
Now it works! Hope this helps someone else with the same issue.
None of the previous helped me, I used this solution: https://github.com/sequelize/sequelize/issues/9489#issuecomment-493304014
The trick is to use dialectModule property and override sequelize.
import Sequelize from 'sequelize';
import mysql2 from 'mysql2'; // Needed to fix sequelize issues with WebPack
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
dialect: 'mysql',
dialectModule: mysql2, // Needed to fix sequelize issues with WebPack
host: process.env.DB_HOST,
port: process.env.DB_PORT
}
)
export async function connectToDatabase() {
console.log('Trying to connect via sequelize')
await sequelize.sync()
await sequelize.authenticate()
console.log('=> Created a new connection.')
// Do something
}
The previous so far works on MySql but is not working with Postgres
I have following CF Template
{
"Conditions":{
"CreatedProdStage" : {...}
}
...
"Resources":{
"GetMethod": {
...
},
"ApiDeployement":{
...
},
"ProdStage":{
"Type":"AWS::ApiGateway::Stage",
"Condition":"CreatedProdStage",
"Properties": {
"DeploymentId":"...",
"RestApiId":"...",
"MethodSettings":[{
"CachingEnabled":true,
"HttpMethod":{"Ref":"GetMethod"},
"ResourcePath":"/"
}]
}
}
}
}
And I am getting error
Invalid method setting path:
/~1/st-GetMetho-xxxAUMMRWxxx/caching/enabled. Must be one of:
[/deploymentId, /description,
/cacheClusterEnabled/cacheClusterSize/clientCertificateId/{resourcePath}/{httpMethod}/metrics/enabled,
/{resourcePath}/{httpMethod}/logging/dataTrace,
/{resourcePath}/{httpMethod}/logging/loglevel,
/{resourcePath}/{httpMethod}/throttling/burstLimit/{resourcePath}/{httpMethod}/throttling/rateLimit/{resourcePath}/{httpMethod}/caching/ttlInSeconds,
/{resourcePath}/{httpMethod}/caching/enabled,
/{resourcePath}/{httpMethod}/caching/dataEncrypted,
/{resourcePath}/{httpMethod}/caching/requireAuthorizationForCacheControl,
/{resourcePath}/{httpMethod}/caching/unauthorizedCacheControlHeaderStrategy,
///metrics/enabled, ///logging/dataTrace, ///logging/loglevel,
///throttling/burstLimit ///throttling/rateLimit
///caching/ttlInSeconds, ///caching/enabled,
///caching/dataEncrypted,
///caching/requireAuthorizationForCacheControl,
///caching/unauthorizedCacheControlHeaderStrategy, /va
Am I missing something? I thought ResourcePath and HttpMethod are the only required attributes
You first need to enable caching on the stage with the CacheClusterEnabled property. This will allow you to set up caching for methods as you have done in you MethodSettings:
...
"ProdStage":{
"Type":"AWS::ApiGateway::Stage",
"Condition":"CreatedProdStage",
"Properties": {
"DeploymentId":"...",
"RestApiId":"...",
"CacheClusterEnabled": true
"MethodSettings":[{
"CachingEnabled":true,
"HttpMethod":{"Ref":"GetMethod"},
"ResourcePath":"/"
}]
}
}
Then you will need to fix the given error. Your ResourcePath to match one of those listed in the error output. Those are not listed in the documentation, so it's a bit confusing what you need to use. What you currently have is set up for the root path only. If you want all paths use "/*"
APIGateWay::MethodSettings (see ResourcePath) doc:
http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html
If anyone is still arriving at this, but is NOT using cache, I have provided an example for setting throttling and logging on the whole API. I could not figure it out until I started playing around with the ResourcePath and HttpMethod, and noticed the error changing.
Please note that I used * for both path and method and USED QUOTATIONS. It will fail without quotations.
ProdStage:
Type: AWS::ApiGateway::Stage
Properties:
StageName: Prod
RestApiId: !Ref StunningDisco
DeploymentId: !Ref StunningDiscoDeployment
MethodSettings:
- ResourcePath: '/*'
HttpMethod: '*'
LoggingLevel: INFO
DataTraceEnabled: True
ThrottlingBurstLimit: '10'
ThrottlingRateLimit: '10.0'
StunningDiscoDomainMapping:
Type: 'AWS::ApiGateway::BasePathMapping'
DependsOn: ProdStage
Properties:
DomainName: !Ref StunningDiscoDomain
RestApiId: !Ref StunningDisco
Stage: !Ref ProdStage
StunningDiscoDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn: [StunningDiscoRootEndpoint, LightsInvokeEndpoint]
Properties:
RestApiId: !Ref StunningDisco
Try setting the HttpMethod to a string instead of a reference:
"MethodSettings":[{
"CachingEnabled":true,
"HttpMethod": "GET",
"ResourcePath":"/"
}]
}