CloudFormation, apply Condition on DependsOn
Asked Answered
C

6

28

The task that I need to do is make CDN depend on a S3 bucket. But we want to make it use the existing bucket rather than creating a new one.

Here is the sample code that I am trying:

"Parameters" : {
  "UseExistingBucket" : {
    "Description" : "Yes/No",
    "Default" : "yes",
    "Type" : "String",
    "AllowedValues" : [ "yes", "no" ]
  }
},
"Conditions" : {
  "CreateS3Resources" : {"Fn::Equals" : [{"Ref" : "UseExistingBucket"}, "no"]}
},
"Resources" : {
  "StaticBucket" : {
    "Type" : "AWS::S3::Bucket",
    "Condition" : "CreateS3Resources",
    "Properties" : {
      "BucketName" : { "Fn::Join": [ "-", [ "app",  { "Ref": "EnvType" }, "static" ] ] }
    },
    "DeletionPolicy": "Retain"
  },
  "MyStaticDistribution": {
    "Type": "AWS::CloudFront::Distribution",
    "Properties": {
      "DistributionConfig": {
        "Origins": [
          {
            "DomainName": {
              "Fn::If" : [
                "CreateS3Resources",
                { "Fn::Join": [ "-", [ "app",  { "Ref": "EnvType" }, "static" ] ] },
                {"Fn::GetAtt": [ "StaticBucket", "DomainName" ] }
              ]
            },
            "Id": "S3Origin",
          }
        ]
      }
    },
    "DependsOn": [{
      "Fn::If" : [
        "CreateS3Resources",
        { "Fn::Join": [ "-", [ "app",  { "Ref": "EnvType" }, "static" ] ] },
        ""
      ]
    }]
  }
}

Please suggest to me any more details, if they are required (atleast stackoverflow does wants more details, but I have not specified any :-P)

Calderon answered 5/1, 2016 at 8:47 Comment(0)
M
20

You can do this by using Fn:GetAtt wrapped in a conditional Fn:If. Using Fn:GetAtt implies a dependency, so CloudFormation will automatically wait once it reaches that function, the same as if you were using a DependsOn.

Example

The code snippet below shows this by conditionally retrieving the name of a nested stack that has not yet been created but only does so if the condition UseNestedStack is set to true. If UseNestedStack is false it will not wait and instead retrieve a local variable name.

