diff --git a/.gitignore b/.gitignore index cd92d6f..9f9deb8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ *.tfstate.backup **/.terraform.lock.hcl .terraform/ +**/terraform.tfstate +**/terraform.tfstate.backup +**/terraform.tfvars +**/a.out # go deps vendor/ diff --git a/data.tf b/data.tf index 449c4f8..9299acb 100644 --- a/data.tf +++ b/data.tf @@ -22,16 +22,6 @@ data "aws_vpc" "peer_vpc" { id = var.peer_vpc_id } -# Get all route tables from vpcs -data "aws_route_tables" "this_vpc_rts" { - provider = aws.this - vpc_id = var.this_vpc_id -} -data "aws_route_tables" "peer_vpc_rts" { - provider = aws.peer - vpc_id = var.peer_vpc_id -} - # get subnets info data "aws_subnet" "this" { count = length(var.this_subnets_ids) @@ -44,15 +34,69 @@ data "aws_subnet" "peer" { id = var.peer_subnets_ids[count.index] } -# Get info for only those route tables associated with the given subnets -data "aws_route_table" "this_subnet_rts" { - count = length(var.this_subnets_ids) + +# Get subnets and route tables from this + +# this vpc main route table +data "aws_route_table" "this_main_route_table" { provider = aws.this - subnet_id = var.this_subnets_ids[count.index] + vpc_id = var.this_vpc_id + filter { + name = "association.main" + values = ["true"] + } +} + +# this subnets +data "aws_subnets" "this" { + provider = aws.this + filter { + name = "vpc-id" + values = [var.this_vpc_id] + } +} + +# get route tables associated with subnets +data "aws_route_tables" "this_associated_route_tables" { + for_each = { for subnet in data.aws_subnets.this.ids: subnet => subnet } + provider = aws.this + vpc_id = var.this_vpc_id + filter { + name = "association.subnet-id" + values = [each.key] + } +} + + +# Get subnets and route tables from peer + +# peer vpc main route table +data "aws_route_table" "peer_main_route_table" { + provider = aws.peer + vpc_id = var.peer_vpc_id + filter { + name = "association.main" + values = ["true"] + } } -data "aws_route_table" "peer_subnet_rts" { - count = length(var.peer_subnets_ids) +# peer subnets +data "aws_subnets" "peer" { provider = aws.peer - subnet_id = var.peer_subnets_ids[count.index] + filter { + name = "vpc-id" + values = [var.peer_vpc_id] + } } + +# get route tables associated with subnets +data "aws_route_tables" "peer_associated_route_tables" { + for_each = { for subnet in data.aws_subnets.peer.ids: subnet => subnet } + provider = aws.peer + vpc_id = var.peer_vpc_id + filter { + name = "association.subnet-id" + values = [each.key] + } +} + diff --git a/examples/partial-subnets/main.tf b/examples/partial-subnets/main.tf new file mode 100644 index 0000000..154db4f --- /dev/null +++ b/examples/partial-subnets/main.tf @@ -0,0 +1,91 @@ +/* +/ To make it unit testable, get required parameters from vpcs. +*/ + +# peer vpc main route table +data "aws_route_table" "peer_main_route_table" { + provider = aws.peer + vpc_id = var.peer_vpc_id + filter { + name = "association.main" + values = ["true"] + } +} + +# peer subnets +data "aws_subnets" "peer" { + provider = aws.peer + filter { + name = "vpc-id" + values = [var.peer_vpc_id] + } +} + +# get route tables associated with subnets +data "aws_route_tables" "peer_associated_route_tables" { + for_each = { for subnet in data.aws_subnets.peer.ids: subnet => subnet } + provider = aws.peer + vpc_id = var.peer_vpc_id + filter { + name = "association.subnet-id" + values = [each.key] + } +} + +locals { + peer_subnet_route_table_map = { + for subnet in data.aws_subnets.peer.ids: + subnet => concat( + data.aws_route_tables.peer_associated_route_tables[subnet].ids, + [data.aws_route_table.peer_main_route_table.id] + )[0] + } + peer_subnets_associated_map = { + for subnet, route_table in local.peer_subnet_route_table_map: + subnet => route_table + if route_table != data.aws_route_table.peer_main_route_table.id + } + + peer_subnets_unassociated_map = { + for subnet, route_table in local.peer_subnet_route_table_map: + subnet => route_table + if route_table == data.aws_route_table.peer_main_route_table.id + } + peer_subnet_ids = distinct(concat( + try(slice(keys(local.peer_subnets_associated_map), 0, 1), []), + try(slice(keys(local.peer_subnets_unassociated_map),0, 1), []), + )) + # actually, peer route tables should be detected from peer subnets if specified + peer_route_tables = distinct([ for subnet in local.peer_subnet_ids: local.peer_subnet_route_table_map[subnet] ]) +} + + + + +module "partial_subnets" { + + source = "../../" + #version = "6.0.0" + + providers = { + aws.this = aws.this + aws.peer = aws.peer + } + + this_vpc_id = var.this_vpc_id + peer_vpc_id = var.peer_vpc_id + + auto_accept_peering = true + peer_dns_resolution = true + this_dns_resolution = true + peer_subnets_ids = length(var.peer_subnets_ids) > 0 ? var.peer_subnets_ids : local.peer_subnet_ids + this_subnets_ids = var.this_subnets_ids + this_rts_ids = var.this_rts_ids + peer_rts_ids = length(var.peer_rts_ids)>0 ? var.peer_rts_ids : local.peer_route_tables + + tags = { + Name = "tf-partial-subnets" + Environment = "Test" + } + +} diff --git a/examples/partial-subnets/outputs.tf b/examples/partial-subnets/outputs.tf new file mode 100644 index 0000000..40be891 --- /dev/null +++ b/examples/partial-subnets/outputs.tf @@ -0,0 +1,6 @@ +// Required for tests + +output "vpc_peering_accept_status" { + value = module.partial_subnets.vpc_peering_accept_status +} + diff --git a/examples/partial-subnets/provider.tf b/examples/partial-subnets/provider.tf new file mode 100644 index 0000000..0d54dc9 --- /dev/null +++ b/examples/partial-subnets/provider.tf @@ -0,0 +1,20 @@ +provider "aws" { + alias = "this" + region = var.this_region != "" ? var.this_region : "eu-west-2" + assume_role { + role_arn = var.this_assume_role_arn != "" ? var.this_assume_role_arn : null + } + access_key = var.aws_this_access_key != "" ? var.aws_this_access_key : null + secret_key = var.aws_this_secret_key != "" ? var.aws_this_secret_key : null +} + +provider "aws" { + alias = "peer" + region = var.peer_region != "" ? var.peer_region : "eu-central-1" + assume_role { + role_arn = var.peer_assume_role_arn != "" ? var.peer_assume_role_arn : null + } + access_key = var.aws_peer_access_key != "" ? var.aws_peer_access_key : null + secret_key = var.aws_peer_secret_key != "" ? var.aws_peer_secret_key : null +} + diff --git a/examples/partial-subnets/variables.tf b/examples/partial-subnets/variables.tf new file mode 100644 index 0000000..0520e67 --- /dev/null +++ b/examples/partial-subnets/variables.tf @@ -0,0 +1,84 @@ +variable this_assume_role_arn { + type = string + default = "" +} + +variable peer_assume_role_arn { + type = string + default = "" +} + +variable "aws_this_access_key" { + description = "AWS Access Key for requester account" + default = "" +} + +variable "aws_this_secret_key" { + description = "AWS Secret Key for requester account" + default = "" +} + +variable "aws_peer_access_key" { + description = "AWS Access Key for accepter account" + default = "" +} + +variable "aws_peer_secret_key" { + description = "AWS Secret Key for accepter account" + default = "" +} + + +variable this_region { + type = string + default = "eu-central-1" +} + +variable peer_region { + type = string + default = "eu-central-1" +} + +variable this_vpc_id { + type = string +} + +variable peer_vpc_id { + type = string +} + +variable "auto_accept_peering" { + description = "Auto accept peering connection: bool" + type = bool + default = false +} + +variable "tags" { + description = "Tags: map" + type = map(string) + default = {} +} + +variable "peer_subnets_ids" { + description = "If communication can only go to some specific subnets of peer vpc. If empty whole vpc cidr is allowed" + type = list(string) + default = [] +} + +variable "this_subnets_ids" { + description = "If communication can only go to some specific subnets of this vpc. If empty whole vpc cidr is allowed" + type = list(string) + default = [] +} + +variable "this_rts_ids" { + description = "Allows to explicitly specify route tables for this VPC" + type = list(string) + default = [] +} + +variable "peer_rts_ids" { + description = "Allows to explicitly specify route tables for peer VPC" + type = list(string) + default = [] +} \ No newline at end of file diff --git a/locals.tf b/locals.tf index 23771f0..fec8241 100644 --- a/locals.tf +++ b/locals.tf @@ -7,11 +7,29 @@ locals { same_acount_and_region = local.same_region && local.same_account # Rout table should either be the one for the vpc, or the ones associated to the subnets if subnets are given - this_rts_ids_new = data.aws_route_tables.this_vpc_rts.ids - peer_rts_ids_new = data.aws_route_tables.peer_vpc_rts.ids + this_subnet_route_table_map = { + for subnet in data.aws_subnets.this.ids: + subnet => concat( + data.aws_route_tables.this_associated_route_tables[subnet].ids, + [data.aws_route_table.this_main_route_table.id] + )[0] + } - this_rts_ids = length(var.this_subnets_ids) == 0 ? local.this_rts_ids_new : data.aws_route_table.this_subnet_rts[*].route_table_id - peer_rts_ids = length(var.peer_subnets_ids) == 0 ? local.peer_rts_ids_new : data.aws_route_table.peer_subnet_rts[*].route_table_id + peer_subnet_route_table_map = { + for subnet in data.aws_subnets.peer.ids: + subnet => concat( + data.aws_route_tables.peer_associated_route_tables[subnet].ids, + [data.aws_route_table.peer_main_route_table.id] + )[0] + } + + this_rts_ids = length(var.this_subnets_ids) == 0 ? distinct(values(local.this_subnet_route_table_map)) : distinct([ + for subnet_id in var.this_subnets_ids : local.this_subnet_route_table_map[subnet_id] + ]) + + peer_rts_ids = length(var.peer_subnets_ids) == 0 ? distinct(values(local.peer_subnet_route_table_map)) : distinct([ + for subnet_id in var.peer_subnets_ids : local.peer_subnet_route_table_map[subnet_id] + ]) # `this_dest_cidrs` represent CIDR of peer VPC, therefore a destination CIDR for this_vpc # `peer_dest_cidrs` represent CIDR of this VPC, therefore a destination CIDR for peer_vpc @@ -43,6 +61,8 @@ locals { } ] + + # Routes for associated subnets this_associated_routes = [ for pair in setproduct(local.this_rts_ids_hack, local.this_associated_dest_cidrs) : { @@ -64,4 +84,3 @@ locals { create_routes_this = var.from_this && !local.create_associated_routes_this create_routes_peer = var.from_peer && !local.create_associated_routes_peer } - diff --git a/test/fixtures/partial-subnets/data.tf b/test/fixtures/partial-subnets/data.tf new file mode 100644 index 0000000..e62b1cb --- /dev/null +++ b/test/fixtures/partial-subnets/data.tf @@ -0,0 +1,17 @@ +data "aws_route_table" "peer_main_route_table" { + provider = aws.peer + vpc_id = aws_vpc.peer.id + filter { + name = "association.main" + values = ["true"] + } +} + +data "aws_route_table" "this_main_route_table" { + provider = aws.this + vpc_id = aws_vpc.this.id + filter { + name = "association.main" + values = ["true"] + } +} \ No newline at end of file diff --git a/test/fixtures/partial-subnets/main.tf b/test/fixtures/partial-subnets/main.tf new file mode 100644 index 0000000..509589d --- /dev/null +++ b/test/fixtures/partial-subnets/main.tf @@ -0,0 +1,114 @@ +// Fixtures +// VPC +resource "aws_vpc" "this" { + provider = aws.this + cidr_block = "172.20.0.0/16" + enable_dns_hostnames = true + tags = { + Name = "this_vpc" + Environment = "Test" + } +} + +resource "aws_vpc" "peer" { + provider = aws.peer + cidr_block = "172.21.0.0/16" + enable_dns_hostnames = true + tags = { + Name = "peer_vpc" + Environment = "Test" + } +} + +// Route Tables +resource "aws_route_table" "this" { + provider = aws.this + count = length(var.this_subnets) + vpc_id = aws_vpc.this.id + + tags = { + Name = "This VPC RT" + Environment = "Test" + } +} + +resource "aws_route_table" "peer" { + provider = aws.peer + count = length(var.peer_subnets) + vpc_id = aws_vpc.peer.id + + tags = { + Name = "Peer VPC RT" + Environment = "Test" + } +} + +// Subnets + +// on main route table +resource "aws_subnet" "this" { + provider = aws.this + count = length(var.azs_this) + vpc_id = aws_vpc.this.id + cidr_block = var.this_subnets[count.index] + availability_zone = element(var.azs_this, count.index) + + tags = { + Name = "This VPC Subnet" + Environment = "Test" + } +} + +// on separate route tables +resource "aws_subnet" "this_separate_routes" { + provider = aws.this + count = length(var.azs_this) + vpc_id = aws_vpc.this.id + cidr_block = var.this_subnets_separate_routes[count.index] + availability_zone = element(var.azs_this, count.index) + + tags = { + Name = "This VPC Subnet with separate routes" + Environment = "Test" + } +} + +resource "aws_route_table_association" "this" { + provider = aws.this + count = length(var.azs_this) + subnet_id = aws_subnet.this_separate_routes[count.index].id + route_table_id = aws_route_table.this[count.index].id +} + +resource "aws_subnet" "peer" { + provider = aws.peer + count = length(var.azs_peer) + vpc_id = aws_vpc.peer.id + cidr_block = var.peer_subnets[count.index] + availability_zone = element(var.azs_peer, count.index) + + tags = { + Name = "This VPC Subnet" + Environment = "Test" + } +} + +resource "aws_subnet" "peer_separate_routes" { + provider = aws.peer + count = length(var.azs_peer) + vpc_id = aws_vpc.peer.id + cidr_block = var.peer_subnets_separate_routes[count.index] + availability_zone = element(var.azs_peer, count.index) + + tags = { + Name = "This VPC Subnet with separate routes" + Environment = "Test" + } +} + +resource "aws_route_table_association" "peer" { + provider = aws.peer + count = length(var.azs_peer) + subnet_id = aws_subnet.peer_separate_routes[count.index].id + route_table_id = aws_route_table.peer[count.index].id +} \ No newline at end of file diff --git a/test/fixtures/partial-subnets/outputs.tf b/test/fixtures/partial-subnets/outputs.tf new file mode 100644 index 0000000..ab7bef8 --- /dev/null +++ b/test/fixtures/partial-subnets/outputs.tf @@ -0,0 +1,39 @@ +output "this_vpc_id" { + value = aws_vpc.this.id +} + +output "peer_vpc_id" { + value = aws_vpc.peer.id +} + +output "this_main_route_table" { + value = data.aws_route_table.this_main_route_table.id +} + +output "peer_main_route_table" { + value = data.aws_route_table.peer_main_route_table.id +} + +output "this_subnet_ids" { + value = aws_subnet.this.*.id +} + +output "peer_subnet_ids" { + value = aws_subnet.peer.*.id +} + +output "this_separate_routes_subnet_ids" { + value = aws_subnet.this_separate_routes.*.id +} + +output "peer_separate_routes_subnet_ids" { + value = aws_subnet.peer_separate_routes.*.id +} + +output "this_route_tables" { + value = aws_route_table.this.*.id +} + +output "peer_route_tables" { + value = aws_route_table.peer.*.id +} \ No newline at end of file diff --git a/test/fixtures/partial-subnets/provider.tf b/test/fixtures/partial-subnets/provider.tf new file mode 100644 index 0000000..f36fb18 --- /dev/null +++ b/test/fixtures/partial-subnets/provider.tf @@ -0,0 +1,13 @@ +provider "aws" { + alias = "this" + region = "eu-west-2" + access_key = var.aws_this_access_key + secret_key = var.aws_this_secret_key +} + +provider "aws" { + alias = "peer" + region = "eu-central-1" + access_key = var.aws_peer_access_key + secret_key = var.aws_peer_secret_key +} diff --git a/test/fixtures/partial-subnets/variables.tf b/test/fixtures/partial-subnets/variables.tf new file mode 100644 index 0000000..693dbaf --- /dev/null +++ b/test/fixtures/partial-subnets/variables.tf @@ -0,0 +1,52 @@ +// Variables +variable "aws_this_access_key" { + description = "AWS Access Key for requester account" +} + +variable "aws_this_secret_key" { + description = "AWS Secret Key for requester account" +} + +variable "aws_peer_access_key" { + description = "AWS Access Key for accepter account" +} + +variable "aws_peer_secret_key" { + description = "AWS Secret Key for accepter account" +} + +variable "this_subnets" { + description = "Subnet list for _this_ VPC" + type = list(string) + default = ["172.20.0.0/24", "172.20.1.0/24", "172.20.2.0/24"] +} + +variable "this_subnets_separate_routes" { + description = "Subnet list for _this_ VPC" + type = list(string) + default = ["172.20.10.0/24", "172.20.11.0/24", "172.20.12.0/24"] +} + +variable "peer_subnets" { + description = "Subnet list for _peer_ VPC" + type = list(string) + default = ["172.21.0.0/24", "172.21.1.0/24", "172.21.2.0/24"] +} + +variable "peer_subnets_separate_routes" { + description = "Subnet list for _peer_ VPC" + type = list(string) + default = ["172.21.10.0/24", "172.21.11.0/24", "172.21.12.0/24"] +} + +variable "azs_this" { + description = "Availability Zones for requester VPC" + type = list(string) + default = ["eu-west-2a", "eu-west-2b", "eu-west-2c"] +} + +variable "azs_peer" { + description = "Availability Zones for accepter VPC" + type = list(string) + default = ["eu-central-1a", "eu-central-1b", "eu-central-1c"] +} diff --git a/test/peering-active_test.go b/test/peering-active_test.go index c8fcd24..4e1c3c8 100644 --- a/test/peering-active_test.go +++ b/test/peering-active_test.go @@ -22,6 +22,7 @@ func TestPeeringActive(t *testing.T) { {"MultiAccountMultiRegion", "./fixtures/multi-account-multi-region", "../examples/multi-account-multi-region"}, {"ModuleDependsOn", "", "../examples/module-depends-on"}, {"AssociatedCIDRs", "./fixtures/associated-cidr", "../examples/associated-cidrs"}, + {"PartialSubnets", "./fixtures/partial-subnets", "../examples/partial-subnets"}, } for _, tc := range testCases {