CloudFormation Conditions Problems - amazon-web-services

I have a CloudFormation template and I want to use a condition within the AutoScale structure, but I'm getting errors. I wonder if I'm missing something?
My condition:
Conditions:
CreateLBResources: !Equals
- !Ref LB
- true
Load Balancer in AutoScale:
LoadBalancerNames:
!If [CreateLBResources, !Ref LoadBalancer, !Ref "AWS::NoValue"]
Error:
Value of property LoadBalancerNames must be of type List of String
What do I want to do?
If I enter "true", add the name of the loadbalancer, if I enter "false" then leave it blank.
Thanks for helps.

In the LoadBalancerNames you don't need any !If condition. You can simply use Condition in ASG block.
myASG:
Condition: CreateLBResources
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
LoadBalancerNames: [<LBName>]
...
On the basis of condition CreateLBResources, it will do the job(create or do not create ASG).

I solved my problem.
LoadBalancerNames:
- !If [CreateLBResources, !Ref LoadBalancer, !Ref "AWS::NoValue"]
I forgot to add the - character.
Thanks.

Related

CloudFormation Global Resource deployment across multiple regions

I am trying to deploy this CloudFormation template across my organization that contains global resources as well as Region-specific. I have seen several methods pertaining to my issue but none seem to work as I was hoping. I pieced my script together with other StackOverflow answers that I can't find the links to right now, but most pointed to this handy link: https://garbe.io/blog/2017/07/17/cloudformation-hacks/
Here is how I have essentially pieced my script together (broken up for easier reading):
Parameters:
CommercialMaster:
Description: The Commercial Account ID for routing.
Type: String
Default: ############
GovCloudMaster:
Description: The GovCloud Account ID for routing.
Type: String
Default: ############
LambdaRoleName:
Type: String
Default: 'LambdaR53Role'
Conditions:
Commercial: !Equals [ !Ref AWS::Partition, 'aws' ]
RegionCheck: !Or [!Equals [!Ref AWS::Region, 'us-east-1'], !Equals [!Ref AWS::Region, 'us-gov-west-1']]
CreateLambdaRole: !Equals [ !Ref LambdaRoleName, 'false' ]
CreateLambdaRoleRegion: !And
- !Condition RegionCheck
- !Condition CreateLambdaRole
Resources:
LambdaPermissionsRole:
Type: "AWS::IAM::Role"
Condition: RegionCheck
Properties:
RoleName: LambdaR53Role
Description: Lambda permissions role for R53 association.
CreateRoleWaitHandle:
Condition: CreateLambdaRole
DependsOn: LambdaPermissionsRole
Type: AWS::CloudFormation::WaitConditionHandle
#added, since DependsOn: !If is not possible, trigger by WaitCondition if CreateLambdaRole is false
WaitHandle:
Type: AWS::CloudFormation::WaitConditionHandle
#added, since DependsOn: !If is not possible
WaitCondition:
Type: AWS::CloudFormation::WaitCondition
Properties:
Handle: !If [CreateLambdaRole, !Ref CreateRoleWaitHandle, !Ref WaitHandle]
Timeout: "1"
Count: 0
AssociateVPCs:
Type: 'AWS::Lambda::Function'
DependsOn: WaitCondition
Properties:
Obviously, because of the hardcoded AWS::Region this works fine when deploying out to us-east-1 for Commercial, but when deploying with a StackSet, it fails in any other region. If I remove the RegionCheck then the stack fails because it tries to create the Role in every region and realizes that it already exists (because IAM is global, obviously).
My question, the part I'm stuck on, is how I can create the global IAM role, and STILL deploy the lambda function out to each region I need it (4 regions)?
My temporary solution, which I don't like is to essentially hardcode the region on to the back of the role name, like so:
Resources:
LambdaPermissionsRole:
Type: "AWS::IAM::Role"
Condition: CreateLambdaRole
Properties:
RoleName: !Sub LambdaR53Role-${AWS::Region}
Description: Lambda permissions role for R53 association.
Any further guidance would be great, but this headache has given me the push to start examining Terraform as my way forward so things can be more consolidated.

AWS cloudformation spot instance parameters

I'm trying to add a parameter in my cloud formation stack that will allow the users to choose between on-demand and spot instances for the launch template, which will initiate the EC2 creation. This stack is designed to launch a workstation for a single user.
Currently there only seems to be one value available for the InstanceMarketType Parameter, does anyone know an alternative way of choosing the instance market type?
InstanceMarketTypeParameter:
Type: String
Default: spot
AllowedValues:
- spot
- on-demand
Description: Choose between on-demand and spot instances.
The launch template would look something like this
Ec2LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: LinuxWorkstation
LaunchTemplateData:
InstanceMarketOptions:
MarketType:
Ref: InstanceMarketTypeParameter
Any ideas are welcome!
You can make InstanceMarketOptions optional using If:
Conditions:
IsOnDemand:
!Equals [!Ref InstanceMarketTypeParameter, "on-demand"]
Resources:
Ec2LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: LinuxWorkstation
LaunchTemplateData:
InstanceMarketOptions:
!If
- IsOnDemand
- !Ref "AWS::NoValue"
- MarketType:
Ref: InstanceMarketTypeParameter

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"

Make VPC creation optional

