diff --git a/README.md b/README.md index 52c984f..f503c63 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,49 @@ kurgan add vpc-v2 ## Configuration +### Selecting Availability Zones + +By default the `vpc-v2` component will automatically select the AZs. This is achieved by looping over the `max_availability_zones` count and using the `Fn::GetAZs` Cloudformation function to select the AZ id. + +```rb +max_availability_zones.times |az| + selected_az = FnSelect(az, FnGetAZs(Ref('AWS::Region'))) +end +``` + +However if you wish to define which as AZs you want to use you can by configuring a map per AWS account with the AZs you wish to use. +**NOTE:** the total count of AZ's defined in the map for each account has to be the same value as `max_availability_zones`. + +To configure your AZ settings, set `az_mapping` to true + +```yaml +az_mapping: true +``` + +Then configure a [Map](https://github.com/theonestack/cfhighlander#cloudformation-mappings) in the following structure to define your AZs + +```yaml +Accounts: + '000000000000': # AWS Account Id + AZs: '3,5,0' # Comma delimited list of numerical values that maps to the Availability Zone for that account +``` + +the numerical values will map to the Availability Zone retuned from the `Fn::GetAZs` function in the AWS account. For example in `us-east-1` returns + +```rb +[ "us-east-1a", "us-east-1b", "us-east-1c", "us-east-1d", "us-east-1e", "us-east-1f" ] +``` + +therefore the mapping `AZs: '3,5,0'` will use AZs `[ "us-east-1d", "us-east-1f", "us-east-1a" ]` + +the new function to retrieve the AZ becomes + +```rb +max_availability_zones.times |az| + selected_az = FnSelect(FnSelect(az, FnSplit(',', FnFindInMap('Accounts', Ref('AWS::AccountId'), 'AZs'))), FnGetAZs(Ref('AWS::Region'))) +end +``` + ### Subnetting **Subnet Allocation** diff --git a/spec/az_mapping_spec.rb b/spec/az_mapping_spec.rb new file mode 100644 index 0000000..cfad549 --- /dev/null +++ b/spec/az_mapping_spec.rb @@ -0,0 +1,89 @@ +require 'yaml' + +describe 'compiled component' do + + context 'cftest' do + it 'compiles test' do + expect(system("cfhighlander cftest #{@validate} --tests tests/az_mapping.test.yaml")).to be_truthy + end + end + + let(:template) { YAML.load_file("#{File.dirname(__FILE__)}/../out/tests/az_mapping/vpc-v2.compiled.yaml") } + + context 'Resource SubnetPublic0' do + + let(:properties) { template["Resources"]["SubnetPublic0"]["Properties"] } + + it 'has property VpcId' do + expect(properties["VpcId"]).to eq({"Ref"=>"VPC"}) + end + + it 'has property CidrBlock' do + expect(properties["CidrBlock"]).to eq({"Fn::Select" => [0, {"Fn::Cidr"=>[{"Ref"=>"CIDR"}, 16, {"Ref"=>"SubnetBits"}]}]}) + end + + it 'has property AvailabilityZone' do + expect(properties["AvailabilityZone"]).to eq({"Fn::Select"=>[{"Fn::Select"=>[0, {"Fn::Split"=>[",", {"Fn::FindInMap"=>["Accounts", {"Ref"=> "AWS::AccountId"}, "AZs"]}]}]}, {"Fn::GetAZs"=>{"Ref"=>"AWS::Region"}}]}) + end + + it 'has property Tags' do + expect(properties["Tags"]).to include({"Key"=>"Name", "Value"=>{"Fn::Sub"=>["${EnvironmentName}-public-${AZ}", {"AZ"=>{"Fn::Select"=>[{"Fn::Select"=>[0, {"Fn::Split"=>[",", {"Fn::FindInMap"=>["Accounts", {"Ref"=> "AWS::AccountId"}, "AZs"]}]}]}, {"Fn::GetAZs"=>{"Ref"=>"AWS::Region"}}]}}]}}) + expect(properties["Tags"]).to include({"Key"=>"Type", "Value"=>"public"}) + expect(properties["Tags"]).to include({"Key"=>"Environment", "Value"=>{"Ref"=>"EnvironmentName"}}) + expect(properties["Tags"]).to include({"Key"=>"EnvironmentType", "Value"=>{"Ref"=>"EnvironmentType"}}) + end + + end + + context 'Resource SubnetPublic1' do + + let(:properties) { template["Resources"]["SubnetPublic1"]["Properties"] } + + it 'has property VpcId' do + expect(properties["VpcId"]).to eq({"Ref"=>"VPC"}) + end + + it 'has property CidrBlock' do + expect(properties["CidrBlock"]).to eq({"Fn::Select" => [1, {"Fn::Cidr"=>[{"Ref"=>"CIDR"}, 16, {"Ref"=>"SubnetBits"}]}]}) + end + + it 'has property AvailabilityZone' do + expect(properties["AvailabilityZone"]).to eq({"Fn::Select"=>[{"Fn::Select"=>[1, {"Fn::Split"=>[",", {"Fn::FindInMap"=>["Accounts", {"Ref"=> "AWS::AccountId"}, "AZs"]}]}]}, {"Fn::GetAZs"=>{"Ref"=>"AWS::Region"}}]}) + end + + it 'has property Tags' do + expect(properties["Tags"]).to include({"Key"=>"Name", "Value"=>{"Fn::Sub"=>["${EnvironmentName}-public-${AZ}", {"AZ"=>{"Fn::Select"=>[{"Fn::Select"=>[1, {"Fn::Split"=>[",", {"Fn::FindInMap"=>["Accounts", {"Ref"=> "AWS::AccountId"}, "AZs"]}]}]}, {"Fn::GetAZs"=>{"Ref"=>"AWS::Region"}}]}}]}}) + expect(properties["Tags"]).to include({"Key"=>"Type", "Value"=>"public"}) + expect(properties["Tags"]).to include({"Key"=>"Environment", "Value"=>{"Ref"=>"EnvironmentName"}}) + expect(properties["Tags"]).to include({"Key"=>"EnvironmentType", "Value"=>{"Ref"=>"EnvironmentType"}}) + end + + end + + context 'Resource SubnetPublic2' do + + let(:properties) { template["Resources"]["SubnetPublic2"]["Properties"] } + + it 'has property VpcId' do + expect(properties["VpcId"]).to eq({"Ref"=>"VPC"}) + end + + it 'has property CidrBlock' do + expect(properties["CidrBlock"]).to eq({"Fn::Select" => [2, {"Fn::Cidr"=>[{"Ref"=>"CIDR"}, 16, {"Ref"=>"SubnetBits"}]}]}) + end + + it 'has property AvailabilityZone' do + expect(properties["AvailabilityZone"]).to eq({"Fn::Select"=>[{"Fn::Select"=>[2, {"Fn::Split"=>[",", {"Fn::FindInMap"=>["Accounts", {"Ref"=> "AWS::AccountId"}, "AZs"]}]}]}, {"Fn::GetAZs"=>{"Ref"=>"AWS::Region"}}]}) + end + + it 'has property Tags' do + expect(properties["Tags"]).to include({"Key"=>"Name", "Value"=>{"Fn::Sub"=>["${EnvironmentName}-public-${AZ}", {"AZ"=>{"Fn::Select"=>[{"Fn::Select"=>[2, {"Fn::Split"=>[",", {"Fn::FindInMap"=>["Accounts", {"Ref"=> "AWS::AccountId"}, "AZs"]}]}]}, {"Fn::GetAZs"=>{"Ref"=>"AWS::Region"}}]}}]}}) + expect(properties["Tags"]).to include({"Key"=>"Type", "Value"=>"public"}) + expect(properties["Tags"]).to include({"Key"=>"Environment", "Value"=>{"Ref"=>"EnvironmentName"}}) + expect(properties["Tags"]).to include({"Key"=>"EnvironmentType", "Value"=>{"Ref"=>"EnvironmentType"}}) + end + + end + +end + diff --git a/tests/az_mapping.test.yaml b/tests/az_mapping.test.yaml new file mode 100644 index 0000000..8716d76 --- /dev/null +++ b/tests/az_mapping.test.yaml @@ -0,0 +1,6 @@ +test_metadata: + type: config + name: az_mapping + description: defing the az's through a map + +az_mapping: true diff --git a/vpc-v2.cfndsl.rb b/vpc-v2.cfndsl.rb index 4bdacb3..90d87d1 100644 --- a/vpc-v2.cfndsl.rb +++ b/vpc-v2.cfndsl.rb @@ -178,7 +178,20 @@ external_parameters[:max_availability_zones].times do |az| - get_az = { AZ: FnSelect(az, FnGetAZs(Ref('AWS::Region'))) } + if az_mapping == true + get_az = { + AZ: FnSelect( + FnSelect(az, + FnSplit(',', + FnFindInMap('Accounts', Ref('AWS::AccountId'), 'AZs') + ) + ), + FnGetAZs(Ref('AWS::Region')) + ) + } + else + get_az = { AZ: FnSelect(az, FnGetAZs(Ref('AWS::Region'))) } + end matches = ((az+1)..external_parameters[:max_availability_zones]).to_a # Determins whether we create resources in a particular availability zone @@ -462,7 +475,19 @@ external_parameters[:max_availability_zones].times do |az| multiplyer = az+index*external_parameters[:subnet_multiplyer] subnet_name_az = "Subnet#{cfg['name']}#{az}" - get_az = { AZ: FnSelect(az, FnGetAZs(Ref('AWS::Region'))) } + + if az_mapping == true + get_az = FnSelect( + FnSelect(az, + FnSplit(',', + FnFindInMap('Accounts', Ref('AWS::AccountId'), 'AZs') + ) + ), + FnGetAZs(Ref('AWS::Region')) + ) + else + get_az = FnSelect(az, FnGetAZs(Ref('AWS::Region'))) + end if external_parameters[:subnet_parameters] subnet_cidr = FnSelect(az, Ref("#{cfg['name']}SubnetList")) @@ -471,7 +496,7 @@ end subnet_tags = vpc_tags.map(&:clone) - subnet_tags << { Key: 'Name', Value: FnSub("${EnvironmentName}-#{cfg['name'].downcase}-${AZ}", get_az) } + subnet_tags << { Key: 'Name', Value: FnSub("${EnvironmentName}-#{cfg['name'].downcase}-${AZ}", { AZ: get_az }) } subnet_tags << { Key: 'Type', Value: cfg['type'] } subnet_tags.push(*cfg['tags'].map{|t| t.transform_keys(&:to_sym) }) if cfg.key?('tags') @@ -479,7 +504,7 @@ Condition("CreateAvailabilityZone#{az}") VpcId Ref(:VPC) CidrBlock subnet_cidr - AvailabilityZone FnSelect(az, FnGetAZs(Ref('AWS::Region'))) + AvailabilityZone get_az Tags subnet_tags.reverse.uniq {|t| t[:Key]} } diff --git a/vpc-v2.config.yaml b/vpc-v2.config.yaml index 7105c55..3a78222 100644 --- a/vpc-v2.config.yaml +++ b/vpc-v2.config.yaml @@ -1,4 +1,5 @@ max_availability_zones: 3 +az_mapping: false subnet_multiplyer: 4 vpc_cidr: 10.0.0.0/16