How to make a whole object in CloudFormation templ

2019-08-21 10:33发布

问题:

I'm making a Lambda function via CloudFormation template, and I'd like to make it optional to enter the information for the VpcConfig property. I've found articles like this one on how to make parameters optional:

https://cloudonaut.io/optional-parameter-in-cloudformation/

That was very helpful for finding syntax to make properties with single values optional (like a single string value).

But what I need to figure out is how to make the whole VpcConfig OBJECT optional.

It's a little tricky, because the VpcConfig object has two properties: SecurityGroupIds and SubnetIds. And both are required. So the user needs to enter both of those or neither of those. If neither are entered, then the whole VpcConfig object should be empty, or not exist (which should be okay, since the VpConfig object itself is optional).

Here's a truncated version of what I have now, but it's not enough since I still get errors saying that SecurityGroupIds and SubnetIds can't have empty values, because they're required as long as the object is present in the VpcConfig property:

"Conditions": {
        "HasSecurityGroups": {"Fn::Not": [{"Fn::Equals": [{"Fn::Join": ["", {"Ref": "SecurityGroupIds"}]}, ""]}]},
        "HasSubnetIds": {"Fn::Not": [{"Fn::Equals": [{"Fn::Join": ["", {"Ref": "SubnetIds"}]}, ""]}]}
      },

and then in the Lambda resource:

"VpcConfig" : {
          "SecurityGroupIds" : {"Fn::If": ["HasSecurityGroups", {"Ref": "SecurityGroupIds"}, {"Ref": "AWS::NoValue"}]},
          "SubnetIds" : {"Fn::If": ["HasSubnetIds", {"Ref": "SubnetIds"}, {"Ref": "AWS::NoValue"}]}
        },

Here's the entire template, if that's helpful:

 {
  "AWSTemplateFormatVersion" : "2010-09-09",

  "Description" : "Lambda template for testing purposes",

  "Parameters" : {
    "S3BucketName" : {
      "Type" : "String",
      "Description" : "The name of the Amazon S3 bucket where the .zip file that contains your deployment package is stored."
    },
    "S3FileLocation" : {
      "Type" : "String",
      "Description" : "The location and name of the .zip file that contains your source code."
    },
    "S3ObjectVersion" : {
      "Type" : "String",
      "Description" : "If you have S3 versioning enabled, the version ID of the zip file that contains your source code."
    },
    "DeadLetterArn" : {
      "Type" : "String",
      "Description" : "ARN that specifies a Dead Letter Queue (DLQ) that Lambda sends events to when it can't process them. For example, you can send unprocessed events to an Amazon Simple Notification Service (Amazon SNS) topic, where you can take further action."
    },
    "EnvironmentVariable" : {
      "Type" : "String",
      "Default" : "test",
      "Description" : "Environment Variable"
    },
    "KmsKeyArn" : {
      "Type" : "String",
      "Description" : "KMS Key ARN if environment variables are encrypted"
    },
    "HandlerFunctionName" : {
      "Type" : "String",
      "Description" : "Name of function to initiate the Lambda"
    },
    "MemorySize" : {
      "Type" : "Number",
      "Description" : "Number of MB to allocate to the Lambda function",
      "ConstraintDescription" : "Multiples of 64, between 128 and 3008",
      "MinValue" : "128",
      "Default" : "128"
    },
    "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."
    },
    "Role" : {
      "Type" : "String",
      "Description" : "ARN of Lambda Role"
    },
    "FuncName" : {
      "Type" : "String",
      "Description" : "Name of the the new Lambda function?"
    }
  },

  "Conditions": {
    "HasSecurityGroups": {"Fn::Not": [{"Fn::Equals": [{"Fn::Join": ["", {"Ref": "SecurityGroupIds"}]}, ""]}]},
    "HasSubnetIds": {"Fn::Not": [{"Fn::Equals": [{"Fn::Join": ["", {"Ref": "SubnetIds"}]}, ""]}]}
  },

  "Resources" : {

    "LambdaFunction" : {
      "Type" : "AWS::Lambda::Function",
      "Properties" : {
        "Code" : {
          "S3Bucket" : { "Ref" : "S3BucketName" },
          "S3Key" : { "Ref" : "S3FileLocation" },
          "S3ObjectVersion" : { "Ref" : "S3ObjectVersion" }
        },
        "DeadLetterConfig" : {
          "TargetArn" : { "Ref" : "DeadLetterArn" }
        },
        "Description" : "Lambda",
        "Environment" : {
          "Variables" : {
            "SomeVariable": {
              "Ref" : "EnvironmentVariable"
            }  
          }
        },
        "FunctionName" : { "Ref" : "FuncName" },
        "Handler" : { "Ref" : "HandlerFunctionName" },
        "KmsKeyArn" : { "Ref" : "KmsKeyArn" },
        "MemorySize" : { "Ref" : "MemorySize" },
        "Role" : { "Ref" : "Role" },
        "Runtime" : "python3.6",
        "VpcConfig" : {
          "SecurityGroupIds" : {"Fn::If": ["HasSecurityGroups", {"Ref": "SecurityGroupIds"}, {"Ref": "AWS::NoValue"}]},
          "SubnetIds" : {"Fn::If": ["HasSubnetIds", {"Ref": "SubnetIds"}, {"Ref": "AWS::NoValue"}]}
        },
        "Tags" : [ {
          "Key" : "test",
          "Value" : "true"
        } ]
      }
    }
  },
  "Outputs" : {
    "LambdaFunction" : {
      "Description" : "Lambda function",
      "Value" : { "Ref" : "LambdaFunction" },
      "Export" : {
        "Name" : {"Fn::Sub": "${AWS::StackName}-Lambda" }
      }
    }
  }
}

Update: the answer posted below by cementblocks works for the AWS GUI Console, but unfortunately it's still hitting the same issue if you try to deploy the template using the CLI. I've created a new question at the link below to address this issue separately, since I imagine his answer will do the trick for most people who reference this post.

Optional parameters when using AWS CLI to launch CloudFormation template

回答1:

Create a 3rd condition HasVPC it should be true when HasSecurityGroups and HasSubnetIds are both true using Fn::And. You will have to duplicate the condition statement; you can't reference on condition in another.

Then set the vpcConfig property using Fn::If

"VpcConfig": {
  "Fn::If": [
    "HasVPC",
    {
      "SecurityGroupIds" : {"Ref": "SecurityGroupIds"},
      "SubnetIds" : {"Ref": "SubnetIds"}
    },
    { "Ref":"AWS::NoValue" }
  ]
}

You can remove the HasSecurityGroups and HasSubnetIds conditions then unless you use them elsewhere.