Referencing Resources between CloudFormation stacks - amazon-web-services

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

Related

Does AWS CloudFormation support Tags Property Attribute for AWS::EC2::VPCEndpoint

I have been trying to create endpoints for my two vpc's, it is creating the vpc's, but it is not working with the Tags property which i require to name the vpc endpoint created.
Error: "Encountered unsupported property Tags"
{
"Resources": {
"VPCEndpoint1": {
"Type" : "AWS::EC2::VPCEndpoint",
"Properties" : {
"PrivateDnsEnabled" : "True",
"RouteTableIds" : ["rtb-1"],
"ServiceName" : "com.amazonaws.eu-west-1.s3",
"VpcEndpointType" : "Gateway",
"VpcId" : "vpc-id1",
"Tags": [
{
"Key": "Name",
"Value": "name1"
}
]
}
},
"VPCEndpoint2": {
"Type" : "AWS::EC2::VPCEndpoint",
"Properties" : {
"PrivateDnsEnabled" : "True",
"RouteTableIds" : ["rtb-2"],
"ServiceName" : "com.amazonaws.eu-west-1.s3",
"VpcEndpointType" : "Gateway",
"VpcId" : "vpc-id2",
"Tags": [
{
"Key": "Name",
"Value": "name2"
}
]
}
}
}
}
Cloudformation do not support Tags property.
Refer cloudformation document => https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcendpoint.html
Tags are supported for AWS::EC2::VPC, But not for AWS::EC2::VPCEndpoint.
If, you need tags, create tags on vpc level, not on endpoints.
VPC Level Tags - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html
CloudFormation does not support tagging VPC Endpoints yet.
See https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/196
Here is a workaround to tag your VPCE in CloudFormation:
You can setup the custom resource and macro from https://github.com/awslabs/aws-cloudformation-templates/tree/55ebf9f7129e87530e68c242d7e46167e6a798b8/aws/services/CloudFormation/MacrosExamples/Boto3
The code is 4 years old, so it needs to be updated:
Use python3.9
Move lambda code to InlineCode in template to replace urllib2 with cfnresponse
Remove calls to json.dumps
Remove property case lowering
Then you should be able to add a tag using CloudFormation like this:
VpceTagName:
Type: Boto3::ec2.create_tags
Properties:
Resources:
- !Ref VpcEndpoint
Tags:
- Key: Name
Value: My VPCE

Optional parameters when using AWS CLI to launch CloudFormation template

