AWS Cloudformation nested intrinsic function not evaluating - amazon-web-services

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.

Related

CloudFormation Select ImageId based on Instance Family

I'm trying to select an AMI dynamically based on the Instance Family. The instance family being determined from the first few letters (before the period) from the InstanceType.
I would think that the following CloudFormation snippet would work. It uses !Select and !Split to find the first few characters of the InstanceType. The Instance Family should be passed as the third argument to FindInMap. However, it fails with the error:
An error occurred (ValidationError) when calling the CreateStack operation: Template error: every Fn::FindInMap object requires three parameters, the map name, map key and the attribute for return value
AWSTemplateFormatVersion: 2010-09-09
Description: my new server
Parameters:
InstanceType:
Description: Instance Type
Type: String
AllowedValues:
- t2.micro
- t3a.small
- t3a.medium
- t4g.micro
- t4g.small
- t4g.medium
Mappings:
AmiMap:
us-east-1:
## AMD64 Instances
t2: ami-0f65ab0fd913bc7be
t3a: ami-0f65ab0fd913bc7be
## Graviton (ARM) Instances
t4g: ami-0cf2a935e8b19b29b
Resources:
LaunchConfig:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
...
ImageId: !FindInMap
- AmiMap
- "us-east-1"
- !Select [0, !Split [".", !Ref InstanceType ]]
Why isn't the !Select ... !Split line returning a string for FindInMap to recognize?
As recognized by #Paolo and #Marcin, !Select can't be used inside a !FindInMap. The full instance type (ie: t4g.small) can't be used as keys inside the Mappings section either, since they contain non alpha-numeric characters.
I feel like this is a better solution, rather than trying to split the instance type (t4g.small) to find the Instance Family, it constructs the InstanceType from an InstanceFamily + InstanceSize. That way, we can use the InstanceFamily inside the Mappings.
AWSTemplateFormatVersion: 2010-09-09
Description: My Server
Parameters:
InstanceFamily:
Description: "Instance Family ie: t4g, t3a, m5)"
Type: String
AllowedValues:
- t2
- t3
- t3a
- t4g
InstanceSize:
Description: "Instance Size (micro, small, medium, large)"
Type: String
AllowedValues:
- micro
- small
- medium
- large
Mappings:
AmiMap:
## Last Updated 2022-07-01
us-east-1:
## AMD64 Instances. Ubuntu 22.04 us-east-1
t2: ami-0f65ab0fd913bc7be
t3: ami-0f65ab0fd913bc7be
t3a: ami-0f65ab0fd913bc7be
## Graviton (ARM) Instances Ubuntu 22.04 us-east-1
t4g: ami-0cf2a935e8b19b29b
Resources:
LaunchConfig:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
AssociatePublicIpAddress: true
BlockDeviceMappings:
...
ImageId: !FindInMap
- AmiMap
- "us-east-1"
- !Ref InstanceFamily
InstanceType: !Join ['.', [!Ref InstanceFamily, !Ref InstanceSize]]
InstanceMonitoring: false
...

Passing Security Group Ids and Subnet Ids in a Clould Formation template

Parameters:
ClusterName:
Type: String
ClusterVersion:
Type: Number
AllowedValues: [1.21, 1.20, 1.19, 1.18]
RoleArnValue:
Type: String
ListOfSubnetIDs:
Description: Array of Subnet IDs
Type: List<AWS::EC2::Subnet::Id>
ListOfSecurityGroupIDs:
Description: Array of security group ids
Type: List<AWS::EC2::SecurityGroup::Id>
Resources:
EKSCluster:
Type: AWS::EKS::Cluster
Properties:
Name: !Sub ${ClusterName}
Version: !Sub ${ClusterVersion}
RoleArn: !Sub ${RoleArnValue}
ResourcesVpcConfig:
SecurityGroupIds:
- !Sub ${ListOfSecurityGroupIDs}
SubnetIds:
- !Sub ${ListOfSubnetIDs}
Above is the .yaml clouldformation template I have created so i can spin up eks cluster. Then i am using aws cli to spin up the cluster using the following command.
aws cloudformation deploy --template-file eks.yaml --stack-name cluster-test --parameter-overrides ClusterName=Dev ClusterVersion=1.21 ListOfSubnetIDs=subnet-11111d11b11b011f4,subnet-99999d237f87f11d7,subnet-222222c110c7e4be7,subnet-88888884de8d25176 ListOfSecurityGroupIDs=sg-01111111a21221 RoleArnValue=arn:aws:iam::123456546456:role/cluster-ServiceRole-WMIC72AOWSP0 --capabilities CAPABILITY_NAMED_IAM
I get the following error
An error occurred (ValidationError) when calling the CreateChangeSet operation: Template error: variable ListOfSecurityGroupIDs in Fn::Sub expression does not resolve to a string
I am not sure why. Am i using !sub in correctly? Would really appreciate input on this.
Since you want to reference the parameters you provided the template as they are, you should use the Ref function.
Here's an example of a valid template:
Parameters:
ClusterName:
Type: String
RoleArnValue:
Type: String
ListOfSubnetIDs:
Description: Array of Subnet IDs
Type: List<AWS::EC2::Subnet::Id>
ListOfSecurityGroupIDs:
Description: Array of security group ids
Type: List<AWS::EC2::SecurityGroup::Id>
Resources:
EKSCluster:
Type: AWS::EKS::Cluster
Properties:
Name: !Ref ClusterName
RoleArn: !Ref RoleArnValue
ResourcesVpcConfig:
SecurityGroupIds: !Ref ListOfSecurityGroupIDs
SubnetIds: !Ref ListOfSubnetIDs
and here's how I deployed it:
aws cloudformation deploy --template-file eks.yml --stack-name cluster-test --parameter-overrides ClusterName=Dev ListOfSubnetIDs=subnet-be0a99c4,subnet-c71046ae ListOfSecurityGroupIDs=sg-009690ac6b3bff6df,sg-009a3f1cb63943941 -RoleArnValue=...
Sub should be used when you want to perform string manipulation. Checkout the examples from the documentation.

