CloudFormation Select ImageId based on Instance Family - amazon-web-services

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
...

Related

Windows spot instance with persistence request using cloudformation

When I try to launch windows server using this template, I get an error:
Property validation failure: [Value of property {/LaunchTemplateData} does not match type {Object}]
I used this template:
Parameters:
1InstanceType:
Type: String
Default: t2.small
AllowedValues:
- t2.small
- m3.medium
- m3.xlarge
- i3.xlarge
2SecurityGroup:
Type: 'AWS::EC2::SecurityGroup::Id'
3KeyName:
Type: 'AWS::EC2::KeyPair::KeyName'
4LatestAmiId:
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: /aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base
Resources:
Ec2LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: WindowsDesktop
LaunchTemplateData:
- ImageId: !Ref 4LatestAmiId
InstanceType: !Ref 1InstanceType
SecurityGroups:
- GroupId: !Ref 2SecurityGroup
KeyName: !Ref 3KeyName
InstanceMarketOptions:
MarketType: spot
SpotOptions:
SpotInstanceType: persistent
InstanceInterruptionBehavior: stop
Similar code works for linux servers. It seems that Windows spot instance (or template) with persistence request can not be created using cloudformation.
The error means that your LaunchTemplateData is not an object, but it is a list in your case. This is because extra - before ImageId. So it should be:
Resources:
Ec2LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: WindowsDesktop
LaunchTemplateData:
ImageId: !Ref 4LatestAmiId
InstanceType: !Ref 1InstanceType
SecurityGroups:
- GroupId: !Ref 2SecurityGroup
KeyName: !Ref 3KeyName
InstanceMarketOptions:
MarketType: spot
SpotOptions:
SpotInstanceType: persistent
InstanceInterruptionBehavior: stop

Review needed to correct this ElastiCache CloudFormation Template

This template is used to create a ElastiCache- Redis cluster.
Its showing me errors like - 1 validation error detected: Value '[AWS::ElastiCache::CacheCluster, AWS::EC2::SecurityGroup::Id]' at 'typeNameList' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 204, Member must have length greater than or equal to 10, Member must satisfy regular expression pattern: [A-Za-z0-9]{2,64}::[A-Za-z0-9]{2,64}::[A-Za-z0-9]{2,64}(::MODULE){0,1}].
Wanted to know if the parameters are declared rightly or not.
AWSTemplateFormatVersion: 2010-09-09
Description: Create ElastiCache and related resources
Parameters:
VPC:
Description: VPC
Type: AWS::EC2::VPC::Id
Subnet:
Description: Subnet
Type: AWS::EC2::Subnet::Id
ClusterName:
Description: Custom name of the cluster. Auto generated if you
don't supply your own.
Type: String
CacheNodeType:
Description: Cache node instance class, e.g. cache.t2.micro.
Type: String
Default: cache.t2.micro
ConstraintDescription: Node instance class not supported
AllowedValues:
- cache.t2.micro
- cache.t2.small
- cache.t2.medium
- cache.m4.large
- cache.m4.xlarge
- cache.m4.2xlarge
- cache.m4.4xlarge
- cache.m4.10xlarge
- cache.r4.large
- cache.r4.xlarge
- cache.r4.2xlarge
- cache.r4.4xlarge
- cache.r4.8xlarge
- cache.r4.16xlarge
CacheEngine:
Description: The underlying cache engine, either Redis or
Memcached
Type: String
Default: redis
ConstraintDescription: Node instance class not supported
AllowedValues:
- redis
- memcached
CacheNodeCount:
Description: Number of nodes in the cluster. Only used with
memcached engine, for redis this value will be set to 1.
Type: Number
MinValue: 1
MaxValue: 15
ConstraintDescription: Node count must be between 1 and 15
Default: 1
AutoMinorVersionUpgrade:
Description: Whether or not minor version upgrades to the cache
engine should be applied automatically during the maintenance
window.
Type: String
Default: true
AllowedValues:
- true
- false
Resources:
SecurityGroup:
Type: AWS::EC2::SecurityGroup::Id
Properties:
GroupDescription: ElastiCache Security Group
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 6379
ToPort: 6379
- IpProtocol: tcp
FromPort: 11211
ToPort: 11211
Tags:
-
Key: Name
Value: "App-SG"
ElastiCacheCluster:
Type: AWS::ElastiCache::CacheCluster
Properties:
AutoMinorVersionUpgrade: !Ref AutoMinorVersionUpgrade
Engine: !Ref CacheEngine
CacheNodeType: !Ref CacheNodeType
ClusterName : !Ref ClusterName
NumCacheNodes: !Ref CacheNodeCount
CacheSubnetGroupName: !Ref Subnet
VpcSecurityGroupIds: !Ref SecurityGroup
Tags:
- Key: Name
Value: ElastiCache-Redis
Also it will be helpful to review the entire template to avoid more errors.Main issues seem to appear from resource section.
This question is old and I'm sure OP found the resolution but for anyone else who may come across this question - I believe the issue is that ElastiCacheCluster.VpcSecurityGroupIds must be a list, whereas !Ref SecurityGroup will simply return the ID of the security group as a string.
In addition, since both resources are created by the same template, we should make sure that the security group will be created first, otherwise referencing it will cause an error when the Elasticache cluster is created.
Here's the ElastiCache::CacheCluster resource with a list as the value of VpcSecurityGroupIds and a dependency on the security group.
ElastiCacheCluster:
Type: AWS::ElastiCache::CacheCluster
Properties:
AutoMinorVersionUpgrade: !Ref AutoMinorVersionUpgrade
Engine: !Ref CacheEngine
CacheNodeType: !Ref CacheNodeType
ClusterName : !Ref ClusterName
NumCacheNodes: !Ref CacheNodeCount
CacheSubnetGroupName: !Ref Subnet
VpcSecurityGroupIds:
- !Ref SecurityGroup
Tags:
- Key: Name
Value: ElastiCache-Redis
DependsOn: SecurityGroup
I had the same error messages and landed here.
Turns out that in my case, the problem was an incorrect type.
I had AWS::IAM:Role instead of AWS::IAM::Role (1 : vs 2)

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.

