CloudFormation: conditional parameters - amazon-web-services

Building a CloudFormation stack template, I have a setup constellation where upon instantiation I want to reference either the name of another CloudFormation stack or a non-CloudFormation-managed database as a parameter.
Is there a way to represent this constellation in my template? I.e. "Parameter DatabaseHost is mandatory if Parameter DatabaseStack is blank"?

Maybe it wasn't possible at the time of the question, but now, you can include conditions on a CloudFormation template. See docs.
In this example, I use one value or another depending on the environment:
InfrastructurePipelineStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub "https://<masked>.yml"
Parameters:
ProjectName: !Ref ProjectName
...
LambdaNotifications: !If [isDev, !GetAtt NotificationsStack.Outputs.LambdaNotifications, !Ref LambdaNotifications]
If the environment is Development ("isDev" condition), I use the output of other CloudFormation Stack as the value. If not, I use a provided fixed value (non-CloudFormation value).
In this case "isDev" is acting as "parameter DatabaseStack is blank" in the OP question.

I'm not aware of a native option in CloudFormation to make one template parameter conditional on a second template parameter.
Possible workarounds might be:
make both optional, and tell user to supply one of them
use two templates, one for each of the two use cases
programmatically generate your template after asking the user for parameters

Related

reuse a value in a sam template

I'm probably missing something very obvious, but I can't find a way of just setting a value that I want to reuse. For instance - I have a sam template that creates a bunch of database tables - I want them to all have the same settings - and I want those settings to depend on whether it's production or not.
so at the moment I do
Resources:
firstTable:
Type: AWS::DynamoDb::Table
...
DeletionPolicy: !If[ isProduction, Retain, Delete ]
secondTable:
Type: AWS::DynamoDb::Table
DeletionPolicy: !If[ isProduction, Retain, Delete ]
in the perfect world, I'd want to say something like "every dynamodb table defined in this template should have this list of settings:" - but I suspect that's not possible, but what I think IS possible - I want to somehow be able to say something like:
somewhere:
deletion_policy_value: !If[ isProduction, Retain, Delete ]
...
firstTable:
Type: AWS::DynamoDb::Table
...
DeletionPolicy: deletion_policy_value
but none of parameters, conditionals globals or environment variables seem to fit - ie I want to define a custom variable that exists only for the life of the template - environment variables seem to exist in the actual cloudformation script - which is NOT what I want (I think)
Sadly its not possible. You would have to develop your own macro to create such substitutions.

How dynamically change S3 path inside DefinitionBody

My code currently is working like this
DefinitionBody:
'Fn::Transform':
Name: AWS::Include
Parameters:
Location:
Fn::Sub: s3://${Bucket}/${AWS::StackName}/${File}
# Note: Bucket and File comes from Parameters in this case
Now I try to get {$File} dynamically using FindInMap
'Fn::Transform':
Name: AWS::Include
Parameters:
Location: !Sub
- s3://${value-from-parameters-section}/${AWS::StackName}/${spec}
- { spec: !FindInMap [ "Config", "type", "file"]}
But is throwing me this error:
Failed to digest functions within transform parameters, intrinsic functions in transform block must only contain parameter values or stack metadata
So, How I can achieve to dynamically change the File name inside the DefinitonBody?
You can't do this, it is not supported. AWS docs explicitly states that no intrinsic functions are supported:
Supported functions None. CloudFormation passes any intrinsic function calls included in Fn::Transform to the specified macro as literal strings.
You can re-design your function to use custom resources instead of macros. This way you can do whatever you wish.
In this case, I think the message is self-explanatory
Failed to digest functions within transform parameters, intrinsic functions in transform block must only contain parameter values or stack metadata
Only metadata or parameters are allowed, that is the reason this works
DefinitionBody:
'Fn::Transform':
Name: AWS::Include
Parameters:
Location:
Fn::Sub: s3://${Bucket}/${AWS::StackName}/${StackType}
In case somebody needs a workaround I just remove the mapping section and rename the name of the files to match the type I want, something like this
Parameters:
StackType:
Type: String
AllowedValues:
- typeA
- typeB
Bucket:
Type: String
Then I just use this sub to get the DefinitionBody like this:
DefinitionBody:
'Fn::Transform':
Name: AWS::Include
Parameters:
Location:
Fn::Sub: s3://${Bucket}/${AWS::StackName}/${StackType}-API.yaml
This will resolve like this:
s3://example-bucket/name-of-stack/typeA-API.yaml
or
s3://example-bucket/name-of-stack/typeB-API.yaml
So now I just need to have these files named in that way on the S3, this is similar to what mapping should do, but for now, is not supported, this will achieve what I want, is not perfect but did the trick, still remain the confusion because I think the documentation is inaccurate

