Accessing name of parent Cloudformation stack in nested stack - amazon-web-services

I'm using a nested Cloudformation template structured like this:
masterTemplate -> childA -> childB
Each of the JSON template files is stored in S3 with a bucket name of "${masterStackName}-env-upload". This works fine from the parent template, as I can simply do:
"TemplateURL": {
"Fn::Join": [
"",
[
"https://s3.amazonaws.com/",
{
"Ref": "AWS::StackName"
},
"-env-upload/device-specific-template-HALO-BUILD-VERSION.json"
]
]
},
However, when childA attempts to do the same thing to launch the childB template, "AWS::StackName" becomes the generated name for childA - meaning that it is trying to access a non-existent bucket.
My question is: how can I pass down the name of the master/parent stack to the child stacks? I attempted to do this as a Parameter, but "Ref" is not allowed for parameter values (so I couldn't do "Ref" : "AWS::StackName" for the value).
Any help is appreciated!

It is in fact possible to pass the parent stack name to the child stacks using parameters:
Here's an exemple:
parent.yml
---
AWSTemplateFormatVersion: '2010-09-09'
Resources:
ChildA:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ...test/test-child-a.yml
Parameters:
ParentStackName: !Ref AWS::StackName
test-child-a.yml
---
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
ParentStackName:
Type: String
Resources:
TestBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref ParentStackName

One option is to decouple the stack name from the S3 bucket name, and specify the S3 bucket as a parameter in the masterTemplate stack, and then reference it in the Outputs section. The
In the master:
"Outputs": {
"EnvUploadBucketName" : {
"Value" : { "Ref" : "paramEnvUploadBucketName" }
}
}
In the child:
"TemplateURL": {
"Fn::Join": [
"",
[
"https://s3.amazonaws.com/",
{ "Fn::GetAtt" : [ "masterTemplate", "Outputs.EnvUploadBucketName" ] }
"/device-specific-template-HALO-BUILD-VERSION.json"
]
]
}
In this case, EnvUploadBucketName would be the name of the upload bucket, passed as an output from the masterTemplate stack.

Related

CloudFormation Resource Creation if not exist

I want to create Route53 HostedZone with CloudFormation so I want to check some information in Route53 about HostedZone is exist.
In logic of my case I need check if resource is exist, ignore the resource creation. How I can handle this problem.
My CloudFormation template show at below.
"myDNSRecord" : {
"Type" : "AWS::Route53::RecordSet",
"Properties" : {
"HostedZoneName" : { "Ref" : "HostedZoneResource" },
"Comment" : "DNS name for my instance.",
"Name" : {
"Fn::Join" : [ "", [
{"Ref" : "Ec2Instance"}, ".",
{"Ref" : "AWS::Region"}, ".",
{"Ref" : "HostedZone"} ,"."
] ]
},
"Type" : "A",
"TTL" : "900",
"ResourceRecords" : [
{ "Fn::GetAtt" : [ "Ec2Instance", "PublicIp" ] }
]
}
}
This is not exactly the answer you need. But in general, you can use Conditions for this. In you template, you define your condition in Conditions section and use it to conditionally create the resource. e.g.
Parameters:
EnvironmentSize:
Type: String
Default: Micro
AllowedValues:
- Micro
- Small
- Medium
- AuroraCluster
Conditions:
isntAuroraCluster:
!Not [!Equals [!Ref EnvironmentSize, "AuroraCluster"]]
DBInstance:
Type: AWS::RDS::DBInstance
Condition: isntAuroraCluster
Properties:
DBInstanceClass: !FindInMap [InstanceSize, !Ref EnvironmentSize, DB]
<Rest of properties>
Here my RDS DBinstance is only created if my environment size is not AuroraCluster.
If you don't find a better solution, you could take that as user input (whether to create a record set or not) & use that as condition to create your resource. Hope it helps.
The best way to do this would be to do the following:
Create a lambda backed custom resource
Check using lambda whether your resource exists or not, depending on that return an identifier
Use cloudformation conditions to check on the value of the returned identifier and then correspondingly create or not create the resource.
You can fetch the return value of the custom resource using !GetAtt
More information can be found on the AWS websites relating to custom resource:
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html
You can try to orchestrate creation of specific resources using AWS::NoValue
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html
Below is taken from variables creation for LambdaFunction
Conditions:
IsProd: !Equals [!Ref Env, "production"]
Environment:
Variables:
USER: !If [IsProd, !GetAtt ...., Ref: AWS::NoValue]

Using Ref for Resource in Step function inside cloudformation template

I have a step function inside cloudformation. The cloudformation stack also create Lambdas which i will use as resource in step function. I have something like
TestLambda:
Type: "AWS::Lambda::Function"
Properties:
Handler: "test_lambda.lambda_handler"
Role: "arn:aws:iam::1234342334:role/Lambda"
Code:
ZipFile: !Sub |
from __future__ import print_function
import boto3
def lambda_handler(event, context):
print(event)
Runtime: "python2.7"
....
TestStateMachine:
Type: AWS::StepFunctions::StateMachine
Properties:
StateMachineName: "Test"
DefinitionString: |-
{
"StartAt": "State1",
"States": {
"State1" : {
"Type" : "Task",
"Resource" : "${!GetAtt TestLambda.Arn}",
"Next": "State2?"
},
...
...
all inside one cloudformation template.
"SCHEMA_VALIDATION_FAILED: Value is not a valid resource ARN"
I also tried !GetAtt TestLambda.Arn, it didn't work. I want the lambda and stepfunction created inside the single cloudformation template. Please let me know if there is better, cleaner way of doing it.
Thank you
You should use Fn::Sub function for that:
TestStateMachine:
Type: AWS::StepFunctions::StateMachine
Properties:
StateMachineName: "Test"
DefinitionString:
Fn::Sub:
|-
{
"StartAt": "State1",
"States": {
"State1" : {
"Type" : "Task",
"Resource" : "${TestLambda.Arn}",
"Next": "State2?"
},

Referencing Resources between CloudFormation stacks

If I have two cloudformation stacks, how do I references a resource in one stack from the other stack?
In the example below I have a stack that creates an EBS volume and want to reference that via the Ref: key in the second stack for my EC2 instance but I keep getting a rollback since it can't see that resource from the first stack:
"Template format error: Unresolved resource dependencies"
I already tried the DependsOn clause but it didn't work. Do I need to pass information via Parameters?
{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"CubesNetworking": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "https://s3.amazonaws.com/mybucket/cf_network.json"
}
},
"CubesInstances": {
"DependsOn": ["CubesNetworking"],
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "https://s3.amazonaws.com/mybucket/cf_instances.json"
}
}
}
}
In each of your nested stacks, you should have an output section. Then you can get those values in your calling stack (the one you have listed above) with syntax like:
{ "Fn::GetAtt" : [ "CubesNetworking", "Outputs.VolumeID" ] }
You then pass the values into your other nested stacks via Parameters:
"Parameters" : {
"VolumeId" : { "Fn::GetAtt" : [ "CubesNetworking", "Outputs.VolumeID" ] }
You still want the DependsOn since you need the volume created before the instance.
Edit, Mid-2017:
CloudFormation has introduced the ability to export values from one stack, and reference them in other stacks that do not have to be nested.
So your output can specify an export:
Outputs:
Desc:
Value: !Ref CubesNetworking.VolumeID
Export:
Name: some-unique-name
Then in another stack:
Fn::ImportValue: some-unique-name

How can I reference recordset names in the output section of my cloudformation script?

I am creating some DNS entries in my cloudformation. There is a param passed into the cfn script, which results in the creation of a Route53 entry like hostname-test.example.com:
"Host" : {
"Type" : "AWS::Route53::RecordSetGroup",
"Properties" : {
"HostedZoneName" : "example.com.",
"RecordSets" : [
{
"Name" : {
"Fn::Join" : [ "-", [
{"Ref" : "Hostname" },
"test.example.com"
]]
},
"Type" : "A",
"AliasTarget" : {
"DNSName" : { "Fn::GetAtt" : [ "PublicWebLoadBalancer", "CanonicalHostedZoneName" ] },
"HostedZoneId" : { "Fn::GetAtt" : [ "PublicWebLoadBalancer", "CanonicalHostedZoneNameID" ] }
}
}
]
}
}
In my output, I would like to get the Name attribute from the RecordSet, but I don't know how to reference it. According to the Fn::GetAtt documentation, Route53 objects are not supported.
Is this possible?
This question is a bit old, but it I just ran into this same issue.
You need to output the entire RecordSet, ie:
"Outputs" : {
"MyDNSRecord" : {
"Description": "The DNS Record of ...",
"Value" : { "Ref": "MyRecordSet" }
}
}
Which (not intuitively) outputs the value of the record set name you are looking for.
I had the same question, and was looking for a clear answer in yaml.
Given the following AWS::Route53::RecordSet
rPublicReverseProxyNLBDnsRecord:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneName: !Ref pPublicHostedZoneName
Comment: !Sub 'DNS record for the ${AWS::StackName} ELB front door.'
Name: !Sub '${pDeploymentType}.${pPublicHostedZoneName}'
Type: CNAME
TTL: '30'
ResourceRecords:
- !GetAtt rPublicReverseProxyNLB.DNSName
I was able to output the Application URL I wanted with the following output section code:
Outputs:
ApplicationURL:
Description: 'The public URL for the application'
Value: !Sub 'https://${rPublicReverseProxyNLBDnsRecord}/'
Instead of embedding your RecordSet inside the RecordSetGroup, define it as a separate property, with the same HostedZoneName as your RecordSetGroup.
You can then use "Ref" to get the value of the Name attribute.
http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-recordset.html
I second the suggestion of trying RecordSet.
But your "Name" is deterministic. If the stack completes, outputting what you already have will never behave differently from what you want:
{
"Fn::Join" : [ "-", [
{"Ref" : "Hostname" },
"test.example.com"
]]
}
If this were OOP, I'd say it's decidedly wrong to kick back an argument without taking the opportunity to implicitly test the function.

How can I get current date in a CloudFormation script?

I am tagging my resources using Tags in my cfn script:
"Tags" : [ { "Key" : "Owner", "Value" : "my name" },
{ "Key" : "Name", "Value" : "instance name" }
{ "Key" : "DateCreated", "Value" : <something goes here> }
],
I would like to create a tag with the current date as per the example above. Is it possible?
You can use a "custom resource" to generate a timestamp (or any other value).
Custom resources are a newish feature in CloudFormation (introduced around 2014) and allow you to basically call a lambda function to "create", "update" or "delete" a resource for which CloudFormation does not provide language support (can even be resources outside AWS).
I use custom resource a lot just to compute some values for use in other parts of the stack, for example to create "variables" that hold computed values (e.g. using !Join and similar functions) that I need to use often and would like to compute once.
You can easily use a custom resource to just generate a time stamp. Here is some example code that is very close to what I actually use in production:
Create the "resource" implementation
Resources:
ValueFunc:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: >
var r = require('cfn-response');
exports.handler = function(ev, ctx) {
ev.ResourceProperties.Time = new Date().toISOString();
r.send(ev, ctx, r.SUCCESS, ev.ResourceProperties);
};
Handler: index.handler
Runtime: nodejs6.10
Timeout: 30
Role: !GetAtt ValueFunctionExecutionRole.Arn
ValueFunctionExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal: { Service: [ lambda.amazonaws.com ] }
Action: sts:AssumeRole
Policies:
- PolicyName:
Fn::Sub: "value-custom-res-${AWS::StackName}-${AWS::Region}"
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "arn:aws:logs:*:*:*"
- Effect: Allow
Action: cloudformation:DescribeStacks
Resource: "arn:aws:cloudformation:*:*:*"
Then wherever you want to generate a time stamp, you do something like this (Scheduled action example taken from here):
Create a custom resource that calculates its creation time
GetTimeThisTime:
Type: Custom::Value
Properties:
ServiceToken: !GetAtt ValueFunc.Arn
Read the created timestamp using the Time attribute
ScheduledActionUp:
Type: AWS::AutoScaling::ScheduledAction
Properties:
AutoScalingGroupName: !Ref WebServerGroup
DesiredCapacity: 2
StartTime: !GetAtt GetTimeThisTime.Time
Recurrence: "0 7 * * *"
You can generate multiple time stamps at different times of the stack creation by simply creating a new "custom value" that depends on the logical entity whose creation you want to time.
The advice by #Guy is correct, you can access the creation timestamp of the stack from the stack properties.
If you still need to specify tags as parameters then you can do it the following way. Currently the JSON syntax supports an extremely limited set of functions. Because of this the possibilities for dynamically modifying your templates are very tiny. The only way I see to introduce this the tag you want is by adding another parameter to the template itself. Depending on the way you initialize the stack, you can script the parameter to be specified dynamically or provide it in the web console.
For example, if you have this in your template:
"Parameters" : {
"CreationDate" : {
"Description" : "Date",
"Type" : "String",
"Default" : "2013-03-20 21:15:00",
"AllowedPattern" : "^\\d{4}(-\\d{2}){2} (\\d{2}:){2}\\d{2}$",
"ConstraintDescription" : "Date and time of creation"
}
},
You can later reference it using the Ref keyword in the tags like this:
"Tags" : [ { "Key" : "Owner", "Value" : "my name" },
{ "Key" : "Name", "Value" : "instance name" },
{ "Key" : "DateCreated", "Value" : { "Ref" : "CreationDate" } }
],
It is not trivial to automatically assign the current time if you create the stack from the AWS console, but if you use the CLI tools you can call cfn-create-stack like this:
cfn-create-stack MyStack --template-file My.template --parameters "CreationDate=$(date +'%F %T')"
Hope this helps!