{
"Fn::If": ["UseNestedStack", {
    "Fn::GetAtt": ["NestedStack", "Outputs.Name"]
}, {
    "Ref": "LocalName"
}]

How Do I know This? (Another Example)

Unfortunately there is no official documentation officially stating this but it was AWS that told me to do it this way and in their code examples you can see that when order matters they use Fn:GetAtt. I've tried this numerous times and it works every time. Try it yourself on a simple stack. Here is some more pseudo proof from an AWS lambda example that I tweaked and have used myself. The stack below could not possibly work if the AMI function is created after the resource AMI info, AMI Info needs output of the AMI function, so AWS has chained them together using Fn: GetAtt. To see this scroll to the bottom and look at the resource AMIInfo and you will see it references AMIFunction via fn: Gett. CloudFormation sees this and goes back to AMIFunction to create it first.

"AMIInfoFunction": {
  "DependsOn":"SourceStack",
  "Type": "AWS::Lambda::Function",
  "Properties": {
    "Code": {
      "S3Bucket": { "Ref": "DeploymentBucket" },
      "S3Key": {"Fn::Join": [
        "",
        [
          {
            "Ref": "ApplicationName"
          },
          "/amilookup.zip"
        ]
      ]}
    },
    "Handler": "amilookup.handler",
    "Runtime": "nodejs",
    "Timeout": "30",
    "Role": { "Fn::GetAtt" : ["LambdaExecutionRole", "Arn"] },
    "VpcConfig": {
      "SecurityGroupIds": [ {"Ref": "InstanceSecurityGroup"}],
      "SubnetIds": [ {"Ref":"PrivateSubnetA"},{"Ref":"PrivateSubnetB"} ]
    }
  }
},
"AMIInfo": {
  "Type": "Custom::AMIInfo",
  "Properties": {
    "ServiceToken": { "Fn::GetAtt" : ["AMIInfoFunction", "Arn"] },
    "StackName": { "Ref":"SourceStack" }
  }
}

Update: This is now documented (Fn::GetAtt) in the documentation for dependent stacks. Thank you to idoimaging in the comments for pointing that out. https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html

Misprint answered 22/10, 2016 at 21:31 Comment(5)
Is there documentation somewhere that says that GetAtt will automatically wait for the other resource to be finished before moving forward?Exponent
@Exponent Good question. Unfortunately I have not seen it documented but I am confident this is true from its use in AWS examples, the fact that I've seen it work multiple times, and from AWS engineers instructing me to do it this way. I've added a lambda example from AWS in which they delay creation this way. But yes some official documentation would be cleaner.Misprint
This is now a documented feature of !GetAtt: docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/…Suspicion
@Suspicion Thanks for pointing that out. I've added your link to the answer. I can't believe that was 5 years ago haha.Misprint
Thanks! This put me on the right track. I needed to conditionally configure S3 object notifications for to an SNS topic - but the topic's access policy must first allow notifications because S3 checks access. Bucket config needs to depend on SNS InlineTopicPolicy CFn rejected specifying the TopicConfiguration via Fn::Get: "Topic": { "Fn::GetAtt": [ "NotifyK9ReportsSNSTopicPolicy", "TopicArn" ] } But a direct Ref of the policy works because it resolves to the topic ARN: "Topic": { "Ref": "NotifyK9ReportsSNSTopicPolicy" }Gonsalve
C
7

In your template you do not need to add the DependsOn attribute to your MyStaticDistribution resource as you already have a reference to the StaticBucket resource.

This is documented in the Specifying Dependencies section of the Optimize AWS CloudFormation Templates blog post: https://aws.amazon.com/blogs/devops/optimize-aws-cloudformation-templates/

When you need CloudFormation to wait to provision one resource until another one has been provisioned, you can use the DependsOn attribute.

You can also introduce references between elements by using either the { "Ref": "MyResource" } or the { "Fn::GetAtt" : [ "MyResource" , "MyAttribute" ] } functions. When you use one of these functions, CloudFormation behaves as if you’ve added a DependsOn attribute to the resource.

Courteous answered 4/5, 2017 at 6:48 Comment(0)
O
6

For yaml users, you can also use:

Conditions:
  CreateConfigRecorder: !Equals [ !Ref ConfigRecorderExists, 'false' ]

Resource:
#my 1st AWS Resource
  ConfigRecorder: 
    Condition: CreateConfigRecorder
    Type: AWS::Config::ConfigurationRecorder
    *more codes below*

#added, since DependsOn: !If is not possible, trigger by WaitCondition if CreateConfigRecorder is true
#Hacks: https://garbe.io/blog/2017/07/17/cloudformation-hacks/
  ConfigRecorderWaitHandle: 
    Condition: CreateConfigRecorder
    DependsOn: ConfigRecorder
    Type: "AWS::CloudFormation::WaitConditionHandle"
#added, since DependsOn: !If is not possible, trigger by WaitCondition if CreateConfigRecorder is false
  WaitHandle: 
    Type: "AWS::CloudFormation::WaitConditionHandle"
#added, since DependsOn: !If is not possible
  WaitCondition: 
    Type: "AWS::CloudFormation::WaitCondition"
    Properties: 
      Handle: !If [CreateConfigRecorder, !Ref ConfigRecorderWaitHandle, !Ref WaitHandle]
      Timeout: "1"
      Count: 0
#my 2nd AWS Resource that requires DependsOn Attribute
  AWSConfigRule:
    Type: AWS::Config::ConfigRule
    DependsOn: WaitCondition #added, since DependsOn: !If is not possible
    *more codes below*

Basically my 2nd resource only has DependsOn attribute if my 1st resource is non-existent, before running the CFN. I got this from: https://garbe.io/blog/2017/07/17/cloudformation-hacks/

Oarlock answered 29/10, 2019 at 8:59 Comment(0)
L
3

To extend @Usman Mutawakil's answer above, you can use GetAtt in tags to implicitly impose a DependsOn condition.

Eg, in our case we use a lambda function to auto delete staging stacks after a length of time. This func is only deployed on staging, and must be deleted last in the stack.

  ShopCluster:
    Type: 'AWS::ECS::Cluster'
    Properties:
      ...
      Tags:
        - Key: ConditionalDependsOn
          Value: !If [IsStaging, !GetAtt DeleteShopStackLambda.Arn, Ignored]
Lander answered 17/6, 2021 at 8:5 Comment(0)
P
1

You can not attach a function to a DependsOn property.

but you can apply a condition to a DependsOn property with workaround by using AWS::CloudFormation::WaitCondition and AWS::CloudFormation::WaitConditionHandle

full example:

Parameters:
  ContainerPort:
    Type: String
    Default: ''
    Description: HTTP Port of the container

Conditions:
  HasAlb: !Not [ !Equals [ !Ref ContainerPort, '']]

Resources:

  ALBListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Condition: HasAlb
    Properties:
      ...


  AlbWaitHandle: 
    Condition: HasAlb
    DependsOn: ALBListenerRule
    Type: "AWS::CloudFormation::WaitConditionHandle"

  WaitHandle: 
    Type: "AWS::CloudFormation::WaitConditionHandle"

  WaitCondition: 
    Type: "AWS::CloudFormation::WaitCondition"
    Properties: 
      Handle: !If [HasAlb, !Ref AlbWaitHandle, !Ref WaitHandle]
      Timeout: "1"
      Count: 0

  Application:
    Type: AWS::ECS::Service
    DependsOn: WaitCondition

original answer can be found here

Pericynthion answered 2/12, 2021 at 12:8 Comment(0)
E
0

In my case I had to assign AWS::NoValue to DependsOn conditionally:

Service:
  Type: AWS::ECS::Service
  DependsOn: !If [ShouldCreateRDS, DatabaseInstance1, 'AWS::NoValue']

but this gave me template error: DependsOn must be a string or list of strings

So I applied this simple workaround:

Service:
  Type: AWS::ECS::Service
  Metadata:
    DependsOn: !If [ShouldCreateRDS, !Ref DatabaseInstance1, !Ref 'AWS::NoValue']
Eyeglasses answered 3/6 at 1:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.