I am trying to make VPC creation conditional in my cloudformation script. For example, if there is a VPC id provided in the parameters, then I want to create all resources in this VPC, otherwise to create a new one.
The problem starts when I want to reuse an existing VPC, but I have no way of deducing the subnets, which I need for one of my resources. So I suppose, I have to provide them as parameters. But if I provide them as parameters, in the case where I want to create a new VPC, it complains because the list of subnet ids is empty and it must be a valid one.
The error is Parameter validation failed: parameter value for parameter name VpcPrivateSubnetIds does not exist. Rollback requested by user. It is impossible to give any dummy value. Any ideas how to achieve this?
Here is my CF script:
VpcId:
Type: String
Description: Give the VPC id if you want to use an existing one. Leave empty for creating a new one.
VpcPublicSubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: List of 3 public SubnetIds for the given VPC.
VpcPrivateSubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: List of 3 private SubnetIds for the given VPC.
Conditions:
CreateVPC: !Equals [ !Ref VpcId, ""]
Resources:
(...)
Properties:
PrivateSubnetIds: !If
- CreateVPC
- !GetAtt VPCStack.Outputs.PrivateSubnets
- !Join [',', [!Select [0, !Ref VpcPrivateSubnetIds], !Select [1, !Ref VpcPrivateSubnetIds], !Select [2, !Ref VpcPrivateSubnetIds]]]
PublicSubnetIds: !If
- CreateVPC
- !GetAtt VPCStack.Outputs.PublicSubnets
- !Join [',', [!Select [0, !Ref VpcPublicSubnetIds], !Select [1, !Ref VpcPublicSubnetIds], !Select [2, !Ref VpcPublicSubnetIds]]]
One solution could be to treat the subnetId parameters as string, which can then be left as empty. (but user will have to manually type in the subnet id list when VPC is present).
In case the list is not empty (existing VPC to be used), use Cloudformation custom resource lambda to turn string (comma separated) into list & return to cloudformation for use in resource creation. So your stack would look something like
Parameters:
VpcId:
Type: String
Description: Give the VPC id if you want to use an existing one. Leave empty for creating a new one.
VpcPublicSubnetIds:
Type: String
Description: List of 3 public SubnetIds for the given VPC.
Default: ''
VpcPrivateSubnetIds:
Type: String
Description: List of 3 private SubnetIds for the given VPC.
Default: ''
Conditions:
CreateVPC: !Equals [ !Ref VpcId, ""]
CreateList: !Not [!Equals [ !Ref VpcId, ""]]
Resources:
CreateList:
Type: AWS::CloudFormation::CustomResource
Condition: CreateList
Properties:
ServiceToken:<some token>
Public: !Ref VpcPublicSubnetIds
Private: !Ref VpcPrivateSubnetIds
SomeResource:
Properties:
PrivateSubnetIds: !If
- CreateVPC
- !GetAtt VPCStack.Outputs.PrivateSubnets
- !GetAtt CreateList.PrivateSubnetIds
PublicSubnetIds: !If
- CreateVPC
- !GetAtt VPCStack.Outputs.PublicSubnets
- !GetAtt CreateList.PublicSubnetIds
Please note I have note validated this script, so you may have to make some corrections.

CloudFormation fails to find RDS Subnet Group because of lowercase

I created my RDS Subnet Group via CloudFormation referencing a parameter ProjectName
DB:
Type: AWS::RDS::DBInstance
Properties:
DBSubnetGroupName: !Ref RDSSubnetGroup
Problem now is CloudFormation says it cannot find my subnet group:
DB subnet group 'AbcDef' does not exist because its actually abcdef ... how can I resolve this?
I tried looking for a toLower function but seems like theres none?
The other option appears to be recreate the stack?
Unfortunately everything you do in CloudFormation templates is case-sensitive including property names and parameter values. You may have to recreate the stack.
As you correctly pointed out, there is no Fn::ToLower function. If you really want to achieve what you are trying to, the only way to do it as of now is create Lambda backed custom resource which basically will convert your string to lower case and return it but it is not worth doing it as there are plenty of challenges you will come across when dealing with custom resources.
I have also found that DB Subnet Groups have their name forcibly changed to lowercase when viewed in the RDS console. Very unusual behavior.
However, I have created them in CloudFormation and it has not caused the error you describe. Here are the bits from my CloudFormation template:
###########
# DB Subnet Group
###########
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Lab DB Subnet Group
DBSubnetGroupName: Lab DB Subnet Group
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
Tags:
-
Key: Name
Value: DBSubnetGroup
###########
# RDS Database
###########
RDSDatabase:
Type: AWS::RDS::DBInstance
Properties:
DBName: inventory
DBInstanceIdentifier: inventory-db
AllocatedStorage: 5
DBInstanceClass: db.t2.micro
Engine: MySQL
MasterUsername: master
MasterUserPassword: lab-password
MultiAZ: false
DBSubnetGroupName: !Ref DBSubnetGroup
VPCSecurityGroups:
- !Ref DBSecurityGroup
Tags:
-
Key: Name
Value: inventory-db
I would suggest rewriting the function_name and the name of the DBSubnetGroup to dbsubnetgroup
This will fix the issue I suppose.
Had the same issue, tried all possible , the only way to fix , was to create a new DB subnet group with the name lower :
rdssubnetgrouplower:
Type: "AWS::RDS::DBSubnetGroup"
Properties:
DBSubnetGroupDescription: "Private subnet group to keep the cluster private"
DBSubnetGroupName: rdssubnetgrouplower
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
Tags:
-
Key: Name
Value: rdssubnetgrouplower
and then in RDS definition used it :
MySQLABC:
Type: "AWS::RDS::DBCluster"
Properties:
DBSubnetGroupName: !Ref rdssubnetgrouplower
...
...
this worked and had the cluster up. in TF there is the lower function :)