10
votes

We are adapting our applications CloudFormation template to make use of VPC. Within this template we need to programmatically generate the CIDR blocks used for our VPC subnets, in order to ensure they do not conflict between CloudFormation stacks.

My initial plan had been to generate the CIDRs by concatenating strings together, for example:

"ProxyLoadBalancerSubnetA" : {
  "Type" : "AWS::EC2::Subnet",
  "Properties" : {
    "VpcId" : { "Ref" : "Vpc" },
    "AvailabilityZone" : "eu-west-1a",
    "CidrBlock" : { "Fn::Join" : [ ".", [ { "Ref" : "VpcCidrPrefix" }, "0.0/24" ] ] }
  }
},

Upon further consideration however, we need to use a single VPC rather than having a VPC for each of our stacks.

AWS restrict VPCs to using a maximum of a /16 CIDR block (we have asked for this limit to be raised, but it is apparently not possible). This means it is no longer possible for us to use this concatenation method as each of our stacks require subnets that span more than 255 addresses in total.

I'd like to generate the CIDR blocks on-the-fly rather than having to define them as parameters to the CloudFormation template,

One idea I had was each stack having a "base integer" and adding to that for each subnet's CIDR block.

For example:

    "CidrBlock" : { "Fn::Join" : [ ".", [ { "Ref" : "VpcCidrPrefix" }, { "Fn::Sum", [ { "Ref" : "VpcCidrStart" }, 3 ] }, "0/24 ] ] }

Where VpcCidrStart is an integer that sets the value that the third CIDR octet should start from within the script, and 3 is the subnet number.

Obviously the Fn::Sum intrinsic function doesn't exist though, so I wanted to know if anyone had a solution to adding integers in VPC (it seems like something that shouldn't be possible, as CloudFormation is string oriented), or a better solution to this conundrum in general.

2

2 Answers

2
votes

My solution for these kinds of issues was to use a legitimate programming language to compile a template into a CloudFormation JSON document. I used PHP 5.4, Twig and Symfony Console, but YMMV.

Essentially, you do the math ahead of time in the programming language, then use that data as you write-out your JSON document.

6
votes

I faced a similar situation. I wanted to control everything from the template without generating the template using some script. My input range is also limited as in your case. I ended up putting a horrible horrible hack. I am kind of ashamed to post it here but if it helps one more person, it may be worth it.

Have a mapping table which will do the math for you and define it for all possible inputs

"Mappings" : { 
    "HorribleHackForSubtraction" : { 
        "1" : {"SubtractOne" : "0"},
        "2" : {"SubtractOne" : "1"},
        "3" : {"SubtractOne" : "2"},
        "4" : {"SubtractOne" : "3"},
        "5" : {"SubtractOne" : "4"},
        "6" : {"SubtractOne" : "5"},
        "7" : {"SubtractOne" : "6"},
        "8" : {"SubtractOne" : "7"},
        "9" : {"SubtractOne" : "8"},
        "10" : {"SubtractOne" : "9"},
        "11" : {"SubtractOne" : "10"},
        "12" : {"SubtractOne" : "11"},
        "13" : {"SubtractOne" : "12"},
        "14" : {"SubtractOne" : "13"},
        "15" : {"SubtractOne" : "14"},
        "16" : {"SubtractOne" : "15"},
    }   
},  

You can refer to the computed value as

{ "Fn::FindInMap" : [ "HorribleHackForSubtraction", { "Ref" : "MyInputParam"}, "SubtractOne" ] }