CloudFormation: How to use AWS::AccountId in Mappings? - amazon-web-services

I have a mapping that looks like this:
Mappings:
AccountToParams:
aws-1234567890:
sshSecurityGroup: sg-abcedf12
And I'd like to retrieve my variables by AccountId, but this doesn't get past the "validation" step
SecurityGroups:
- !FindInMap [AccountToParams, !Sub "aws-${AWS::AccountId}", sshSecurityGroup]
Error is
16/08/2017, 16:36:18 - Template contains errors.: Template error:
every Fn::FindInMap object requires three parameters,
the map name, map key and the attribute for return value
The goal is to have some configuration driven by the account (hence environment) this is run under. And I can't seem to use the accountId as the key in the mapping, otherwise AWS isn't happy because it doesn't contain alphanumeric chars

Change the map to:
Mappings:
AccountToParams:
"1234567890":
sshSecurityGroup: sg-abcedf12
and use !Ref instead of !Sub:
SecurityGroupIds:
- !FindInMap [AccountToParams, !Ref "AWS::AccountId", sshSecurityGroup]
Use FN::Join to prepend "aws" string to account ID if that's required further down the stack.

This works
Mappings:
AccountToParams:
"123456789012":
sshSecurityGroup: sg-abcdef12
Resources:
SecurityGroups: !FindInMap
- AccountToParams
- !Ref 'AWS::AccountId'
- sshSecurityGroup

Related

AWS Parameter Must Have Values Error (value exists)

I have the following parameter in my CloudFormation script:
CloudFormationURL:
Type: String
Description: S3 URL for nested CloudFormation templates
Default: ""
This parameter covers the CloudFormation scripts in a nested folder of my deployment config.
I use it with a resource like this:
CloudWatchDashboardStack:
Type: "AWS::CloudFormation::Stack"
Properties:
TemplateURL: !Sub "${CloudFormationURL}/cloudwatch-dashboard.cfn.yaml"
Parameters:
AppName: !Ref AppName
DeployPhase: !Ref DeployPhase
DeveloperPrefix: !Ref DeveloperPrefix
Environment: !Ref Environment
Which works fine, and has worked for months.
I needed to add another resource, so I added this:
BatchDNSResources:
Type: "AWS::CloudFormation::Stack"
Properties:
Parameters:
AppName: !Ref AppName
Environment: !Ref Environment
DeveloperPrefix: !Ref DeveloperPrefix
DeployPhase: !Ref DeployPhase
AppVersion: !Ref AppVersion
SharedBucketName: !Ref SharedBucketName
S3Version: !Ref S3Version
HostedZone: !Ref HostedZone
VPCStackName: !FindInMap
- EnvironmentMap
- !Ref Environment
- VpcStackName
Company: !Ref Company
CostCenter: !Ref CostCenter
Team: !Ref Team
TemplateURL: !Sub "${CloudFormationURL}/batch-dns.cfn.yaml"
CloudFormation throws this error and then fails:
Parameters: [CloudFormationURL] must have values
Checking the changeset for the stack I can see the following value for the CloudFormationURL:
s3://application-shared-dev/application-name/qa/cf/nested/KShyDj205UK8mz6W_XUA5TnEF8nqPWHS
Checking the application predeploy logs I can see:
upload: deploy/cloudformation/templates/nested/batch-dns.cfn.yaml to s3://application-shared-dev/application-name/qa/cf/nested/KShyDj205UK8mz6W_XUA5TnEF8nqPWHS/batch-dns.cfn.yaml
And I can see the file in the S3 bucket.
If I remove BatchDNSResource the stack completes successfully.
What the heck am I missing here?
Sometimes, the smallest things will get you.
I had copied the Parameters from the master CloudFormation script, including this one, into the nested script:
CloudFormationURL:
Type: String
Description: S3 URL for nested CloudFormation templates
Default: ""
If you look closely, you will see that I did not pass the parameter into the nested script when calling the resource:
BatchDNSResources:
Type: "AWS::CloudFormation::Stack"
Properties:
Parameters:
AppName: !Ref AppName
Environment: !Ref Environment
DeveloperPrefix: !Ref DeveloperPrefix
DeployPhase: !Ref DeployPhase
AppVersion: !Ref AppVersion
SharedBucketName: !Ref SharedBucketName
S3Version: !Ref S3Version
HostedZone: !Ref HostedZone
VPCStackName: !FindInMap
- EnvironmentMap
- !Ref Environment
- VpcStackName
Company: !Ref Company
CostCenter: !Ref CostCenter
Team: !Ref Team
TemplateURL: !Sub "${CloudFormationURL}/batch-dns.cfn.yaml"
Because the CloudFormation console was saying the issue was with the BatchDNSResources I kept looking at the master script for the problem and missing the reference in the other script. There are two ways to solve this problem:
Keep CloudFormationURL as a parameter in the nested script (if you need it for some reason) and pass the value from the master script.
Remove the parameter from the nested script (if it is not needed)
Sometimes just asking for an extra set of eyeballs and getting a little rest will help you to find the issues. I want to leave this question/answer in place here because when I was searching for the error here and elsewhere no one ever mentioned (probably out of embarrassment) that the mistake is simply overlooking something like this. I hope this answer prompts others to check everything when they run across this type of error.

