Why does cloudformation give "Invalid method response 200" error, but manual deployment work? (AWS API Gateway Websocket) - aws-cloudformation

I am getting this error when I deploy a simple websocket mock route.
Execution failed due to configuration error: Output mapping refers to an invalid method response: 200
First of all, I'm a little confused about what method response means, as in Websocket API, the terminology used is Route Response and Integration Response. I'm guessing this is referring to the Route Response.
The resources I have are:
Websocket API
Stage
Deployment
$connect route
$connect integration with mock (default maps to {"statusCode": 200})
$connect integration response (just passes the integration through)
$connect route response
The funny part is: to fix this, all I have to do is go to the console and click deploy API. I don't have to change any configuration. But that is not a good solution for me, as I want to run this on a CI/CD pipeline.
I'm guessing the problem is with the Route Response, as that is not configurable from the console. So something must be going on behind the scenes during console deployment, which I am missing during cloudformation deployment. Any ideas how to solve this?
Here's my Cloudformation Template.
Resources:
testWsApiBackendWsApi40DF2EE8:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: testWsApi
ProtocolType: WEBSOCKET
RouteSelectionExpression: $request.body.action
testWsApiApiDeployment423ACBB9:
Type: AWS::ApiGatewayV2::Deployment
Properties:
ApiId:
Fn::GetAtt:
- testWsApiBackendWsApi40DF2EE8
- ApiId
DependsOn:
- MockWithAuthAwsStackwsMockRoute04DB7577
testWsApiApiStageF40CAAE0:
Type: AWS::ApiGatewayV2::Stage
Properties:
ApiId:
Fn::GetAtt:
- testWsApiBackendWsApi40DF2EE8
- ApiId
StageName: production
DeploymentId:
Fn::GetAtt:
- testWsApiApiDeployment423ACBB9
- DeploymentId
MockWithAuthAwsStackwsMockRoute04DB7577:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId:
Fn::GetAtt:
- testWsApiBackendWsApi40DF2EE8
- ApiId
RouteKey: $connect
Target:
Fn::Join:
- ""
- - integrations/
- Ref: MockWithAuthAwsStackwsMockIntegration36E7A460
MockWithAuthAwsStackwsMockIntegration36E7A460:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId:
Fn::GetAtt:
- testWsApiBackendWsApi40DF2EE8
- ApiId
IntegrationType: MOCK
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
$default: '{"statusCode":200}'
TemplateSelectionExpression: \$default
MockWithAuthAwsStackwsMockRouteResponseAEE0B8ED:
Type: AWS::ApiGatewayV2::RouteResponse
Properties:
ApiId:
Fn::GetAtt:
- testWsApiBackendWsApi40DF2EE8
- ApiId
RouteId:
Ref: MockWithAuthAwsStackwsMockRoute04DB7577
RouteResponseKey: $default
MockWithAuthAwsStackwsMockIntegrationResponse85928773:
Type: AWS::ApiGatewayV2::IntegrationResponse
Properties:
ApiId:
Fn::GetAtt:
- testWsApiBackendWsApi40DF2EE8
- ApiId
IntegrationId:
Ref: MockWithAuthAwsStackwsMockIntegration36E7A460
IntegrationResponseKey: $default
TemplateSelectionExpression: \$default
P.S I am actually using AWS CDK. The above template is the result of cdk synth. Let me know if you want to see the CDK code.

The reason why manual deployment using console works, while using cloudformation causes errors occasionally, is due to the order in which these resources are created. In the console, this is the order followed:
Routes, Integrations and Responses are created
They are associated with a stage
They are deployed to the specified stage
When using cloudformation, the order of creation of resources gets mixed up, resulting in them not being wired up properly. It seems that you have wired up the deployment to depend on the route being created first. You also need to make sure that the stage is created before the deployment. For this you could add an explicit DependsOn attribute, or an implicit reference to the stage within the deployment; perhaps in the stageName attribute as !Ref StageResource.
Or you could save the trouble and just add an autoDeploy: true to your stage, which will take care of the linking and order on its own.

Related

For ECS deployment group, ec2TagFilters can not be specified