Value of property IamInstanceProfile must be of type String

I want to reference my instance profile value from another stack.
If the Instance profile resource would be in the template, I could !Ref Name of the resource
---
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
SSMInstanceProfileStack:
Description: instance profile stack
Type: String
Default: instance-profile
Resources:
Ec2:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
ImageId: ami-077a5b1762a2dde35
IamInstanceProfile:
- Fn::ImportValue:
Fn::Sub: "${SSMInstanceProfileStack}-Suffix"
Tags:
- Key: Name
Value: Ec2SandPit
Outputs:
Ec2Output:
Description: A bare bone Ec2 instance
Value: !Ref Ec2
Export:
Name: !Sub "${AWS::StackName}-Ec2"
I am getting this error, the resource is deployed.
Value of property IamInstanceProfile must be of type String
Is there a fix for this?
IamInstanceProfile should be a string only, not a list of strings. So it should be (remove -):
IamInstanceProfile:
Fn::ImportValue:
Fn::Sub: "${SSMInstanceProfileStack}-Suffix"

how to use ImportValue in parameters?

As I knew, I can use ImportValue to reference value from another cloudformation stack in part of Resources.
NetworkInterfaces:
- GroupSet:
- Fn::ImportValue:
Fn::Sub: "${NetworkStackNameParameter}-SecurityGroupID"
AssociatePublicIpAddress: 'true'
DeviceIndex: '0'
DeleteOnTermination: 'true'
SubnetId:
Fn::ImportValue:
Fn::Sub: "${NetworkStackNameParameter}-SubnetID"
But seems this feature can't be used in Parameters
Parameters:
VPC:
Description: VPC ID
Type: String
Default:
Fn::ImportValue:
!Sub "${NetworkStackNameParameter}-VPC"
If I use above way, will get the error:
An error occurred (ValidationError) when calling the CreateChangeSet operation: Template format error: Every Default member must be a string.
Anyway to work around? because the same vpc id, subnet id, security group Id, will be used not only one place.
updates
So I have to give up:
In your AWS CloudFormation template, confirm that the Parameters section doesn't contain any intrinsic functions.
https://aws.amazon.com/premiumsupport/knowledge-center/cloudformation-template-validation/
One way of doing this is to use a condition:
Parameters:
MyValue:
Type: String
Value: ''
Conditions:
MyValueExists: !Not [ !Equals [!Ref MyValue, '']]
Resources:
Resource:
Type: AWS::Something
Properties:
Key: !If [MyValueExists, !Ref MyValue, !ImportValue 'Imported']

Value of property Parameters must be an object with String (or simple type) properties