Storing parameterized values in cloud formation and referencing it

Is there a way to store variables in Cloudformation?
I've created a resource with a name which is a stage specific name in the following form:
DeliveryStreamName: {'Fn::Sub': ['firehose-events-${Stage}', 'Stage': {'Ref' : 'Stage' }]}
Now if I've to create a cloudwatch alarm on that resource I'm again following the same pattern:
Dimensions:
- Value: {'Fn::Sub': ['firehose-events-${Stage}', 'Stage': {'Ref' : 'Stage' }]}
Instead if I could store the whole value in one variable, it would be much easier for me to refer it.
I thought initially storing it in parameters, like this:
Parameters:
FirehoseEvent: {Type:String, Default: 'firehose-events-${Stage}'}
But the stage value doesn't seem to get passed in here. And there is no non default value either for this resource name.
The other option I considered was using mapping, but that defeats the purpose of using ${Stage}.
Is there some other way which I've missed?
Sadly you haven't missed anything. Parameters can't reference other parameters in their definition.
The only way I can think of doing what you which would be through a custom macro. In its simplest form the macro would just perform traditional find-and-replace type of template processing.
However, the time required to develop such macro could be not worth its benefits, at least in this simple example you've provided in the question.

Is there a global properties for cloudformation Resources section?

I have a cloudformation template for my lambda:
Resources:
Resource1:
Type: AWS::Res
Properties:
StreamArn:
"Fn::Sub": "${var1}-${var2}"
Resource2:
Type: AWS::Res
Properties:
StreamArn:
"Fn::Sub": "${var1}-${var2}"
Is it possible to move these properties somewhere to Properties field of Resources section or any other place to avoid duplication?
Resources:
Properties:
StreamArn:
"Fn::Sub": "${var1}-${var2}"
I've tried to do it, but it doesn't work.
You can use a Parameters entry with a default value to create the equivalent to a Constant Variable, but it can't accept any values from the Resources section (since they haven't been created at that point).
Otherwise, no -- you'll need to duplicate the values. (As at the time of writing this answer.)
If you're using AWS::Serverless::Function, you can use Globals section to have common properties in a stack in one place. So, you can put the resources you mentioned in a stack for them and define a Globals section that has StreamArn
See docs
If you are using SAM and the right resources then you can use the globals sections for this:
AWS::Serverless::Function
AWS::Serverless::Api, and
AWS::Serverless::SimpleTable
The Globals section is unique to AWS SAM. It defines properties that
are common to all your serverless functions and APIs. All the
AWS::Serverless::Function
AWS::Serverless::Api, and
AWS::Serverless::SimpleTable
resources inherit the properties that are
defined in the Globals section. For more information about the Globals
section, see Globals Section of the Template in the AWS Serverless
Application Model Developer Guide.
Documentation
The most convenient way that I found so far is to use mapping like:
Mappings:
ParametersMap:
Var1:
Value: "A"
Var2:
Value: "B"
and then put line !FindInMap: [ "ParametersMap", "Var1", "Value" ] in all the places were you need Var1 param

CloudFormation nested stack name

I need to set nested stack name explicitly in a CloudFormation template, but don't see such option in AWS documentation. Is there way to achieve this?
I can specify stack name, when running a parent stack, but all nested stacks, got a randomly generated stack name, based on a resource name created, like:
VPC:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3-eu-west-1.amazonaws.com/cf-templates-wtmg/vpc.yaml
Parameters:
EnvironmentName: !Ref AWS::StackName
Which will generate nested stack name in form parent_stack_name-VPC-random_hash.
Yes. I was looking for the same thing also but currently it's not available.
I think the reason of you wanted a specific stack name is to use it for output referral?
What you can do/I did was:
1) For those in the same parent stack, you need to output from nested stack and then refer directly from the stack like !GetAtt NestedStack1.Outputs.Output1
2) For those which are outside for parent stack, you will need to output twice. Once in the nested stack and once in the parent stack. Then you can refer to the parent stack output.
Hope this will help.
I ran into the same thing just today.
From the official AWS documentation, supporting the original answer to this question:
You can add output values from a nested stack within the containing template. You use the GetAtt function with the nested stack's logical name and the name of the output value in the nested stack in the format Outputs.NestedStackOutputName
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html
Looks like we still cannot reference the stack name more easily. The first answer to the question on this page still stands.