When creating a AWS::CodeDeploy::DeploymentGroup in CloudFormation, I get this error, even though I have no EC2TagFilters in my script:
For ECS deployment group, ec2TagFilters can not be specified (Service: AmazonCodeDeploy; Status Code: 400; Error Code: InvalidEC2TagException; Request ID: af5c3f68-6033-4df0-9f6f-ecd064ad6b7b; Proxy: null)
CodeDeploymentGroupDev:
Type: AWS::CodeDeploy::DeploymentGroup
DependsOn:
- CodeDeployApplication
Properties:
ApplicationName: !Ref ApplicationName
DeploymentConfigName: CodeDeployDefault.AllAtOnce
DeploymentGroupName: !Sub "${ApplicationName}-Dev"
DeploymentStyle:
DeploymentType: IN_PLACE
OnPremisesTagSet:
OnPremisesTagSetList:
- OnPremisesTagGroup:
- Key: !Ref OnPremisesTagKey
Type: KEY_AND_VALUE
Value: !Ref OnPremisesTagValue
ServiceRoleArn: !Sub 'arn:aws:iam::${AWS::AccountId}:role/CodeDeployServiceRole'
Is AWS::CodeDeploy::DeploymentGroup not implemented correctly in CloudFormation?
I can see that you are using OnPremisesTagSet which mean you should already register on-premises instances with CodeDeploy, see https://docs.aws.amazon.com/codedeploy/latest/userguide/instances-on-premises.html.
If you are using Ec2 then you need to use Ec2TagSet instead of OnPremisesTagSet.
This occurred because my corresponding AWS::CodeDeploy::Application was not configured correctly (not in original post, but shown below). I was attempting an onprem deploy using a registered agent. I had ECS for the compute platform, but it should have been Server:
CodeDeployApplication:
Type: AWS::CodeDeploy::Application
Properties:
ApplicationName: !Ref ApplicationName
ComputePlatform: Server // Allowed values: ECS | Lambda | Server
It worked, after I fixed that.

Get attribute of EC2 created via LaunchConfiguration