How to create list of resource ARNS using a CommaDelimitedList parameter?

I am trying to create a Cloudwatch event rule which will use multiple Codepipelines as source and triggers target.
Parameters:
SourcePipeline:
Description: Name of Source codepipeline
Type: CommaDelimitedList
Default: 'test3, test4'
Resources:
PipelineTrigger:
Type: 'AWS::Events::Rule'
Properties:
EventPattern:
source:
- aws.codepipeline
resources: !Split
- ','
- !Sub
- 'arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${pipeline}'
- pipeline: !Join
- ',arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:'
- !Ref SourcePipeline
Expecting resources as below:
"resources": ["arn:aws:codepipeline:us-east-1:123:test3","arn:aws:codepipeline:us-east-1:123:test4"],
Any idea how to pass the list of names as a parameter?
#FYI Reference i am following Using Lists of ARNs
First, it should be !Ref SourcePipelines. Second you forgot about comma. you can't do this the way you want. This is because, the first parameter to join must be literal string, not any CloudFormation expression or function. So you have to hardcode your account id and region:
resources: !Split
- ','
- !Sub
- 'arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${pipeline}'
- pipeline: !Join
- ',arn:aws:codepipeline:us-east-1:12312321:'
- !Ref SourcePipelines

How to add conditions in Parameters section in AWS CloudFormation?

So what I am trying to do is, I defined a Parameter called EnvType with allowed values test or production.
What should happen is when the user selects one of these environments test or production, lets say test
then he should be able to select another parameter called InstanceType in which allowed values would be all the 't' type instances in the drop-down list while creating the stack.
If the user selects production as the EnvType then the allowed values under the same parameter called InstanceType must be all instances types except 't' type for eg ('m' type).
And the same must apply for rds as well. Let's say user chooses EnvType as test then allowed values under parameter called DBInstanceType must be 'db.t' type instances otherwise 'db.r' type instances.
Parameters
Parameters:
EnvType:
Default: test
Type: String
AllowedValues:
- production
- test
InstanceType:
Type: String
AllowedValues: !FindInMap
- InstanceTypeMap
- !Ref EnvType
- instanceType
DBInstanceType:
Type: String
AllowedValues: !FindInMap
- InstanceTypeMap
- !Ref EnvType
- dbinstanceType
Mapping
InstanceTypeMap:
production:
instanceType: [m1.small, m1.medium, m1.large, m1.xlarge, m2.xlarge, m2.2xlarge, m2.4xlarge]
dbinstancetype: [db.r5.large, db.r5.xlarge]
test:
instanceType: [t1.micro, t2.nano, t2.micro, t2.small, t2.medium, t2.large]
dbinstancetype: [db.t2.small, db.t2.medium, db.t3.small]
Resources
Resources:
WebServer:
Type: 'AWS::EC2::Instance'
Properties:
InstanceType: !Ref InstanceType
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceClass: !FindInMap
- MyEnvironmentMap
- !Ref EnvType
- dbinstanceType
Well i know the template is not valid, and is prone to errors in allowed values in InstanceType and DBInstanceType parameters but i am seeking an alternative of doing this.
Please help!!
Simply, such functionality is not possible in CloudFormation (CFN). Sadly, there is no alternative in CFN for that. You would have to develop a custom solution for deployment of such templates.

