Skip to content

Commit

Permalink
Merge pull request #76 from lorengordon/tgw
Browse files Browse the repository at this point in the history
  • Loading branch information
lorengordon authored Oct 15, 2020
2 parents aa901fc + 860b455 commit bb89e7d
Show file tree
Hide file tree
Showing 53 changed files with 1,560 additions and 1,105 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.2.1
current_version = 1.0.0
commit = True
message = Bumps version to {new_version}
tag = False
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ tardigrade-ci/
# eclint
.git/

# terratest
tests/go.*
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
SHELL := /bin/bash

-include $(shell curl -sSL -o .tardigrade-ci "https://raw.githubusercontent.com/plus3it/tardigrade-ci/master/bootstrap/Makefile.bootstrap"; echo .tardigrade-ci)
export TIMEOUT=50m

-include $(shell curl -sSL -o .tardigrade-ci "https://raw.githubusercontent.com/plus3it/tardigrade-ci/master/bootstrap/Makefile.bootstrap"; echo .tardigrade-ci)
70 changes: 49 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,75 @@
# terraform-aws-tardigrade-transit-gateway-attachment
# terraform-aws-tardigrade-transit-gateway

This module assumes there are 2 accounts involved in the process. The first account is the `owner` of the transit gateway and
has shared it with the second account. This module will attach a VPC that exists in the second account to the shared transit
gateway and then go into the first account and accept the attachment.
This module will manage a Transit Gateway, as well as its Route Tables, Routes, VPC attachments, Route
Table associations and propagations, and VPC routes associated with the VPC attachments.

## Tests
## Submodules

Given the necessity of two accounts to test this module, the tests assume one of the AWS profiles used for credentials is
called `owner`.
This module includes several submodules for different workflows and use cases.

- [`cross-account-vpc-attachment`](modules/cross-account-vpc-attachment): Creates a cross-account Transit
Gateway VPC Attachment by managing the invite/accept interaction between two accounts. Requires two
providers, one for each account. The providers must be different accounts, and must be using the same
region. The Transit Gateway must be shared using the AWS Resource Access Manager.
- [`cross-region-peering-attachment`](modules/cross-region-peering-attachment):: Creates a cross-region
Peering Attachment, managing the invite/accept workflow between the two regions. Requires two providers,
one for each region. The providers may be the same or different account, but **must** be different
regions.
- [`peering-accepter`](modules/peering-accepter): Accepts a peering attachment request. Used by the
cross-region-peering-attachment module.
- [`peering-attachment`](modules/peering-attachment): Sends a peering attachment invite. Used by the
cross-region-peering-attachment module.
- [`route`](modules/route): Creates a Transit Gateway Route.
- [`route-table`](modules/route-table): Creates a Transit Gateway Route Table.
- [`vpc-accepter`](modules/vpc-accepter): Accepts a VPC attachment request. Used by the cross-account-vpc-attachment
module. Will also the create Transit Gateway Route Table association and propagations for the attachment,
and will manage VPC routes associated with the attachment.
- [`vpc-attachment`](modules/vpc-attachment): Sends a VPC attachment invite. Used by the cross-account-vpc-attachment
module. Will also the create Transit Gateway Route Table association and propagations for the attachment,
and will manage VPC routes associated with the attachment.

<!-- BEGIN TFDOCS -->
## Requirements

No requirements.
| Name | Version |
|------|---------|
| terraform | >= 0.13.0 |

## Providers

