Skip to content

Commit

Permalink
Merge pull request #6 from theonestack/feature/az-select-map
Browse files Browse the repository at this point in the history
support defining AZs through account mapping
  • Loading branch information
Guslington committed May 6, 2021
2 parents 3f0b886 + 56da559 commit f522860
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 4 deletions.
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down
89 changes: 89 additions & 0 deletions spec/az_mapping_spec.rb
Original file line number Diff line number Diff line change
@@ -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

6 changes: 6 additions & 0 deletions tests/az_mapping.test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
test_metadata:
type: config
name: az_mapping
description: defing the az's through a map

az_mapping: true
33 changes: 29 additions & 4 deletions vpc-v2.cfndsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"))
Expand All @@ -471,15 +496,15 @@
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')

EC2_Subnet(subnet_name_az) {
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]}
}

Expand Down
1 change: 1 addition & 0 deletions vpc-v2.config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
max_availability_zones: 3
az_mapping: false
subnet_multiplyer: 4

vpc_cidr: 10.0.0.0/16
Expand Down

0 comments on commit f522860

Please sign in to comment.