aws-cloudformation / cloudformation-cli

The CloudFormation Provider Development Toolkit allows you to author your own resource providers and modules that can be used by CloudFormation.
Apache License 2.0
316 stars 157 forks source link

CloudFormation `deploy` doesn't update `!Sub` contents with new parameter value. #989

Closed garretwilson closed 9 months ago

garretwilson commented 1 year ago

I have CloudFormation parameter:

Parameters:
  MyParam:
    Type: String
    Default: "FooBar"

And I have a CloudFront functiont that interpolates it using !Sub:

  MyFunction:
    Type: AWS::CloudFront::Function
    Properties:
      Name: "my-function"
      AutoPublish: false
      FunctionConfig:
        Comment: Testing.
        Runtime: cloudfront-js-1.0
      FunctionCode: !Sub |
        function handler(event) {
          var myParam = "${MyParam}";
          return request;
        }

I can deploy that fine:

aws cloudformation deploy --stack-name my-stack --template-file foobar.cf.yaml

The function shows up in the console like this, as expected:

        function handler(event) {
          var myParam = "FooBar";
          return request;
        }

But then I try to change the default value of my parameter:

Parameters:
  MyParam:
    Type: String
    Default: ""

Now if I try to deploy the CloudFormation template, it doesn't even recognize that there is a change!

If I try to find some way to force the deployment (for example changing AutoPublish to true), then the stack redeploys. But the !Sub is not reevaluated, and even though MyParam is now set to "", the CloudFront function doesn't change and still contains a value ("FooBar") that no longer even exists in the template:

        function handler(event) {
          var myParam = "FooBar";
          return request;
        }

But it gets worse. Let's say I manually modify my function so that it doesn't even contain a reference:

  MyFunction:
    Type: AWS::CloudFront::Function
    Properties:
      Name: "my-function"
      AutoPublish: false
      FunctionConfig:
        Comment: Testing.
        Runtime: cloudfront-js-1.0
      FunctionCode: !Sub |
        function handler(event) {
          var myParam = "";
          return request;
        }

I can redeploy the template, and I can verify in the console that the function now simple an empty var myParam = "" as expected. But then I put the reference back:

  MyFunction:
    Type: AWS::CloudFront::Function
    Properties:
      Name: "my-function"
      AutoPublish: false
      FunctionConfig:
        Comment: Testing.
        Runtime: cloudfront-js-1.0
      FunctionCode: !Sub |
        function handler(event) {
          var myParam = "${MyParam}";
          return request;
        }

Remember that MyParam is still set to the empty string in the template file. Nevertheless, CloudFormation somehow "remembered" the old value of MyParam (before I changed it to ""), even and puts FooBar back, even though FooBar is nowhere in the template file at all!

        function handler(event) {
          var myParam = "FooBar";
          return request;
        }

So I thought maybe the problem was with the empty string, so I set MyParam to "some other value":

Parameters:
  MyParam:
    Type: String
    Default: "some other value"

CloudFormation deploy tells me:

No changes to deploy. Stack my-stack is up to date

🤦‍♂️

So I force an update by mucking with AutoPublish and get it to redeploy. But MyFunction still contains var myParam = "FooBar" even though MyParam is now set to "some other value"!

This is frustrating.

But then it gets even worse!

I went to the CloudFront console and deleted the function altogether! Then I redeployed the template with the function removed from the template. Then finally I put the function back in and redeployed the stack. The function still has "FooBar" even though it should be a brand new function, and MyParam is set to "some other value"!!!

🤯

benbridts commented 1 year ago

The default is only used the first time a parameter is added to a stack (so in your case on create).

They are meant for values you want to change when you deploy the stack, you can do this with Parameter Overrides:

aws cloudformation deploy [...] --parameter-overrides Key1=Value1 Key2=Value2

If you want to update the template to change the value, you can either hardcode it in the resource:

FunctionCode: !Sub
  - |
      function handler(event) {
        var myParam = "${MyParam}";
        return request;
      }
  - MyParam: SomeValue

Or "misuse" a mapping

Mappings:
  Constants:
    default:
      MyKey: SomeValue
FunctionCode: !Sub
  - |
      function handler(event) {
        var myParam = "${MyParam}";
        return request;
      }
  - MyParam: !FindInMap [Constants, default, MyKey]
mircealam commented 9 months ago

see input provided by @benbridts