I am trying to pass parameters to one of the nested stacks by populating the values from another nested stacks output.
And i do not want any cross-referencing (unless there is no way around it)
The idea is pretty straight forward.
RootStack
-NstdStackVPC
-NstdStackSG
-NstdStackEC2
The problem is on the last nested stack while creating EC2.
If i created the resource in the root stack directly the EC2 gets created
Description: RootStack
Parameters:
MyKeyName:
Type: AWS::EC2::KeyPair::KeyName
Default: my-test-key
EC2ImageId:
Type: AWS::EC2::Image::Id
Default: ami-0dxxxxa
Resources:
NstdStackVPC ......
NstdStackSG ......
EC2Host:
Type: AWS::EC2::Instance
Properties:
SubnetId: !GetAtt NstdStackVPC.Outputs.VPCPubSubnet
ImageId: !Ref EC2ImageId
InstanceType: t2.micro
KeyName: !Ref MyKeyName
SecurityGroupIds:
- !GetAtt NstdStackSG.Outputs.SecGrp4EC2Host
But if i try to create the EC2 as a nested stack
AWSTemplateFormatVersion: '2010-09-09'
Description: NstdStackEC2.
Parameters:
myNstdKeyName:
Type: AWS::EC2::KeyPair::KeyName
myNstdImageId:
Type: AWS::EC2::Image::Id
myNstdSecGrp:
Type: AWS::EC2::SecurityGroup::Id
myNstdEC2HostSubnet:
Type: AWS::EC2::Subnet::Id
Resources:
EC2Host:
Type: AWS::EC2::Instance
Properties:
SubnetId: !Ref myNstdEC2HostSubnet
ImageId: !Ref myNstdImageId
InstanceType: t2.micro
KeyName: !Ref myNstdKeyName
SecurityGroupIds:
- Ref myNstdSecGrp
By changing the root stack as follows
AWSTemplateFormatVersion: '2010-09-09'
Description: RootStack
Parameters:
MyKeyName:
Type: AWS::EC2::KeyPair::KeyName
Default: my-test-key
EC2ImageId:
Type: AWS::EC2::Image::Id
Default: ami-0dxxxxa
Resources:
NstdStackVPC ......
NstdStackSG ......
NstdStackEC2:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://bkt.s3.eu-central-1.amazonaws.com/NstdEC2Host.yml
Parameters:
myNstdKeyName: !Ref MyKeyName
myNstdImageId: !Ref EC2ImageId
myNstdSecGrp: !GetAtt NstdStackSG.Outputs.SecGrp4BasHost
myNstdEC2HostSubnet: !GetAtt NstdStackVPC.Outputs.VPCPubSubnet
It gives me the following error:
Value of property Parameters must be an object with String (or simple type) properties
tried removing all the parameters to try one by one. But it fails on everything.
Even for the parameters that are being referenced directly from the root stack i.e., MyKeyName, EC2ImageId
I ran into the same exact error message with a similar problem and solution. I came here and since the issue was slightly different, this question helped me get to my solution. So, not trying to hijack this question, simply hoping to provide what I found additionally useful to the next person visiting.
I was nesting a cluster template very similar to this one and OPs example. Passing Subnets as a list of strings (I believe List<AWS::Some::Type> will also work).
Subnets:
Description: Subnets of the of the cluster availaibility zone
Type: CommaDelimitedList
Default: subnet-0d..de,subnet-0e..7a,subnet-0b..24
And I'm using the above parameters to call the partial child template as follows.
ECS:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://xx.amazonaws.com/yy/zz.yaml
Parameters:
SecurityGroups: !Join [",", [!GetAtt SecurityGroups.Outputs.ECSHostSecurityGroup]]
Subnets: !Join [",", !Ref Subnets]
So, In the above example, the SecurityGroups are joined together into a list from the output of the SecurityGroup Nested Template, but the subnets are simply joined together from the comma delimited parameter. There is a knowledge-center article too, if you want more info. TA OP
Ok finally sorted this out myself.
In my NstdStackSG outputs section i was referring to the object itself.
And that is where this goes wrong.
AWSTemplateFormatVersion: 2010-09-09
Description: Security group nested stack
Resources:
MySecGrp
Type: ....
.....
....
Outputs:
MyOtptSecGrp:
#This one is working for me.
Value: !GetAtt MySecGrp.GroupId
#previously i was assigning the following value
#Value: !Re MySecGrp
And now in the RootStack
AWSTemplateFormatVersion: '2010-09-09'
Description: RootStack
Parameters:
MyKeyName:
Type: AWS::EC2::KeyPair::KeyName
Default: my-test-key
EC2ImageId:
Type: AWS::EC2::Image::Id
Default: ami-0dxxxxa
Resources:
NstdStackVPC ......
NstdStackSG ......
NstdStackEC2:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://bkt.s3.eu-central-1.amazonaws.com/NstdEC2Host.yml
Parameters:
myNstdKeyName: !Ref MyKeyName
myNstdImageId: !Ref EC2ImageId
myNstdSecGrp: !GetAtt NstdStackSG.Outputs.SecGrp4BasHost
myNstdEC2HostSubnet: !GetAtt NstdStackVPC.Outputs.VPCPubSubnet
And in my nestedEC2Stack
AWSTemplateFormatVersion: 2010-09-09
Description: NstdStackEC2
Parameters:
myNstdSecGrp:
Type: AWS::EC2::SecurityGroup::Id
myNstdEC2HostSubnet:
Type: AWS::EC2::Subnet::Id
myNstdKeyName:
Type: AWS::EC2::KeyPair::KeyName
myNstdImageId:
Type: AWS::EC2::Image::Id
Resources:
EC2Host:
Type: AWS::EC2::Instance
Properties:
SubnetId: !Ref myNstdEC2HostSubnet
ImageId: !Ref myNstdImageId
InstanceType: t2.micro
KeyName: !Ref myNstdKeyName
SecurityGroupIds:
- !Ref myNstdSecGrp
Hope this helps. (If not in solving then at least in pointing the right direction)