I'm trying to make AutoScalingGroup names on cloud formation templates dynamic. I was thinking if this is possible via parameters, or any other way?
"DynamicASGName": {
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
...properties here...
}
}
CloudFormation uses two sets of names: the logical resource name, to identify a resource within the stack, and the physical name which uniquely identifies it across the whole region.
CloudFormation doesn't support setting the logical name dynamically, but with certain types, you can set the physical name in the template with the Name property. For example:
MyUserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref MyUserPool
ClientName: !Sub '${AppName}-userpoolclient'
GenerateSecret: false
Unfortunately, AutoScalingGroup doesn't support this.
A better solution is probably to use Tags on your resources. Most AWS resource types (including AutoScalingGroup) support Tags and they can be set dynamically in a CloudFormation template.
Related
I have a CloudFormation stack that creates an EC2 instance and gives it a name tag.
I want to create a CloudWatch alarm, and reference the EC2 instance's name in the alarm's name - something like AlarmName: !Sub "Status Check Alarm - ${EC2InstanceName}".
!Ref will allow me to reference the CloudFormation script's parameters, but I don't want to parameterize the EC2 instance name - I don't want or need that to be customizable, and I don't want users to have the ability to choose a custom name for the server.
I tried outputting the EC2 instance name so I could !Ref that, but I got an Invalid template resource property 'Outputs' error, so I don't know if my approach even works:
EC2Instance:
Properties: ...
Type: AWS::EC2::Instance
Outputs:
EC2InstanceName:
Description: The server's name.
Value: !GetAtt EC2Instance.Tags.Name
Export:
Name: "EC2InstanceName"
How do I reference the EC2 instance's name without parameterizing the name at the top-level of the script?
EDIT:
I ended up using parameters anyway so I could !Ref them. I guess you could also set up an "allowed values" list containing only a single value that matches the default. It's lame but it works, I guess.
Parameters:
EC2InstanceName:
Type: String
Default: "web-server-blah"
Description: The name of the EC2 instance.
You can use !GetAtt only for attributes which are specifically named in the documentation https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html
Tags are not among them.
But if you provide a different tag for your instance, then you can refer it without even exporting it (providing that it is a constant value).
I see what you are trying to do, but AWS does not support everything you would like to work out of the box. One way how I imagine it can be done - and you may not like it - is either via a macro or a custom resource (lambda function).
Can't use just use !Ref EC2Instance? I realize it won't be the friendly "Name" tag value, but it could be more useful, especially if you have duplicates of the same "Name". It would make your alarm be something like "Status Check Alarm - i-123456789".
Whereas if you use the name it might be something more like 10 alarms that read "Status Check Alarm - WWWServer", but now which WWWServer?
When creating a new IAM role resource using Cloudformation, AWS::IAM::Role.
There is an optional RoleName parameter, and I am not sure when if it is better practice to include the RoleName parameter or exclude it.
The following are the differences that are specified in the documentation
If you don't specify a name, AWS CloudFormation generates a unique physical ID and uses that ID for the role name.
If you specify a name, you must specify the CAPABILITY_NAMED_IAM value to acknowledge your template's capabilities.
Naming an IAM resource can cause an unrecoverable error if you reuse the same template in multiple Regions.
Are there any other functional differences betweeen using or excluding the RoleName parameter?
This will simply generate a name for you if you don't specify it (constructed from the stack and resource name with a random string at the end).
The only difference is re-usability. If you want to recreate the stack multiple times, it would be better to pass in this IAM role ARN as a parameter.
I'd recommend not naming, which will allow you the freedom to create the stack as many times as you want without the fear of breaking additional resources.
If you really must the recommendation from AWS is below:
Naming an IAM resource can cause an unrecoverable error if you reuse the same template in multiple Regions. To prevent this, we recommend using Fn::Join and AWS::Region to create a Region-specific name, as in the following example: {"Fn::Join": ["", [{"Ref": "AWS::Region"}, {"Ref": "MyResourceName"}]]}.
The documentation explains that it may be dangerous to name your roles:
Naming an IAM resource can cause an unrecoverable error if you reuse the same template in multiple Regions. To prevent this, we recommend using Fn::Join and AWS::Region to create a Region-specific name, as in the following example: {"Fn::Join": ["", [{"Ref": "AWS::Region"}, {"Ref": "MyResourceName"}]]}.
I have a CloudFormation template with that creates a Launch Configuration:
Resources:
# Launch Configuration for the instances in the Atoscaling Group
LaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
AssociatePublicIpAddress: false
ImageId: !Ref EC2AMI
InstanceType: !Ref EC2InstanceType
KeyName: !Ref EC2Key
IamInstanceProfile: !ImportValue EC2RoleInstanceProfileARN
LaunchConfigurationName: jxt-private-asg-launch-config
SecurityGroups:
- !ImportValue PrivateSecurityGroupId
When I try to update the stack I get the below error:
CloudFormation cannot update a stack when a custom-named resource
requires replacing
I am running this script via TeamCity so it is not possible for the user to change the Launch Confoiguration's name each time. What can I do to get rid of this error?
One solution can be to omit the LaunchConfigurationName since it is not mandatory.
Copied from the AWS::AutoScaling::LauncConfiguration documentation:
The name of the launch configuration. This name must be unique per Region per account. [...]
Update requires: Replacement
The problem you are facing is that you have made a change which requires the replacement of the launch configuration. Typically, CloudFormation creates a new resource (in case the existing resource cannot be updated), points any dependant resources to the new resource and then deletes the old resource. However, this operation fails if the resource uses a static name because then it conflicts with the unique name constraint mentioned in the docs.
You can either:
Do what #matsev recommended and not use names for resources that don't require it (probably the best option) - names will be generated based on stack name.
Add a variable into your Resource name, such as a parameter which passes in commit-id or date or something along those lines. This will ideally make your Resource name unique.
I'm facing a decision to Use Cross-Stack References to Export Shared Resources or to Use Nested Stacks to Reuse Common Template Patterns following AWS CloudFormation best practices.
However, they seem the same to me apart from a few differences:
cross-stack uses Fn::ImportValue, templates are in one folder.
nested-stack must be in S3, uses type AWS::CloudFormation::Stack and TemplateURL.
There's no clear pros and cons between them as far as I could search.
My goal is to create a parent stack that passes some core variables like stackName to the child stacks, then the child stacks create the resources sharing some variables between them like ARN or Policies, using the stackName to name their resources like stackNameDynamoDBTable.
You should use cross-stack references as it was created for your use case of passing between stacks.
Whereas nested stacks would work, it’s primary purpose is for reuse of modular components, like a template of a resource you use in lots of stacks to save copy pasting and updating the stacks independently.
Nested stacks: if you need to manage your stacks from a single point, you should use nested stacks.
example: assume that you have load balancer configuration that you use for most of your stacks. Instead of copying and pasting the same configurations into your templates you can create a dedicated template for load balancer.
cross-stack : Alternatively, if you need to manage your stacks as separate entities, you should use cross-stack references.(AWS limits the number of VPCs you can create in an AWS region to five.)
example : You might have a network stack that includes a VPC, a security group, and a subnet. You want all public web apps to use these resources. By exporting the resources, you allow all stacks with public web applications to use them.
There is a way to get the best of both worlds. The trick is to use cross-stack resource sharing but make it depend on a parameter that is passed using Nested stack.
Here's an example from how I used this, consider two stacks IAMRoleStack and ComputeStack. The former contains all the necessary IAM roles and the latter contains a bunch of Lambda functions that those roles are applied to.
Resources:
IAMCustomAdminRoleForLambda:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Policies:
Output:
IAMRoleArnForLambda:
Description: Returns the Amazon Resource Name for the newly created IAM Custom
Role for Lambda function
Value: !GetAtt 'IAMCustomAdminRoleForLambda.Arn'
Export:
Name: !Sub '${AWS::StackName}-IAMRoleArnForLambda'
StackName:
Description: Returns name of stack after deployment
Value: !Sub ${AWS::StackName}
As you can see I've exported the IAM role but it's Name depends on the stack name that is calculated once the stack is deployed. You can read more about exporting outputs in the docs.
In the ComputeStack, I use this role by importing it.
Resources:
LambdaForCompute:
Type: AWS::Lambda::Function
Properties:
Role: !ImportValue
Fn::Sub: ${StackNameOfIAMRole}-IAMRoleArnForLambda
The parent stack that "nests" both ComputeStack and IAMRoleStack orchestrates passing the stack name parameter.
Resources:
IAMRoleStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Ref IAMRoleStackURL
ComputeStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Ref ComputeStackURL
Parameters:
StackNameOfIAMRole: !GetAtt IAMRoleStack.Outputs.StackName
I can't attest to best practice but this style allows me to pick and choose where I want orchestrated deployment and where I want to do the deployments individually.
I also want to point out that this kind of modularization based on type of resources is not very feasible for nested stacks. For e.g. in this scenario, if I had 10 different roles for 10 different Lambda functions, I would have to pass each of those 10 roles through parameters. Using this hybrid style, I only need to pass one parameter the stack name.
With cross stacks, you pass a reference to a bunch existing components X to stacks A and B when you want A and B to reuse these very same existing components. With nested stacks, when you nest a nested stack Y in stacks C and D, Y shall create a new set of components Y is describing individually for C and for D.
It is similar to concepts 'passing by reference' and 'passing by value' in programming.
How to specify ParentId of existing "AWS::ApiGateway::RestApi" for "AWS::ApiGateway::Resource" in CloudFormation template? For example, I have already created API Gateway REST API and I want to specify it in my template:
MyTestResource:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId:
Ref: 'RestApi'
ParentId: <<placeholder_for_my_value>>
In case when I describe "AWS::ApiGateway::RestApi" in my template too I can do that such way:
ParentId: !GetAtt "RestApi.RootResourceId"
But how can I do that for already existing REST API?
You can lookup the resource id of an existing REST API either through the console
Click "your REST API" -> "Resources"
in the higher left side you'll see something like
APIs>YOUR_API_NAME (YOUT_API_ID)>Resources>/your_already_existing_resource (YOUR_RESOURCE_ID)
That's then the resource Id you can specify as a "parentId".
Alternatively, use the aws cli.
https://docs.aws.amazon.com/cli/latest/reference/apigateway/get-resources.html
Where you'd need to specify the rest-api-id which you again can either get from the console, or via https://docs.aws.amazon.com/cli/latest/reference/apigateway/get-rest-apis.html
In fact, if you need to specify a new resource in an existing API, you can check the stack in the CloudFormation, in the resources tab and see the Logical Did with which the API was generated.
Whit that LogicalId you can specify en that manner, considering logical Id as RestApiLogicalId:
"ParentId": {
"Fn::GetAtt": [
"RestApiLogicalId",
"RootResourceId"
]
}
Enjoy!