AWS Cloudformation - use AWS::NoValue within Fn::Sub

I am using cloudformation and I want to be able to use the pseudo value
AWS::NoValue within the Fn::Sub like this:
!Sub ["ATL_DATASET_URL=${DatasetURL}",
DatasetURL: !If [IsURLProvided,
!Ref BitbucketDatasetURL,
!Ref "AWS::NoValue"]]
My Template passes validation but does not deploy. Here is the error message I get when I click Create Stack.
Template error: every value of the context object of every Fn::Sub object must be a string or a function that returns a string
If you want to skip setting a value for DatasetURL, make the !If to return an empty string '' when the condition evaluates to false instead of AWS::NoValue.
Returning AWS::NoValue when false, removes the mapping for DatasetURL.
The alternative to #franklinsijo is to swap the If and Sub statements if you want to actually remove the property (e.g. YourPropertyName) if BitbucketDatasetURL is not given.
YourPropertyName: !If
- IsURLProvided
- !Sub ["ATL_DATASET_URL=${DatasetURL}", DatasetURL: !Ref BitbucketDatasetURL]
- !Ref "AWS::NoValue"
Or shorter
YourPropertyName: !If
- IsURLProvided
- !Sub "ATL_DATASET_URL=${BitbucketDatasetURL}"
- !Ref "AWS::NoValue"

AWS Cloudformation nested intrinsic function not evaluating

This is part of the cloudformation template I am writing and getting errors using the Fn::FindInMap function:
Parameters:
VpcStackName:
Description: >-
Name of an active CloudFormation VPC stack that contains the networking
resources, such as the subnet and security group, that will be used in
this stack.
Type: String
MinLength: 1
MaxLength: 255
AllowedPattern: '^[a-zA-Z][-a-zA-Z0-9]*$'
Default: wordpress-dev-vpc
Mappings:
Instance:
development:
AllocatedStorage: 20
DBInstanceClass: db.t2.micro
production:
AllocatedStorage: 25
DBInstanceClass: db.m3.medium
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
DeletionPolicy: Snapshot
Properties:
Engine: MariaDB
StorageType: gp2
MasterUsername: !Ref MasterUsername
MasterUserPassword: !Ref MasterUserPassword
AllocatedStorage:
Fn::FindInMap:
- Instance
- Fn::ImportValue:
Fn::Sub: '${VpcStackName}-Environment'
- AllocatedStorage
DBInstanceClass:
Fn::FindInMap:
- Instance
- Fn::ImportValue:
Fn::Sub: '${VpcStackName}-Environment'
- DBInstanceClass
In another stack, I am exporting ${VpcStackName}-Environment like this:
Outputs:
Environment:
Description: Environment type of this stack
Value: !Ref Environment
Export:
Name: !Sub '${AWS::StackName}-Environment'
When trying to use the Fn::FindInMap function, I get this error:
An error occurred (ValidationError) when calling the ValidateTemplate operation: Template error: every Fn::FindInMap object requires three parameters, the map name, map key and the attribute for return value
Any advice?
Based on the documentation at https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-findinmap.html , the supported functions inside a Fn::FindInMap function are Fn::FindInMap and Ref. So is there another way to do it? For example, storing the value of Fn::ImportValue: !Sub '${VpcStackName}-Environment' in a temporary variable?
According to this document, the Fn::FindInMap function is only usable with these:
Fn::FindInMap
Ref
So Fn::ImportValue and Fn::Sub won't be evaluated.