| Name | Version |
|------|---------|
| aws | n/a |
| aws.owner | n/a |
| null | n/a |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| create\_tgw\_attachment | Controls whether to create the TGW attachment | `bool` | `true` | no |
| dependencies | List of resource dependencies to force terraform to wait until they are done | `list(string)` | `[]` | no |
| dns\_support | (Optional) Whether DNS support is enabled. Valid values: disable, enable. | `string` | `"enable"` | no |
| name | The name of the TGW attachment for tagging purposes | `string` | `null` | no |
| owner\_routes | List of AWS route objects to create with the "owner" provider. Each route will be created with a target of the transit gateway. | <pre>list(object({<br> route_table_id = string<br> destination_cidr_block = string<br> destination_ipv6_cidr_block = string<br> }))</pre> | `[]` | no |
| routes | List of AWS route objects to create with the "aws" provider. Each route will be created with a target of the transit gateway. | <pre>list(object({<br> route_table_id = string<br> destination_cidr_block = string<br> destination_ipv6_cidr_block = string<br> }))</pre> | `[]` | no |
| subnet\_ids | A list of subnets inside the VPC | `list` | `[]` | no |
| tags | A map of tags to apply to the TGW attachment | `map` | `{}` | no |
| transit\_gateway\_id | The ID of the Transit Gateway | `string` | `null` | no |
| vpc\_id | VPC ID to attach to the TGW | `string` | `null` | no |
| amazon\_side\_asn | Private Autonomous System Number (ASN) for the Amazon side of a BGP session (range is 64512 to 65534 for 16-bit ASNs and 4200000000 to 4294967294 for 32-bit ASN) | `number` | `64512` | no |
| auto\_accept\_shared\_attachments | Whether resource attachment requests are automatically accepted (valid values: disable, enable) | `string` | `"disable"` | no |
| default\_route\_table\_association | Whether resource attachments are automatically associated with the default association route table (valid values: disable, enable) | `string` | `"enable"` | no |
| default\_route\_table\_propagation | Whether resource attachments automatically propagate routes to the default propagation route table (valid values: disable, enable) | `string` | `"enable"` | no |
| description | Description of the EC2 Transit Gateway | `string` | `null` | no |
| dns\_support | Whether DNS support is enabled (valid values: disable, enable) | `string` | `"enable"` | no |
| route\_tables | List of TGW route tables to create with the transit gateway | <pre>list(object({<br> # `name` used as for_each key<br> name = string<br> tags = map(string)<br> }))</pre> | `[]` | no |
| routes | List of TGW routes to add to TGW route tables | <pre>list(object({<br> # `name` used as for_each key<br> name = string<br> blackhole = bool<br> default_route_table = bool<br> destination_cidr_block = string<br> # name from `vpc_attachments` or id of a pre-existing tgw attachment<br> transit_gateway_attachment = string<br> # name from `route_tables` or id of a pre-existing route table<br> transit_gateway_route_table = string<br> }))</pre> | `[]` | no |
| tags | Map of tags to apply to the TGW and associated resources | `map(string)` | `{}` | no |
| vpc\_attachments | List of VPC attachments to create with the transit gateway | <pre>list(object({<br> # `name` used as for_each key<br> name = string<br> subnet_ids = list(string)<br> dns_support = string<br> ipv6_support = string<br> tags = map(string)<br> vpc_routes = list(object({<br> # `name` is used as for_each key<br> name = string<br> route_table_id = string<br> destination_cidr_block = string<br> destination_ipv6_cidr_block = string<br> }))<br> transit_gateway_default_route_table_association = bool<br> transit_gateway_default_route_table_propagation = bool<br> # name from `route_tables` or id of a pre-existing route table<br> transit_gateway_route_table_association = string<br> # list of route table names from `route_tables` or ids of pre-existing route tables<br> transit_gateway_route_table_propagations = list(string)<br> }))</pre> | `[]` | no |
| vpn\_ecmp\_support | Whether VPN Equal Cost Multipath Protocol support is enabled (valid values: disable, enable) | `string` | `"disable"` | no |

## Outputs

| Name | Description |
|------|-------------|
| transit\_gateway\_attachment\_id | The ID of the Transit Gateway Attachment |
| route\_tables | Map of TGW route table objects |
| routes | Map of TGW route objects |
| transit\_gateway | Object with attributes of the Transit Gateway |
| vpc\_attachments | Map of TGW peering attachment objects |

<!-- END TFDOCS -->

## Testing

This module has tests that require multiple providers. In order to simplify the provider config, it
assumes you have AWS Profiles named `resource-owner` and `resource-member`. These profiles should
resolve a credential for two different accounts.
121 changes: 65 additions & 56 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,72 +1,81 @@
provider "aws" {}

provider "aws" {
alias = "owner"
terraform {
required_version = ">= 0.13.0"
}

resource "aws_ec2_transit_gateway_vpc_attachment" "this" {
count = var.create_tgw_attachment ? 1 : 0

provider = aws

subnet_ids = var.subnet_ids
transit_gateway_id = var.transit_gateway_id
vpc_id = var.vpc_id
dns_support = var.dns_support
tags = merge(var.tags, map("Name", var.name))

depends_on = [null_resource.dependencies]
resource aws_ec2_transit_gateway this {
amazon_side_asn = var.amazon_side_asn
auto_accept_shared_attachments = var.auto_accept_shared_attachments
default_route_table_association = var.default_route_table_association
default_route_table_propagation = var.default_route_table_propagation
description = var.description
dns_support = var.dns_support
tags = var.tags
vpn_ecmp_support = var.vpn_ecmp_support
}

resource "aws_ec2_transit_gateway_vpc_attachment_accepter" "this" {
count = local.create_tgw_accepter ? 1 : 0
module route_tables {
source = "./modules/route-table"
for_each = { for route_table in var.route_tables : route_table.name => route_table }

provider = aws.owner
transit_gateway_id = aws_ec2_transit_gateway.this.id

transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.this[0].id
tags = merge(var.tags, map("Name", var.name))
tags = merge(
{ "Name" : each.key },
each.value.tags,
)
}

resource "aws_route" "this" {
count = var.create_tgw_attachment ? length(var.routes) : 0

provider = aws

route_table_id = var.routes[count.index].route_table_id
destination_cidr_block = var.routes[count.index].destination_cidr_block
destination_ipv6_cidr_block = var.routes[count.index].destination_ipv6_cidr_block
transit_gateway_id = local.create_tgw_accepter ? aws_ec2_transit_gateway_vpc_attachment_accepter.this[0].transit_gateway_id : aws_ec2_transit_gateway_vpc_attachment.this[0].transit_gateway_id
module routes {
source = "./modules/route"
for_each = { for route in var.routes : route.name => route }

blackhole = each.value.blackhole
destination_cidr_block = each.value.destination_cidr_block

transit_gateway_route_table_id = each.value.default_route_table ? (
aws_ec2_transit_gateway.this.association_default_route_table_id) : (
try(
module.route_tables[each.value.transit_gateway_route_table].route_table.id,
each.value.transit_gateway_route_table
)
)

transit_gateway_attachment_id = try(
module.vpc_attachments[each.value.transit_gateway_attachment].vpc_attachment.id,
each.value.transit_gateway_attachment
)
}

resource "aws_route" "owner" {
count = var.create_tgw_attachment ? length(var.owner_routes) : 0

provider = aws.owner
module vpc_attachments {
source = "./modules/vpc-attachment"
for_each = { for attachment in var.vpc_attachments : attachment.name => attachment }

route_table_id = var.owner_routes[count.index].route_table_id
destination_cidr_block = var.owner_routes[count.index].destination_cidr_block
destination_ipv6_cidr_block = var.owner_routes[count.index].destination_ipv6_cidr_block
transit_gateway_id = local.create_tgw_accepter ? aws_ec2_transit_gateway_vpc_attachment_accepter.this[0].transit_gateway_id : aws_ec2_transit_gateway_vpc_attachment.this[0].transit_gateway_id
}
subnet_ids = each.value.subnet_ids
transit_gateway_id = aws_ec2_transit_gateway.this.id
dns_support = each.value.dns_support
ipv6_support = each.value.ipv6_support
vpc_routes = each.value.vpc_routes

resource "null_resource" "dependencies" {
count = var.create_tgw_attachment ? 1 : 0
transit_gateway_default_route_table_association = each.value.transit_gateway_default_route_table_association
transit_gateway_default_route_table_propagation = each.value.transit_gateway_default_route_table_propagation

triggers = {
this = join(",", var.dependencies)
transit_gateway_route_table_association = each.value.transit_gateway_route_table_association == null ? null : {
transit_gateway_route_table_id = try(
module.route_tables[each.value.transit_gateway_route_table_association].route_table.id,
each.value.transit_gateway_route_table_association
)
}
}

data "aws_caller_identity" "this" {
count = var.create_tgw_attachment ? 1 : 0
}

data "aws_caller_identity" "owner" {
count = var.create_tgw_attachment ? 1 : 0

provider = aws.owner
}

locals {
create_tgw_accepter = var.create_tgw_attachment ? data.aws_caller_identity.this[0].account_id != data.aws_caller_identity.owner[0].account_id : false
transit_gateway_route_table_propagations = [for route_table in each.value.transit_gateway_route_table_propagations : {
name = route_table
transit_gateway_route_table_id = try(
module.route_tables[route_table].route_table.id,
route_table
)
}]

tags = merge(
{ "Name" : each.key },
each.value.tags,
)
}
45 changes: 45 additions & 0 deletions modules/cross-account-vpc-attachment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# terraform-aws-tardigrade-transit-gateway/cross-account-vpc-attachment

Terraform module for managing a cross-account Transit Gateway VPC Attachment.

<!-- BEGIN TFDOCS -->
## Requirements

No requirements.

## Providers

| Name | Version |
|------|---------|
| aws.owner | n/a |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| ram\_resource\_association\_id | ID of the RAM resource association for the Transit Gateway | `string` | n/a | yes |
| ram\_share\_id | ID of the RAM Share associated with the Transit Gateway | `string` | n/a | yes |
| subnet\_ids | List of subnets to associate with the VPC attachment | `list(string)` | n/a | yes |
| transit\_gateway\_id | ID of the Transit Gateway | `string` | n/a | yes |
| dns\_support | Whether DNS support is enabled. Valid values: disable, enable. | `string` | `"enable"` | no |
| ipv6\_support | Whether IPv6 support is enabled. Valid values: disable, enable | `string` | `"disable"` | no |
| routes | List of TGW route objects with a target of the VPC attachment in the `aws.owner` account (TGW route tables are *only* in the `aws.owner` account) | <pre>list(object({<br> # `name` is used as for_each key<br> name = string<br> destination_cidr_block = string<br> transit_gateway_route_table_id = string<br> }))</pre> | `[]` | no |
| tags | Map of tags to apply to the TGW attachments | `map(string)` | `{}` | no |
| transit\_gateway\_default\_route\_table\_association | Boolean whether the VPC Attachment should be associated to the Transit Gateway default route table | `bool` | `true` | no |
| transit\_gateway\_default\_route\_table\_propagation | Boolean whether the VPC Attachment should propagate routes to the Transit Gateway propagation default route table | `bool` | `true` | no |
| transit\_gateway\_route\_table\_association | ID of the Transit Gateway route table to associate with the VPC attachment (an attachment can be associated with a single TGW route table) | <pre>object({<br> transit_gateway_route_table_id = string<br> })</pre> | `null` | no |
| transit\_gateway\_route\_table\_propagations | List of Transit Gateway route tables this VPC attachment will propagate routes to | <pre>list(object({<br> # `name` is used as for_each key<br> name = string<br> transit_gateway_route_table_id = string<br> }))</pre> | `[]` | no |
| vpc\_routes | List of VPC route objects with a target of the transit gateway. | <pre>list(object({<br> # `name` is used as for_each key<br> name = string<br> provider = string<br> route_table_id = string<br> destination_cidr_block = string<br> destination_ipv6_cidr_block = string<br> }))</pre> | `[]` | no |

## Outputs

| Name | Description |
|------|-------------|
| route\_table\_association | Object with the Transit Gateway route table association attributes |
| route\_table\_propagations | Map of Transit Gateway route table propagation objects |
| routes | Map of Transit Gateway route objects |
| vpc\_accepter | Object with the Transit Gateway VPC attachment accepter attributes |
| vpc\_attachment | Object with the Transit Gateway VPC attachment attributes |
| vpc\_routes | Map of VPC route objects |

<!-- END TFDOCS -->
65 changes: 65 additions & 0 deletions modules/cross-account-vpc-attachment/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
provider aws {
alias = "owner"
}

module vpc_attachment {
source = "../vpc-attachment"

subnet_ids = var.subnet_ids
transit_gateway_id = var.transit_gateway_id
cross_account = true
dns_support = var.dns_support
ipv6_support = var.ipv6_support
vpc_routes = [for route in var.vpc_routes : route if route.provider == "aws"]

tags = merge(
{
ram_share_id = var.ram_share_id
ram_resource_association_id = var.ram_resource_association_id
},
var.tags,
)
}

module vpc_accepter {
source = "../vpc-accepter"
providers = {
aws = aws.owner
}

transit_gateway_attachment_id = module.vpc_attachment.vpc_attachment.id
vpc_routes = [for route in var.vpc_routes : route if route.provider == "aws.owner"]
tags = var.tags

transit_gateway_route_table_association = var.transit_gateway_route_table_association
transit_gateway_route_table_propagations = var.transit_gateway_route_table_propagations

# default assocation and propagation values must be:
# `false` if the transit gateway has no default route table (== "disable")
transit_gateway_default_route_table_association = (
data.aws_ec2_transit_gateway.this.default_route_table_association == "disable") ? false : (
var.transit_gateway_default_route_table_association
)

transit_gateway_default_route_table_propagation = (
data.aws_ec2_transit_gateway.this.default_route_table_propagation == "disable") ? false : (
var.transit_gateway_default_route_table_propagation
)
}

module routes {
source = "../route"
for_each = { for route in var.routes : route.name => route }
providers = {
aws = aws.owner
}

destination_cidr_block = each.value.destination_cidr_block
transit_gateway_attachment_id = module.vpc_accepter.vpc_attachment_accepter.id
transit_gateway_route_table_id = each.value.transit_gateway_route_table_id
}

data aws_ec2_transit_gateway this {
provider = aws.owner
id = var.transit_gateway_id
}
Loading

0 comments on commit bb89e7d

Please sign in to comment.