I would like to get the PrivateIP attribute of EC2s that i create via LaunchConfiguration.
I need that attribute so that i can assign a type A dns record to the instance for other purposes.
Here is my code:
Resources:
webLaunchConfig:
Type: 'AWS::AutoScaling::LaunchConfiguration'
Properties:
ImageId: !Ref webEc2AMI
InstanceType: !Ref ec2WebInstanceType
SecurityGroups: !Ref webEc2SG
UserData:
'Fn::Base64': !Sub >
#!/bin/bash -xe
apt update -y
dnsWebServerName:
Type: 'AWS::Route53::RecordSet'
Properties:
HostedZoneId: !Ref hostedZoneId
Comment: DNS name for my db server.
Name: !Ref dnsWebServerNamePar
Type: A
TTL: '900'
ResourceRecords:
- !GetAtt webLaunchConfig.PrivateIp
... and when i try to launch it i get this error:
Template contains errors.: Template error: resource webLaunchConfig
does not support attribute type PrivateIp in Fn::GetAtt
... indicating me that what i am trying to do is not supported. Though there must be a way to achieve this.
Do you know how to do it? Or a workaround for this?
Sadly you can't do this. AWS::AutoScaling::LaunchConfiguration is only a blueprint of an instance to be launched. Thus it does not provide information about instance PrivateIp. The get the PrivateIp you have to actually launch the instance.
To do so you have to use AWS::EC2::Instance. However AWS::EC2::Instance does not support launching from ``AWS::AutoScaling::LaunchConfiguration. So either you have to change your LaunchConfigurationintoLaunchTemplateor just create instance directly usingAWS::EC2::Instance` rather then any templates.

Serverless CloudFormation template error instance of Fn::GetAtt references undefined resource

I'm trying to setup a new repo and I keep getting the error
The CloudFormation template is invalid: Template error: instance of Fn::GetAtt
references undefined resource uatLambdaRole
in my uat stage, however the dev stage with the exact same format works fine.
I have a resource file for each of these environments.
dev
devLambdaRole:
Type: AWS::IAM::Role
Properties:
RoleName: dev-lambda-role # The name of the role to be created in aws
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AWSLambdaFullAccess
#Documentation states the below policy is included automatically when you add VPC configuration but it is currently bugged.
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
uat
uatLambdaRole:
Type: AWS::IAM::Role
Properties:
RoleName: uat-lambda-role # The name of the role to be created in aws
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AWSLambdaFullAccess
#Documentation states the below policy is included automatically when you add VPC configuration but it is currently bugged.
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
In my serverless.yml my role is defined as
role: ${self:custom.stage}LambdaRole
and the stage is set as
custom:
stage: ${opt:stage, self:provider.stage}
Running serverless deploy --stage dev --verbose succeeds, but running serverless deploy --stage uat --verbose fails with the error. Can anyone see what I'm doing wrong? The uat resource was copied directly from the dev one with only the stage name change.
Here is a screenshot of the directory the resource files are in
I had the same issue, eventually I discovered that my SQS queue name wasn't the same in all 3 places. The following 3 places that the SQS name should match are shown below:
...
functions:
mylambda:
handler: sqsHandler.handler
events:
- sqs:
arn:
Fn::GetAtt:
- mySqsName # <= Make sure that these match
- Arn
resources:
Resources:
mySqsName: # <= Make sure that these match
Type: "AWS::SQS::Queue"
Properties:
QueueName: "mySqsName" # <= Make sure that these match
FifoQueue: true
Ended up here with the same error message. My issue ended up being that I got the "resource" and "Resource" keys in serverless.yml backwards.
Correct:
resources: # <-- lowercase "r" first
Resources: # <-- uppercase "R" second
LambdaRole:
Type: AWS::IAM::Role
Properties:
...
🤦‍♂️
I missed copying a key part of my config here, the actual reference to my Resources file
resources:
Resources: ${file(./serverless-resources/${self:provider.stage}-resources.yml)}
The issue was that I had copied this from a guide and had accientally used self:provider.stage rather than self:custom.stage. When I changed this, it could then deploy.
Indentation Issue
In general, when YAML isn't working I start by checking the indentation.
I hit this issue in my case one of my resources was indented too much, therefore, putting the resource in the wrong node/object. The resources should be two indents in as they're in node resources sub-node Resources
For more info on this see yaml docs

IAM nested stack fails to complete due to undefined resource policies

I have created a nested IAM stack, which constists of 3 templates:
- iam-policies
- iam-roles
-iam user/groups
the masterstack template looks like this:
Resources:
Policies:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/xxx/iam/iam_policies.yaml
UserGroups:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/xxx/iam/iam_user_groups.yaml
Roles:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/xxx/iam/iam_roles.yaml
The policy ARNs are exported via Outputs section like:
Outputs:
StackName:
Description: Name of the Stack
Value: !Ref AWS::StackName
CodeBuildServiceRolePolicy:
Description: ARN of the managed policy
Value: !Ref CodeBuildServiceRolePolicy
in the Role template the policies ARNs are imported like
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${EnvironmentName}-CodeBuildRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service:
- codebuild.amazonaws.com
Path: /
ManagedPolicyArns:
- !GetAtt
- Policies
- Outputs.CodeBuildServiceRolePolicy
But when I try create the stack, it fails saying the Roles stack cannot be created because
Template error: instance of Fn::GetAtt references undefined resource Policies
How can I force the creation of the policies first so the second and third template can use the policies to create roles and user/ groups? Or is the issue elsewhere?
merci A
Your question,
How can I force the creation of the policies first so the second and
third template can use the policies to create roles and user/ groups?
Or is the issue elsewhere?
You can use "DependsOn" attribute. It automatically determines which resources in a template can be parallelized and which have dependencies that require other operations to finish first. You can use DependsOn to explicitly specify dependencies, which overrides the default parallelism and directs CloudFormation to operate on those resources in a specified order.
In your case second and third template DependsOn Policies
More details : DependsOn
The reason on why you aren't able to access the outputs is that, you haven't exposed the outputs for other stacks.
Update your Outputs with the data you want to export. Ref - Outputs for the same.
Then, use the function Fn::ImportValue in the dependent stacks to consume the required data. Ref - ImportValue for the same.
Hope this helps.

Add AWS::Route53::RecordSet DnsRecord to a serverless Cloudfront Distribution

I found this on how to associate a route53 dns record with a S3 bucket in a serverless.yml file.
I've tried to adapt that to the case of deploying a cloudfront distrib
DnsRecord:
Type: "AWS::Route53::RecordSet"
Properties:
AliasTarget:
DNSName: <cloudfrontdistribution id>
HostedZoneId: Z21DNDUVLTQW6Q
HostedZoneName: ${self:custom.appFQDN}.
Name:
Ref: WebAppCloudFrontDistribution
Type: 'CNAME'
but am struggling with how to get the distribution id as a ref rather than a fixed string.
How would I do this?
To set up an AliasTarget, you actually just need to provide the CloudFront DNS name for the DNSName parameter, not the distribution ID. You can do this with:
!GetAtt WebAppCloudFrontDistribution.DomainName
I'm assuming that WebAppCloudFrontDistribution is the logical ID of an AWS::CloudFront::Distribution resource in your template and not a parameter. If this is actually a parameter, just set the value of the parameter to the DNS name listed for the distribution in the AWS console dashboard for CloudFront.
There are some other things you'll need to fix in your template:
HostedZoneName should be the name of the Route53 hosted zone, not the FQDN you want to use. Personally, I prefer to use the HostedZoneId property for AWS::Route53::RecordSet resources instead since it's clearer what the meaning of this property is, but to each their own. (Note: HostedZoneId property for the AWS::Route53::RecordSet resource should be the HostedZoneId for YOUR hosted zone, not the same value as the AliasTarget HostedZoneId.)
Name should be the DNS name that you want to be a CNAME for the CloudFront distribution resource.
I know it's a bit weird, but with alias targets, you have to set the type to either "A" (for IPv4) or "AAAA" (IPv6). I recommend doing both - you can do this by creating a duplicate of your AWS::Route53::RecordSet resource but set type to "AAAA" instead of "A".
Finally, note that in order for this to work, you will also need to make sure to add the FQDN as an alternate name for the CloudFront distribution resource - you can set this using the "Aliases" property of the "DistributionConfig" property of the distribution resource in your template, or by configuring this manually for the distribution settings in the AWS console if you're not creating the resource in this template.
I struggled to create a AWS::Route53::RecordSet with CloudFormation producing unspecific, unhelpful error messages of the type "The resource failed to create". The key for me was to use HostedZoneId rather than HostedZoneName to specify the parent "hosted zone". This is what I ended up with:
NaaaaaComDNSEntry:
Type: 'AWS::Route53::RecordSet'
DependsOn: NaaaaaComCloudFront
Properties:
AliasTarget:
DNSName: !GetAtt NaaaaaComCloudFront.DomainName
# For CloudFront, HostedZoneId is always Z2FDTNDATAQYW2, see:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html#cfn-route53-aliastarget-hostedzoneid
HostedZoneId: Z2FDTNDATAQYW2
# HostedZoneId is for ID for 'naaaaa.com.'; In theory its valid to use `HostedZoneName` OR `HostedZoneId`
# but in practice the recordset always failed to create if I used `HostedZoneName`
HostedZoneId: ZABCDEFGHIJK5M
Name: 'www.naaaaa.com.'
Type: 'A'
This is what my working config looks like in serverless templates:
DnsRecord:
Type: "AWS::Route53::RecordSet"
Properties:
AliasTarget:
DNSName:
Fn::GetAtt:
- CloudFrontDistribution
- DomainName
# Looks like it is always the same for CloudFront distribs.
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html
# https://docs.aws.amazon.com/general/latest/gr/rande.html#cf_region
HostedZoneId: ${self:custom.zoneId}
HostedZoneName: ${self:custom.secondLevelDomain}.
Name: ${self:custom.appFQDN}
Type: 'A'
And
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
...
Aliases:
- ${self:custom.appFQDN}
Also courtesy of an example by Tom McLaughlin:
https://github.com/ServerlessOpsIO/serverless-zombo.com/blob/master/serverless.yml