Using Conditions and Parameters in CloudFormation

I am trying to create a condition based on an optional parameter. The option is whether to run some additional installation from my userData script for an EC2 deployment.
The parameters and conditions look like this:
Parameters:
EnvType:
Description: Option to install New Relic Infrastructure.
Default: apm
Type: String
AllowedValues:
- apm
- +infra
Then my EC2 Resource with conditional startup scripts
Resources:
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
ImageId: ami-9c9443e3 #Amazon Linux AMI in Tokyo
KeyName: tokyocloudformation
IamInstanceProfile: 'S3EC2'
SecurityGroupIds:
- !Ref myNewSecurityGroup
UserData:
Condition: apmOnly
Fn::Base64:
|
#!/bin/bash
installstuff
Condition: addInfrastructureAgent
Fn::Base64:
|
#!/bin/bash
installstuff
installsomeotherstuff
The error message I'm getting is:
Template validation error: Template format error: Unresolved dependencies [EnvTyp]. Cannot reference resources in the Conditions block of the template
I get what the error is saying I believe, but it doesn't seem to fit with the examples given by AWS.
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html .
This AWS example clearly uses a !Ref in the Conditions block.
EnvType:
Description: Environment type.
Default: test
Type: String
AllowedValues:
- prod
- test
ConstraintDescription: must specify prod or test.
Conditions:
CreateProdResources: !Equals [ !Ref EnvType, prod ]
Can someone provide some feedback on how to implement this conditional or why this error message is being thrown for this implementation?
According to the docs, Conditions should be used at the top level of the resource you want to conditionally create.
Putting a Condition inside the Instance UserData section isn't supported.
To use Conditions in your situation, you'd want separate Resources conditionally created based on the Parameter.
Resources:
Ec2InstanceAPMOnly:
Type: AWS::EC2::Instance
Condition: apmOnly
Properties:
InstanceType: t2.micro
ImageId: ami-9c9443e3 #Amazon Linux AMI in Tokyo
KeyName: tokyocloudformation
IamInstanceProfile: 'S3EC2'
SecurityGroupIds:
- !Ref myNewSecurityGroup
UserData:
Fn::Base64:
!Sub |
#!/bin/bash
installstuff
Ec2InstanceWithInfrastructureAgent:
Type: AWS::EC2::Instance
Condition: addInfrastructureAgent
Properties:
InstanceType: t2.micro
ImageId: ami-9c9443e3 #Amazon Linux AMI in Tokyo
KeyName: tokyocloudformation
IamInstanceProfile: 'S3EC2'
SecurityGroupIds:
- !Ref myNewSecurityGroup
UserData:
Fn::Base64:
!Sub |
#!/bin/bash
installstuff
installsomeotherstuff
You can use Conditions in userData as well. i followed this post https://www.singlestoneconsulting.com/blog/cloudformation-mapping-and-conditionals-making-your-templates-more-universal/ and it worked perfactlly

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.