I'm trying to create a CloudFormation template that'll deploy a Lambda function, And I need the security options to be optional parameters.
I was able to partially accomplish this using the question here:
How to make a whole object in CloudFormation templates optional?
Interestingly, that method worked great to make the VpcConfig property optional in the AWS GUI Console, but it did NOT work to make it optional for the CLI. And unfortunately, I need it to work in the CLI, since I'll be using CodeBuild to call and deploy this template's resources.
Here are the relevant parameters:
"SecurityGroupIds" : {
"Type" : "CommaDelimitedList",
"Description" : "A list of one or more security groups IDs in the VPC that includes the resources to which your Lambda function requires access."
},
"SubnetIds" : {
"Type" : "CommaDelimitedList",
"Description" : "A list of one or more subnet IDs in the VPC that includes the resources to which your Lambda function requires access."
}
And conditions:
"HasVPC": {"Fn::And": [{"Fn::Not": [{"Fn::Equals": [{"Fn::Join": ["", {"Ref": "SubnetIds"}]}, ""]}]}, {"Fn::Not": [{"Fn::Equals": [{"Fn::Join": ["", {"Ref": "SecurityGroupIds"}]}, ""]}]}]}
And here's where that condition is used in the Lambda resource being defined in the Resources section of the template:
"VpcConfig": {
"Fn::If": [
"HasVPC",
{
"SecurityGroupIds" : {"Ref": "SecurityGroupIds"},
"SubnetIds" : {"Ref": "SubnetIds"}
},
{ "Ref":"AWS::NoValue" }
]
},
When I issue the command to deploy this stack in the CLI, I get the following error:
An error occurred (ValidationError) when calling the CreateChangeSet
operation: Parameters: [SecurityGroupIds, SubnetIds] must have values
Here's the AWS CLI command I'm issuing, from the same directory in which the template is located. Note: the ARN values have all been heavily modified to not be real values from my account, but I kept them in the right format so you can see the real format of the command:
aws cloudformation deploy --template-file lambda-template.json --stack-name "CLI-lambda-stack" --parameter-overrides S3BucketName="myBucket" S3FileLocation="lambda_function.zip" S3ObjectVersion="ZuB0iueEghOyh5q00.DiykLNudujdsc5" DeadLetterArn="arn:aws:sns:us-west-2:577898337216:CloudFormationTests" EnvironmentVariable="testing" KmsKeyArn="arn:aws:kms:us-west-2:504398934246:key/b24e7b72-a94d-6a3e-b848-165115c86212" HandlerFunctionName="lambda_function.lambda_handler" MemorySize="128" Role="arn:aws:iam::102893937243:role/serverless-test-default-us-east-1-lambdaRole" FuncName="myCLILambda"
You are not providing SecurityGroupIds neither SubnetIds default values and your are not providing them on your --parameter-overrides. Therefore, CloudFormation doesn't know how to process them if no values are provided.
Adding the Default statement should do the trick:
{
"Parameters" : {
"SecurityGroupIds" : {
"Type" : "CommaDelimitedList",
"Description" : "A list of one or more security groups IDs in the VPC that includes the resources to which your Lambda function requires access.",
"Default" : ""
},
"SubnetIds" : {
"Type" : "CommaDelimitedList",
"Description" : "A list of one or more subnet IDs in the VPC that includes the resources to which your Lambda function requires access.",
"Default" : ""
}
}

Accessing name of parent Cloudformation stack in nested stack

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.

Invalid domain name identifier specified

When trying to create an AWS::ApiGateway::BasePathMapping through CloudFormation, I am given the following error:
Invalid domain name identifier specified
Below is the portion(s) of my CloudFormation template that should create the AWS::ApiGateway::BasePathMapping:
{
"Parameters": {
"ApiDomainName": {
"Description": "The domain name for the API",
"Type": "String"
}
},
"Resources": {
"ApiBasePathMapping": {
"Type": "AWS::ApiGateway::BasePathMapping",
"Properties": {
"DomainName": {
"Ref": "ApiDomainName"
},
"RestApiId": {
"Ref": "RepositoryApi"
},
"Stage": {
"Ref": "ApiProductionStage"
}
},
"DependsOn": [
"ApiProductionStage"
]
}
}
}
The documentation makes no mention that it needs to be anything special for the DomainName, but the documentation for this resource seems to be lacking some information (It doesn't list outputs for example even though there is a Distribution Domain Name created as an example).
The remainder of the stack works as expected. I am trying to add this resource in as a Change Set. I do own the domain I am trying to use, and I have created a certificate in ACM for this domain.
Quoting from AWS forums:
You can only create or modify base path mappings after the domain name
has been added to API Gateway. This "Invalid domain name identifier
specified" error message is returned when the domain name given in the
base path mapping is not found, indicating that it has not been added
yet.
Also, as of March 2017, the only way to add domain name to the API Gateway via CloudFormation is via custom resources that CloudFormation offers.
Ref: https://forums.aws.amazon.com/message.jspa?messageID=769627
It is now possible to just do that. You just have to explicit state on your CFN template that there is a dependency (DependsOn):
...
ApiDevMapping:
Type: 'AWS::ApiGateway::BasePathMapping'
Properties:
BasePath: v1.0
Stage: dev
DomainName: my-api.example.com
RestApiId: !Ref MobileApiDev
DependsOn:
- MobileApiDevDomain
...

How to pass a VPC's ID as a parameter to the Cluster.template to set as a default value?

I am trying to find a way to set default VPCs, Subnets and Security Groups in the Cluster.template JSON file.
Is there a way to pass an existing VPC ( or Subnet/Security group) as a parameter to the template using the "Ref" built-in?
This Obviously dones't work:
"Parameters": {
"VpcId": {
"Type": "AWS::EC2::VPC::Id",
"Default": { "Ref" : "vpc-123456789" },
....
}
To inject a VPC id into your template I would do the following. First remove your default value.
"Parameters": {
"VpcId": {
"Type": "AWS::EC2::VPC::Id"
....
}
Next place the value you want to set VpcId to inside a parameters.json file and when you perform a create-stack or update-stack using your cloudformation use the parameters file as the input.
parameters.json
[
{
"ParameterKey": "VpcId",
"ParameterValue": "vpc-123456789"
}
]
Multi-valued Parameters
If you had a parameter that takes a list of values you could represent it as follows
"PrivateEC2Subnets": {
"Type": "CommaDelimitedList",
"Description": "List of private subnets to run your EC2 instances inside. Note that they must be in the same availability zone that your ELB is configured for. May require you to manually create a private subnet with a specific AZ if your VPC isnt auto-configured."
},
Then in your external parameters file pass in a comma separated list like so
{
"ParameterKey": "PrivateEC2Subnets",
"ParameterValue": "subnet-9934670a544,subnet-d74ea349f"
},
For more information on the different parameter types see the AWS doc http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html but beware, people have reported issues when trying to represent lists of complex datatypes in external parameters files. To my knowledge, only CommaDelimitedList works if you want to pass the values in from another json file outside your cloudformation template.
I found out that it's really much simpler than I thought... this worked:
"Parameters": {
"VpcId": {
"Type": "List<AWS::EC2::VPC::Id>",
"Default": "vpc-123456789,vpc-987654123" ,
....
}