diff --git a/.kitchen.yml b/.kitchen.yml index ed06b86b..23ea6d2f 100644 --- a/.kitchen.yml +++ b/.kitchen.yml @@ -74,7 +74,7 @@ suites: customized_inspec_attribute: output_subnets_private_access customized_inspec_attribute: output_subnets_regions customized_inspec_attribute: output_subnets_secondary_ranges - customized_inspec_attribute: output_svpc_host_project_id + customized_inspec_attribute: output_project_id backend: local @@ -131,7 +131,7 @@ suites: customized_inspec_attribute: output_subnets_private_access customized_inspec_attribute: output_subnets_regions customized_inspec_attribute: output_subnets_secondary_ranges - customized_inspec_attribute: output_svpc_host_project_id + customized_inspec_attribute: output_project_id backend: local controls: - gcloud diff --git a/CHANGELOG.md b/CHANGELOG.md index 69ce66c7..24c47e68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning][semver-site]. ## [Unreleased] v2.0.0 is a backwards-incompatible release. Please see the [upgrading guide](./docs/upgrading_to_v2.0.md). +### Added + +- Split main module up into vpc, subnets, and routes submodules. [#103] + ### Fixed - Fixes subnet recreation when a subnet is updated. [#73] diff --git a/Makefile b/Makefile index 50d30874..fd4c9220 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ # Make will use bash instead of sh SHELL := /usr/bin/env bash -DOCKER_TAG_VERSION_DEVELOPER_TOOLS := 0.1.0 +DOCKER_TAG_VERSION_DEVELOPER_TOOLS := 0.6.0 DOCKER_IMAGE_DEVELOPER_TOOLS := cft/developer-tools REGISTRY_URL := gcr.io/cloud-foundation-cicd @@ -42,7 +42,7 @@ docker_test_prepare: # Clean up test environment within the docker container .PHONY: docker_test_cleanup -docker_test_prepare: +docker_test_cleanup: docker run --rm -it \ -e SERVICE_ACCOUNT_JSON \ -e TF_VAR_org_id \ diff --git a/README.md b/README.md index e6beea3f..859eb10f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ It supports creating: - Subnets within the VPC - Secondary ranges for the subnets (if applicable) +Sub modules are provided for creating individual vpc, subnets, and routes. See the modules directory for the various sub modules usage. + ## Compatibility This module is meant for use with Terraform 0.12. If you haven't [upgraded](https://www.terraform.io/upgrade-guides/0-12.html) and need a Terraform 0.11.x-compatible version of this module, the last released version intended for Terraform 0.11.x is [0.8.0](https://registry.terraform.io/modules/terraform-google-modules/network/google/0.8.0). @@ -100,7 +102,7 @@ Then perform the following commands on the root folder: | routes | List of routes being created in this VPC | list(map(string)) | `` | no | | routing\_mode | The network routing mode (default 'GLOBAL') | string | `"GLOBAL"` | no | | secondary\_ranges | Secondary ranges that will be used in some of the subnets | object | `` | no | -| shared\_vpc\_host | Makes this project a Shared VPC host if 'true' (default 'false') | string | `"false"` | no | +| shared\_vpc\_host | Makes this project a Shared VPC host if 'true' (default 'false') | bool | `"false"` | no | | subnets | The list of subnets being created | list(map(string)) | n/a | yes | ## Outputs @@ -109,7 +111,8 @@ Then perform the following commands on the root folder: |------|-------------| | network\_name | The name of the VPC being created | | network\_self\_link | The URI of the VPC being created | -| routes | The routes associated with this VPC | +| project\_id | VPC project id | +| route\_names | The route names associated with this VPC | | subnets\_flow\_logs | Whether the subnets will have VPC flow logs enabled | | subnets\_ips | The IPs and CIDRs of the subnets being created | | subnets\_names | The names of the subnets being created | @@ -117,11 +120,11 @@ Then perform the following commands on the root folder: | subnets\_regions | The region where the subnets will be created | | subnets\_secondary\_ranges | The secondary ranges associated with these subnets | | subnets\_self\_links | The self-links of subnets being created | -| svpc\_host\_project\_id | Shared VPC host project id. | ### Subnet Inputs + The subnets list contains maps, where each object represents a subnet. Each map has the following inputs (please see examples folder for additional references): | Name | Description | Type | Default | Required | @@ -133,7 +136,8 @@ The subnets list contains maps, where each object represents a subnet. Each map | subnet\_flow\_logs | Whether the subnet will record and send flow log data to logging | string | `"false"` | no | ### Route Inputs -The routes list contains maps, where each object represents a route. For the next\_hop\_* inputs, only one is possible to be used in each route. Having two next_hop_* inputs will produce an error. Each map has the following inputs (please see examples folder for additional references): + +The routes list contains maps, where each object represents a route. For the next_hop_* inputs, only one is possible to be used in each route. Having two next_hop_* inputs will produce an error. Each map has the following inputs (please see examples folder for additional references): | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index 299ca3e8..b76f495d 100644 --- a/build/int.cloudbuild.yaml +++ b/build/int.cloudbuild.yaml @@ -38,4 +38,4 @@ tags: - 'integration' substitutions: _DOCKER_IMAGE_DEVELOPER_TOOLS: 'cft/developer-tools' - _DOCKER_TAG_VERSION_DEVELOPER_TOOLS: '0.1.0' + _DOCKER_TAG_VERSION_DEVELOPER_TOOLS: '0.6.0' diff --git a/build/lint.cloudbuild.yaml b/build/lint.cloudbuild.yaml index b2d4c569..3f3923fb 100644 --- a/build/lint.cloudbuild.yaml +++ b/build/lint.cloudbuild.yaml @@ -21,4 +21,4 @@ tags: - 'lint' substitutions: _DOCKER_IMAGE_DEVELOPER_TOOLS: 'cft/developer-tools' - _DOCKER_TAG_VERSION_DEVELOPER_TOOLS: '0.1.0' + _DOCKER_TAG_VERSION_DEVELOPER_TOOLS: '0.6.0' diff --git a/docs/upgrading_to_v2.0.md b/docs/upgrading_to_v2.0.md index cb397a0c..cd3514fb 100644 --- a/docs/upgrading_to_v2.0.md +++ b/docs/upgrading_to_v2.0.md @@ -48,7 +48,7 @@ Terraform will perform the following actions: - self_link = "https://www.googleapis.com/compute/v1/projects/dev-xpn-networking/regions/us-west1/subnetworks/simple-project-timh-subnet-02" -> null } - # module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork["us-west1/simple-project-timh-subnet-01"] will be created + # module.example.module.test-vpc-module.google_compute_subnetwork.module.subnets.subnetwork["us-west1/simple-project-timh-subnet-01"] will be created + resource "google_compute_subnetwork" "subnetwork" { + creation_timestamp = (known after apply) + enable_flow_logs = false @@ -65,7 +65,7 @@ Terraform will perform the following actions: + self_link = (known after apply) } - # module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork["us-west1/simple-project-timh-subnet-02"] will be created + # module.example.module.test-vpc-module.module.subnets.google_compute_subnetwork.subnetwork["us-west1/simple-project-timh-subnet-02"] will be created + resource "google_compute_subnetwork" "subnetwork" { + creation_timestamp = (known after apply) + enable_flow_logs = true @@ -93,11 +93,17 @@ can't guarantee that exactly these actions will be performed if ### Manual Migration Steps -In this example here are the two commands used migrate the subnets created by the `simple_project` in the examples directory. _please note the need to escape the quotes on the new resource_. You may also use the migration script. +In this example here are the commands used migrate the vpc and subnets created by the `simple_project` in the examples directory. _please note the need to escape the quotes on the new resource_. You may also use the migration script. -- `terraform state mv module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork[0] module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork[\"us-west1/simple-project-timh-subnet-01\"]` +- `terraform state mv module.example.module.test-vpc-module.google_compute_network.network module.example.module.test-vpc-module.module.vpc.google_compute_subnetwork.network` -- `terraform state mv module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork[1] module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork[\"us-west1/simple-project-timh-subnet-02\"]` +- `terraform state mv module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork module.example.module.test-vpc-module.module.subnets.google_compute_subnetwork.subnetwork` + +- `terraform state mv module.example.module.test-vpc-module.module.subnets.google_compute_subnetwork.subnetwork[0] module.example.module.test-vpc-module.module.subnets.google_compute_subnetwork.subnetwork[\"us-west1/simple-project-timh-subnet-01\"]` + +- `terraform state mv module.example.module.test-vpc-module.module.subnets.google_compute_subnetwork.subnetwork[1] module.example.module.test-vpc-module.module.subnets.google_compute_subnetwork.subnetwork[\"us-west1/simple-project-timh-subnet-02\"]` + +*You'll notice that because of a terraform [issue](https://github.com/hashicorp/terraform/issues/22301), we need to move the whole resource collection first before renaming to the `for_each` keys* `terraform plan` should now return a no-op and show no new changes. @@ -125,26 +131,66 @@ actions need to be performed. 1. Download the script ```sh - curl -O https://raw.githubusercontent.com/terraform-google-modules/terraform-google-network/master/helpers/migrate.sh - chmod +x migrate.sh + curl -O https://raw.githubusercontent.com/terraform-google-modules/terraform-google-network/master/helpers/migrate.py + chmod +x migrate.py ``` 2. Run the script to output the migration commands: ```sh - $ ./migrate.sh --dry-run - terraform state mv module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork[0] module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork[\"us-west1/simple-project-timh-subnet-01\"] - terraform state mv module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork[1] module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork[\"us-west1/simple-project-timh-subnet-02\"] + $ ./migrate.py --dry-run + terraform state mv 'module.example.module.test-vpc-module-02.google_compute_network.network[0]' 'module.example.module.test-vpc-module-02.module.vpc.google_compute_network.network' + terraform state mv 'module.example.module.test-vpc-module-02.google_compute_subnetwork.subnetwork' 'module.example.module.test-vpc-module-02.module.subnets.google_compute_subnetwork.subnetwork' + terraform state mv 'module.example.module.test-vpc-module-02.module.subnets.google_compute_subnetwork.subnetwork[0]' 'module.example.module.test-vpc-module-02.module.subnets.google_compute_subnetwork.subnetwork["us-west1/multi-vpc-a1-02-subnet-01"]' + terraform state mv 'module.example.module.test-vpc-module-02.module.subnets.google_compute_subnetwork.subnetwork[1]' 'module.example.module.test-vpc-module-02.module.subnets.google_compute_subnetwork.subnetwork["us-west1/multi-vpc-a1-02-subnet-02"]' + terraform state mv 'module.example.module.test-vpc-module-02.google_compute_route.route' 'module.example.module.test-vpc-module-02.module.routes.google_compute_route.route' + terraform state mv 'module.example.module.test-vpc-module-02.module.routes.google_compute_route.route[0]' 'module.example.module.test-vpc-module-02.module.routes.google_compute_route.route["multi-vpc-a1-02-egress-inet"]' + terraform state mv 'module.example.module.test-vpc-module-02.module.routes.google_compute_route.route[1]' 'module.example.module.test-vpc-module-02.module.routes.google_compute_route.route["multi-vpc-a1-02-testapp-proxy"]' + ``` 3. Execute the migration command ```sh - $ ./migrate.sh - Move "module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork[0]" to "module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork[\"us-west1/simple-project-timh-subnet-01\"]" + $ ./migrate.py + ---- Migrating the following modules: + -- module.example.module.test-vpc-module-02 + ---- Commands to run: + Move "module.example.module.test-vpc-module-02.google_compute_network.network[0]" to "module.example.module.test-vpc-module-02.module.vpc.google_compute_network.network" + Successfully moved 1 object(s). + Move "module.example.module.test-vpc-module-02.google_compute_subnetwork.subnetwork" to "module.example.module.test-vpc-module-02.module.subnets.google_compute_subnetwork.subnetwork" Successfully moved 1 object(s). - Move "module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork[1]" to "module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork[\"us-west1/simple-project-timh-subnet-02\"]" + Move "module.example.module.test-vpc-module-02.module.subnets.google_compute_subnetwork.subnetwork[0]" to "module.example.module.test-vpc-module-02.module.subnets.google_compute_subnetwork.subnetwork[\"us-west1/multi-vpc-a1-02-subnet-01\"]" Successfully moved 1 object(s). + Move "module.example.module.test-vpc-module-02.module.subnets.google_compute_subnetwork.subnetwork[1]" to "module.example.module.test-vpc-module-02.module.subnets.google_compute_subnetwork.subnetwork[\"us-west1/multi-vpc-a1-02-subnet-02\"]" + Successfully moved 1 object(s). + Move "module.example.module.test-vpc-module-02.google_compute_route.route" to "module.example.module.test-vpc-module-02.module.routes.google_compute_route.route" + Successfully moved 1 object(s). + Move "module.example.module.test-vpc-module-02.module.routes.google_compute_route.route[0]" to "module.example.module.test-vpc-module-02.module.routes.google_compute_route.route[\"multi-vpc-a1-02-egress-inet\"]" + Successfully moved 1 object(s). + Move "module.example.module.test-vpc-module-02.module.routes.google_compute_route.route[1]" to "module.example.module.test-vpc-module-02.module.routes.google_compute_route.route[\"multi-vpc-a1-02-testapp-proxy\"]" + Successfully moved 1 object(s). + ``` 4. Run `terraform plan` to confirm no changes are expected. + +### Known Issues + +If your previous state only contains a **single** subnet or route then `terraform mv` will throw an error similar to the following during migration: + +``` +Error: Invalid target address + +Cannot move to +module.example.module.test-vpc-module-01.module.routes.google_compute_route.route["multi-vpc-a1-01-egress-inet"]: +module.example.module.test-vpc-module-01.module.routes.google_compute_route.route +does not exist in the current state. +``` + +This is due to a terraform mv [issue](https://github.com/hashicorp/terraform/issues/22301) + +The workaround is to either + +1. Create a temporary subnet or route prior to migration +2. Manually updating the state file. Update the `index_key` of the appropriate user and push the to the remote state if necessary. diff --git a/examples/delete_default_gateway_routes/README.md b/examples/delete_default_gateway_routes/README.md index 42052f0d..2735dfb5 100644 --- a/examples/delete_default_gateway_routes/README.md +++ b/examples/delete_default_gateway_routes/README.md @@ -18,7 +18,7 @@ This VPC has a single subnet with no secondary ranges, and ensures the default i |------|-------------| | network\_name | The name of the VPC being created | | network\_self\_link | The URI of the VPC being created | -| routes | The routes associated with this VPC | +| route\_names | The routes associated with this VPC | | subnets\_flow\_logs | Whether the subnets will have VPC flow logs enabled | | subnets\_ips | The IP and cidrs of the subnets being created | | subnets\_names | The names of the subnets being created | diff --git a/examples/delete_default_gateway_routes/outputs.tf b/examples/delete_default_gateway_routes/outputs.tf index 429e0b97..d7a27ff4 100644 --- a/examples/delete_default_gateway_routes/outputs.tf +++ b/examples/delete_default_gateway_routes/outputs.tf @@ -54,7 +54,7 @@ output "subnets_secondary_ranges" { description = "The secondary ranges associated with these subnets" } -output "routes" { - value = module.test-vpc-module.routes +output "route_names" { + value = module.test-vpc-module.route_names description = "The routes associated with this VPC" } diff --git a/examples/multi_vpc/outputs.tf b/examples/multi_vpc/outputs.tf index d389a4b6..c2d6a828 100644 --- a/examples/multi_vpc/outputs.tf +++ b/examples/multi_vpc/outputs.tf @@ -56,7 +56,7 @@ output "network_01_subnets_secondary_ranges" { } output "network_01_routes" { - value = module.test-vpc-module-01.routes + value = module.test-vpc-module-01.route_names description = "The routes associated with network-01" } @@ -102,6 +102,6 @@ output "network_02_subnets_secondary_ranges" { } output "network_02_routes" { - value = module.test-vpc-module-02.routes + value = module.test-vpc-module-02.route_names description = "The routes associated with network-02" } diff --git a/examples/secondary_ranges/README.md b/examples/secondary_ranges/README.md index 3ffc4ec3..acca7c73 100644 --- a/examples/secondary_ranges/README.md +++ b/examples/secondary_ranges/README.md @@ -19,13 +19,13 @@ ranges and the third being given a single secondary range. |------|-------------| | network\_name | The name of the VPC being created | | network\_self\_link | The URI of the VPC being created | -| routes | The routes associated with this VPC | +| project\_id | VPC project id | +| route\_names | The routes associated with this VPC | | subnets\_flow\_logs | Whether the subnets will have VPC flow logs enabled | | subnets\_ips | The IP and cidrs of the subnets being created | | subnets\_names | The names of the subnets being created | | subnets\_private\_access | Whether the subnets will have access to Google API's without a public IP | | subnets\_regions | The region where subnets will be created | | subnets\_secondary\_ranges | The secondary ranges associated with these subnets | -| svpc\_host\_project\_id | Shared VPC host project id. | diff --git a/examples/secondary_ranges/outputs.tf b/examples/secondary_ranges/outputs.tf index 95d5b63e..6c3f49cb 100644 --- a/examples/secondary_ranges/outputs.tf +++ b/examples/secondary_ranges/outputs.tf @@ -24,9 +24,9 @@ output "network_self_link" { description = "The URI of the VPC being created" } -output "svpc_host_project_id" { - value = module.vpc-secondary-ranges.svpc_host_project_id - description = "Shared VPC host project id." +output "project_id" { + value = module.vpc-secondary-ranges.project_id + description = "VPC project id" } output "subnets_names" { @@ -59,7 +59,7 @@ output "subnets_secondary_ranges" { description = "The secondary ranges associated with these subnets" } -output "routes" { - value = module.vpc-secondary-ranges.routes +output "route_names" { + value = module.vpc-secondary-ranges.route_names description = "The routes associated with this VPC" } diff --git a/examples/simple_project/README.md b/examples/simple_project/README.md index 3f6cb802..a4325668 100644 --- a/examples/simple_project/README.md +++ b/examples/simple_project/README.md @@ -18,13 +18,13 @@ This VPC has two subnets, with no secondary ranges. |------|-------------| | network\_name | The name of the VPC being created | | network\_self\_link | The URI of the VPC being created | -| routes | The routes associated with this VPC | +| project\_id | VPC project id | +| route\_names | The routes associated with this VPC | | subnets\_flow\_logs | Whether the subnets will have VPC flow logs enabled | | subnets\_ips | The IP and cidrs of the subnets being created | | subnets\_names | The names of the subnets being created | | subnets\_private\_access | Whether the subnets will have access to Google API's without a public IP | | subnets\_regions | The region where subnets will be created | | subnets\_secondary\_ranges | The secondary ranges associated with these subnets | -| svpc\_host\_project\_id | Shared VPC host project id. | diff --git a/examples/simple_project/outputs.tf b/examples/simple_project/outputs.tf index b5f631cc..f69ae043 100644 --- a/examples/simple_project/outputs.tf +++ b/examples/simple_project/outputs.tf @@ -24,9 +24,9 @@ output "network_self_link" { description = "The URI of the VPC being created" } -output "svpc_host_project_id" { - value = module.test-vpc-module.svpc_host_project_id - description = "Shared VPC host project id." +output "project_id" { + value = module.test-vpc-module.project_id + description = "VPC project id" } output "subnets_names" { @@ -59,7 +59,7 @@ output "subnets_secondary_ranges" { description = "The secondary ranges associated with these subnets" } -output "routes" { - value = module.test-vpc-module.routes +output "route_names" { + value = module.test-vpc-module.route_names description = "The routes associated with this VPC" } diff --git a/examples/simple_project_with_regional_network/README.md b/examples/simple_project_with_regional_network/README.md index 445f3111..354711e2 100644 --- a/examples/simple_project_with_regional_network/README.md +++ b/examples/simple_project_with_regional_network/README.md @@ -18,13 +18,13 @@ This VPC has two subnets, with no secondary ranges. |------|-------------| | network\_name | The name of the VPC being created | | network\_self\_link | The URI of the VPC being created | -| routes | The routes associated with this VPC | +| project\_id | VPC project id | +| route\_names | The routes associated with this VPC | | subnets\_flow\_logs | Whether the subnets will have VPC flow logs enabled | | subnets\_ips | The IP and cidrs of the subnets being created | | subnets\_names | The names of the subnets being created | | subnets\_private\_access | Whether the subnets will have access to Google API's without a public IP | | subnets\_regions | The region where subnets will be created | | subnets\_secondary\_ranges | The secondary ranges associated with these subnets | -| svpc\_host\_project\_id | Shared VPC host project id. | diff --git a/examples/simple_project_with_regional_network/outputs.tf b/examples/simple_project_with_regional_network/outputs.tf index b5f631cc..f69ae043 100644 --- a/examples/simple_project_with_regional_network/outputs.tf +++ b/examples/simple_project_with_regional_network/outputs.tf @@ -24,9 +24,9 @@ output "network_self_link" { description = "The URI of the VPC being created" } -output "svpc_host_project_id" { - value = module.test-vpc-module.svpc_host_project_id - description = "Shared VPC host project id." +output "project_id" { + value = module.test-vpc-module.project_id + description = "VPC project id" } output "subnets_names" { @@ -59,7 +59,7 @@ output "subnets_secondary_ranges" { description = "The secondary ranges associated with these subnets" } -output "routes" { - value = module.test-vpc-module.routes +output "route_names" { + value = module.test-vpc-module.route_names description = "The routes associated with this VPC" } diff --git a/examples/submodule_firewall/README.md b/examples/submodule_firewall/README.md index faf52212..48f2bd1c 100644 --- a/examples/submodule_firewall/README.md +++ b/examples/submodule_firewall/README.md @@ -20,13 +20,13 @@ This VPC has two subnets, with no secondary ranges. | internal\_ranges | Firewall attributes for internal ranges. | | network\_name | The name of the VPC being created | | network\_self\_link | The URI of the VPC being created | -| routes | The routes associated with this VPC | +| project\_id | VPC project id | +| route\_names | The routes associated with this VPC | | subnets\_flow\_logs | Whether the subnets will have VPC flow logs enabled | | subnets\_ips | The IP and cidrs of the subnets being created | | subnets\_names | The names of the subnets being created | | subnets\_private\_access | Whether the subnets will have access to Google API's without a public IP | | subnets\_regions | The region where subnets will be created | | subnets\_secondary\_ranges | The secondary ranges associated with these subnets | -| svpc\_host\_project\_id | Shared VPC host project id. | diff --git a/examples/submodule_firewall/outputs.tf b/examples/submodule_firewall/outputs.tf index 0586334f..182dc845 100644 --- a/examples/submodule_firewall/outputs.tf +++ b/examples/submodule_firewall/outputs.tf @@ -34,9 +34,9 @@ output "network_self_link" { description = "The URI of the VPC being created" } -output "svpc_host_project_id" { - value = module.test-vpc-module.svpc_host_project_id - description = "Shared VPC host project id." +output "project_id" { + value = module.test-vpc-module.project_id + description = "VPC project id" } output "subnets_names" { @@ -69,7 +69,7 @@ output "subnets_secondary_ranges" { description = "The secondary ranges associated with these subnets" } -output "routes" { - value = module.test-vpc-module.routes +output "route_names" { + value = module.test-vpc-module.route_names description = "The routes associated with this VPC" } diff --git a/examples/submodule_svpc_access/main.tf b/examples/submodule_svpc_access/main.tf index dc0777bb..edb65bb1 100644 --- a/examples/submodule_svpc_access/main.tf +++ b/examples/submodule_svpc_access/main.tf @@ -25,7 +25,7 @@ module "net-vpc-shared" { source = "../.." project_id = var.host_project_id network_name = var.network_name - shared_vpc_host = "true" + shared_vpc_host = true subnets = [ { @@ -43,7 +43,7 @@ module "net-vpc-shared" { module "net-svpc-access" { source = "../../modules/fabric-net-svpc-access" - host_project_id = module.net-vpc-shared.svpc_host_project_id + host_project_id = module.net-vpc-shared.project_id service_project_num = 1 service_project_ids = [var.service_project_id] host_subnets = ["data"] diff --git a/helpers/migrate.py b/helpers/migrate.py new file mode 100755 index 00000000..ca3ae89b --- /dev/null +++ b/helpers/migrate.py @@ -0,0 +1,422 @@ +#!/usr/bin/env python3 + +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import copy +import subprocess +import sys +import re +import json + +MIGRATIONS = [ + { + "resource_type": "google_compute_network", + "name": "network", + "module": ".module.vpc", + "new_plural": False + }, + { + "resource_type": "google_compute_shared_vpc_host_project", + "name": "shared_vpc_host", + "module": ".module.vpc" + }, + { + "resource_type": "google_compute_subnetwork", + "name": "subnetwork", + "module": ".module.subnets", + "for_each_migration": True, + "for_each_migration_key": "id" + }, + { + "resource_type": "google_compute_route", + "name": "route", + "module": ".module.routes", + "for_each_migration": True, + "for_each_migration_key": "id" + }, + { + "resource_type": "null_resource", + "name": "delete_default_internet_gateway_routes", + "module": ".module.routes" + } +] + + +class ModuleMigration: + """ + Migrate the resources from a flat project factory to match the new + module structure created by the G Suite refactor. + """ + + def __init__(self, source_module, state): + self.source_module = source_module + self.state = state + + def moves(self): + """ + Generate the set of old/new resource pairs that will be migrated + to the `destination` module. + """ + resources = self.targets() + for_each_migrations = [] + + moves = [] + for (old, migration) in resources: + new = copy.deepcopy(old) + new.module += migration["module"] + + # Update the copied resource with the "rename" value if it is set + if "rename" in migration: + new.name = migration["rename"] + + old.plural = migration.get("old_plural", True) + new.plural = migration.get("new_plural", True) + + if (migration.get("for_each_migration", False) and + migration.get("old_plural", True)): + for_each_migrations.append((old, new, migration)) + else: + pair = (old.path(), new.path()) + moves.append(pair) + + for_each_moves = self.for_each_moves(for_each_migrations) + return moves + for_each_moves + + def for_each_moves(self, for_each_migrations): + """ + When migrating from count to for_each we need to move the + whole collection first + https://github.com/hashicorp/terraform/issues/22301 + """ + for_each_initial_migration = {} + moves = [] + + for (old, new, migration) in for_each_migrations: + # Do the initial migration of the whole collection + # only once if it hasn't been done yet + key = old.resource_type + "." + old.name + if key not in for_each_initial_migration: + for_each_initial_migration[key] = True + old.plural = False + new.plural = False + + pair = (old.path(), new.path()) + moves.append(pair) + + # Whole collection is moved to new location. Now needs right index + new.plural = True + new_indexed = copy.deepcopy(new) + new_indexed.key = self.state.resource_value( + old, migration["for_each_migration_key"]) + pair = (new.path(), new_indexed.path()) + moves.append(pair) + + return moves + + def targets(self): + """ + A list of resources that will be moved to the new module """ + to_move = [] + + for migration in MIGRATIONS: + resource_type = migration["resource_type"] + resource_name = migration["name"] + matching_resources = self.source_module.get_resources( + resource_type, + resource_name) + to_move += [(r, migration) for r in matching_resources] + + return to_move + + +class TerraformModule: + """ + A Terraform module with associated resources. + """ + + def __init__(self, name, resources): + """ + Create a new module and associate it with a list of resources. + """ + self.name = name + self.resources = resources + + def get_resources(self, resource_type=None, resource_name=None): + """ + Return a list of resources matching the given resource type and name. + """ + + ret = [] + for resource in self.resources: + matches_type = (resource_type is None or + resource_type == resource.resource_type) + + name_pattern = re.compile(r'%s(\[\d+\])?' % resource_name) + matches_name = (resource_name is None or + name_pattern.match(resource.name)) + + if matches_type and matches_name: + ret.append(resource) + + return ret + + def has_resource(self, resource_type=None, resource_name=None): + """ + Does this module contain a resource with the matching type and name? + """ + for resource in self.resources: + matches_type = (resource_type is None or + resource_type == resource.resource_type) + + matches_name = (resource_name is None or + resource_name in resource.name) + + if matches_type and matches_name: + return True + + return False + + def __repr__(self): + return "{}({!r}, {!r})".format( + self.__class__.__name__, + self.name, + [repr(resource) for resource in self.resources]) + + +class TerraformResource: + """ + A Terraform resource, defined by the the identifier of that resource. + """ + + @classmethod + def from_path(cls, path): + """ + Generate a new Terraform resource, based on the fully qualified + Terraform resource path. + """ + if re.match(r'\A[\w.\["/\]-]+\Z', path) is None: + raise ValueError( + "Invalid Terraform resource path {!r}".format(path)) + + parts = path.split(".") + name = parts.pop() + resource_type = parts.pop() + module = ".".join(parts) + return cls(module, resource_type, name) + + def __init__(self, module, resource_type, name): + """ + Create a new TerraformResource from a pre-parsed path. + """ + self.module = module + self.resource_type = resource_type + self.key = None + self.plural = True + + find_suffix = re.match(r'(^.+)\[(\d+)\]', name) + if find_suffix: + self.name = find_suffix.group(1) + self.index = find_suffix.group(2) + else: + self.name = name + self.index = -1 + + def path(self): + """ + Return the fully qualified resource path. + """ + parts = [self.module, self.resource_type, self.name] + if parts[0] == '': + del parts[0] + path = ".".join(parts) + if self.key is not None: + path = "{0}[\"{1}\"]".format(path, self.key) + elif self.index != -1 and self.plural: + path = "{0}[{1}]".format(path, self.index) + return path + + def __repr__(self): + return "{}({!r}, {!r}, {!r})".format( + self.__class__.__name__, + self.module, + self.resource_type, + self.name) + + +class TerraformState: + """ + A Terraform state representation, pulled from terraform state pull + Used for getting values out of individual resources + """ + + def __init__(self): + self.read_state() + + def read_state(self): + """ + Read the terraform state + """ + argv = ["terraform", "state", "pull"] + result = subprocess.run(argv, + capture_output=True, + check=True, + encoding='utf-8') + + self.state = json.loads(result.stdout) + + def resource_value(self, resource, key): + # Find the resource in the state + state_resource_list = [r for r in self.state["resources"] if + r["module"] == resource.module and + r["type"] == resource.resource_type and + r["name"] == resource.name] + + if (len(state_resource_list) != 1): + raise ValueError( + "Could not find resource list in state for {}" + .format(resource)) + + index = int(resource.index) + # If this a collection use the index to find the right resource, + # otherwise use the first + if (index >= 0): + state_resource = [r for r in state_resource_list[0]["instances"] if + r["index_key"] == index] + + if (len(state_resource) != 1): + raise ValueError( + "Could not find resource in state for {} key {}" + .format(resource, resource.index)) + else: + state_resource = state_resource_list[0]["instances"] + + return state_resource[0]["attributes"][key] + + +def group_by_module(resources): + """ + Group a set of resources according to their containing module. + """ + + groups = {} + for resource in resources: + if resource.module in groups: + groups[resource.module].append(resource) + else: + groups[resource.module] = [resource] + + return [ + TerraformModule(name, contained) + for name, contained in groups.items() + ] + + +def read_resources(): + """ + Read the terraform state at the given path. + """ + argv = ["terraform", "state", "list"] + result = subprocess.run(argv, + capture_output=True, + check=True, + encoding='utf-8') + elements = result.stdout.split("\n") + elements.pop() + return elements + + +def state_changes_for_module(module, state): + """ + Compute the Terraform state changes (deletions and moves) for a single + module. + """ + commands = [] + + migration = ModuleMigration(module, state) + + for (old, new) in migration.moves(): + wrapper = "'{0}'" + argv = ["terraform", + "state", + "mv", + wrapper.format(old), + wrapper.format(new)] + commands.append(argv) + + return commands + + +def migrate(state=None, dryrun=False): + """ + Generate and run terraform state mv commands to migrate resources from one + state structure to another + """ + + # Generate a list of Terraform resource states from the output of + # `terraform state list` + resources = [ + TerraformResource.from_path(path) + for path in read_resources() + ] + + # Group resources based on the module where they're defined. + modules = group_by_module(resources) + + # Filter our list of Terraform modules down to anything that looks like a + # google network original module. We key this off the presence off of + # `terraform-google-network` resource type and names + modules_to_migrate = [ + module for module in modules + if module.has_resource("google_compute_network", "network") + ] + + print("---- Migrating the following modules:") + for module in modules_to_migrate: + print("-- " + module.name) + + # Collect a list of resources for each module + commands = [] + for module in modules_to_migrate: + commands += state_changes_for_module(module, state) + + print("---- Commands to run:") + for argv in commands: + if dryrun: + print(" ".join(argv)) + else: + argv = [arg.strip("'") for arg in argv] + subprocess.run(argv, check=True, encoding='utf-8') + + +def main(argv): + parser = argparser() + args = parser.parse_args(argv[1:]) + + state = TerraformState() + + migrate(state=state, dryrun=args.dryrun) + + +def argparser(): + parser = argparse.ArgumentParser(description='Migrate Terraform state') + parser.add_argument('--dryrun', action='store_true', + help='Print the `terraform state mv` commands instead ' + 'of running the commands.') + return parser + + +if __name__ == "__main__": + main(sys.argv) diff --git a/helpers/migrate.sh b/helpers/migrate.sh deleted file mode 100755 index 752fc06a..00000000 --- a/helpers/migrate.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash -# Copyright 2019 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# shellcheck shell=bash -# Output Terraform Commands to migrate to new subnet config -set -e -set -o pipefail -CMD="terraform state" - -while (( "$#" )); do - # shellcheck disable=SC2221,SC2222 - case "$1" in - -d|--dry-run) - DRY_RUN=true - shift 1 - ;; - --) # end argument parsing - shift - break - ;; - -*|--*=) # unsupported flags - echo "Error: Unsupported flag $1" >&2 - exit 1 - ;; - *) # preserve positional arguments - PARAMS="$PARAMS $1" - shift - ;; - esac -done - -eval set -- "$PARAMS" - -if [ ! -e "$(command -v terraform)" ]; then - echo "can not find terraform" - exit 1 -fi - -MODULES=$(${CMD} list | grep google_compute_network.network) -for module in $MODULES; do - NAME=$(sed 's/.google_compute_network.network//' <<<"${module}") - for x in $($CMD list | grep "${NAME}".google_compute_subnetwork.subnetwork); do - ID=$(${CMD} show "$x" | grep id | grep -v ip_cidr_range | awk '{ print $3 }'| tr -d '"') - if [[ $DRY_RUN ]]; then - echo "${CMD} mv $x ${NAME}.google_compute_subnetwork.subnetwork[\\\"${ID}\\\"]" - else - ${CMD} mv "$x" "${NAME}".google_compute_subnetwork.subnetwork[\""${ID}"\"] - fi - done -done diff --git a/main.tf b/main.tf index e6057d9e..93794145 100644 --- a/main.tf +++ b/main.tf @@ -14,107 +14,38 @@ * limitations under the License. */ -locals { - subnets = { - for x in var.subnets : - "${x.subnet_region}/${x.subnet_name}" => x - } -} - /****************************************** VPC configuration *****************************************/ -resource "google_compute_network" "network" { - name = var.network_name +module "vpc" { + source = "./modules/vpc" + network_name = var.network_name auto_create_subnetworks = var.auto_create_subnetworks routing_mode = var.routing_mode - project = var.project_id + project_id = var.project_id description = var.description -} - -/****************************************** - Shared VPC - *****************************************/ -resource "google_compute_shared_vpc_host_project" "shared_vpc_host" { - count = var.shared_vpc_host == "true" ? 1 : 0 - project = var.project_id - depends_on = [google_compute_network.network] + shared_vpc_host = var.shared_vpc_host } /****************************************** Subnet configuration *****************************************/ -resource "google_compute_subnetwork" "subnetwork" { - for_each = local.subnets - name = each.value.subnet_name - ip_cidr_range = each.value.subnet_ip - region = each.value.subnet_region - private_ip_google_access = lookup(each.value, "subnet_private_access", "false") - dynamic "log_config" { - for_each = lookup(each.value, "subnet_flow_logs", false) ? [{ - aggregation_interval = lookup(each.value, "subnet_flow_logs_interval", null) - flow_sampling = lookup(each.value, "subnet_flow_logs_sampling", null) - metadata = lookup(each.value, "subnet_flow_logs_metadata", null) - }] : [] - content { - aggregation_interval = log_config.value.aggregation_interval - flow_sampling = log_config.value.flow_sampling - metadata = log_config.value.metadata - } - } - network = google_compute_network.network.name - project = var.project_id - description = lookup(each.value, "description", null) - secondary_ip_range = [ - for i in range( - length( - contains( - keys(var.secondary_ranges), each.value.subnet_name) == true - ? var.secondary_ranges[each.value.subnet_name] - : [] - )) : - var.secondary_ranges[each.value.subnet_name][i] - ] +module "subnets" { + source = "./modules/subnets" + project_id = var.project_id + network_name = module.vpc.network_name + subnets = var.subnets + secondary_ranges = var.secondary_ranges } /****************************************** Routes *****************************************/ -resource "google_compute_route" "route" { - count = length(var.routes) - project = var.project_id - network = var.network_name - name = lookup(var.routes[count.index], "name", format("%s-%s-%d", lower(var.network_name), "route", count.index)) - description = lookup(var.routes[count.index], "description", "") - tags = compact(split(",", lookup(var.routes[count.index], "tags", ""))) - dest_range = lookup(var.routes[count.index], "destination_range", "") - next_hop_gateway = lookup(var.routes[count.index], "next_hop_internet", "false") == "true" ? "default-internet-gateway" : "" - next_hop_ip = lookup(var.routes[count.index], "next_hop_ip", "") - next_hop_instance = lookup(var.routes[count.index], "next_hop_instance", "") - next_hop_instance_zone = lookup(var.routes[count.index], "next_hop_instance_zone", "") - next_hop_vpn_tunnel = lookup(var.routes[count.index], "next_hop_vpn_tunnel", "") - priority = lookup(var.routes[count.index], "priority", "1000") - - depends_on = [ - google_compute_network.network, - google_compute_subnetwork.subnetwork, - ] -} - -resource "null_resource" "delete_default_internet_gateway_routes" { - count = var.delete_default_internet_gateway_routes ? 1 : 0 - - provisioner "local-exec" { - command = "${path.module}/scripts/delete-default-gateway-routes.sh ${var.project_id} ${var.network_name}" - } - - triggers = { - number_of_routes = length(var.routes) - } - - depends_on = [ - google_compute_network.network, - google_compute_subnetwork.subnetwork, - google_compute_route.route, - ] +module "routes" { + source = "./modules/routes" + project_id = var.project_id + network_name = module.vpc.network_name + routes = var.routes + delete_default_internet_gateway_routes = var.delete_default_internet_gateway_routes + module_depends_on = [module.subnets.subnets] } diff --git a/modules/fabric-net-firewall/README.md b/modules/fabric-net-firewall/README.md index f34555ef..3f0228cd 100644 --- a/modules/fabric-net-firewall/README.md +++ b/modules/fabric-net-firewall/README.md @@ -74,7 +74,7 @@ module "net-firewall" { |------|-------------|:----:|:-----:|:-----:| | admin\_ranges | IP CIDR ranges that have complete access to all subnets. | list | `` | no | | admin\_ranges\_enabled | Enable admin ranges-based rules. | string | `"false"` | no | -| custom\_rules | List of custom rule definitions (refer to variables file for syntax). | map | `` | no | +| custom\_rules | List of custom rule definitions (refer to variables file for syntax). | object | `` | no | | http\_source\_ranges | List of IP CIDR ranges for tag-based HTTP rule, defaults to 0.0.0.0/0. | list | `` | no | | https\_source\_ranges | List of IP CIDR ranges for tag-based HTTPS rule, defaults to 0.0.0.0/0. | list | `` | no | | internal\_allow | Allow rules for internal ranges. | list | `` | no | @@ -89,6 +89,10 @@ module "net-firewall" { | Name | Description | |------|-------------| | admin\_ranges | Admin ranges data. | +| custom\_egress\_allow\_rules | Custom egress rules with allow blocks. | +| custom\_egress\_deny\_rules | Custom egress rules with allow blocks. | +| custom\_ingress\_allow\_rules | Custom ingress rules with allow blocks. | +| custom\_ingress\_deny\_rules | Custom ingress rules with deny blocks. | | internal\_ranges | Internal ranges. | diff --git a/modules/fabric-net-firewall/versions.tf b/modules/fabric-net-firewall/versions.tf index 1fe4caaa..5b19897c 100644 --- a/modules/fabric-net-firewall/versions.tf +++ b/modules/fabric-net-firewall/versions.tf @@ -16,4 +16,7 @@ terraform { required_version = "~> 0.12.0" + required_providers { + google = "~> 2.19.0" + } } diff --git a/modules/fabric-net-svpc-access/versions.tf b/modules/fabric-net-svpc-access/versions.tf index 1fe4caaa..5b19897c 100644 --- a/modules/fabric-net-svpc-access/versions.tf +++ b/modules/fabric-net-svpc-access/versions.tf @@ -16,4 +16,7 @@ terraform { required_version = "~> 0.12.0" + required_providers { + google = "~> 2.19.0" + } } diff --git a/modules/routes/README.md b/modules/routes/README.md new file mode 100644 index 00000000..8051ac5d --- /dev/null +++ b/modules/routes/README.md @@ -0,0 +1,79 @@ +# Terraform Network Module + +This submodule is part of the the `terraform-google-network` module. It creates the individual vpc routes and optionally deletes the default internet gateway routes. + +It supports creating: + +- Routes within vpc network. +- Optionally deletes the default internet gateway routes. + +## Usage + +Basic usage of this submodule is as follows: + +```hcl +module "vpc" { + source = "terraform-google-modules/network/google//modules/routes" + version = "~> 2.0.0" + + project_id = "" + network_name = "example-vpc" + + delete_default_internet_gateway_routes = false + + routes = [ + { + name = "egress-internet" + description = "route through IGW to access internet" + destination_range = "0.0.0.0/0" + tags = "egress-inet" + next_hop_internet = "true" + }, + { + name = "app-proxy" + description = "route through proxy to reach app" + destination_range = "10.50.10.0/24" + tags = "app-proxy" + next_hop_instance = "app-proxy-instance" + next_hop_instance_zone = "us-west1-a" + }, + ] +} +``` + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| delete\_default\_internet\_gateway\_routes | If set, ensure that all routes within the network specified whose names begin with 'default-route' and with a next hop of 'default-internet-gateway' are deleted | string | `"false"` | no | +| module\_depends\_on | List of modules or resources this module depends on. | list | `` | no | +| network\_name | The name of the network where routes will be created | string | n/a | yes | +| project\_id | The ID of the project where the routes will be created | string | n/a | yes | +| routes | List of routes being created in this VPC | list(map(string)) | `` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| routes | The created routes resources | + + + + +### Routes Input + +The routes list contains maps, where each object represents a route. For the next_hop_* inputs, only one is possible to be used in each route. Having two next_hop_* inputs will produce an error. Each map has the following inputs (please see examples folder for additional references): + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| name | The name of the route being created | string | - | no | +| description | The description of the route being created | string | - | no | +| tags | The network tags assigned to this route. This is a list in string format. Eg. "tag-01,tag-02"| string | - | yes | +| destination\_range | The destination range of outgoing packets that this route applies to. Only IPv4 is supported | string | - | yes +| next\_hop\_internet | Whether the next hop to this route will the default internet gateway. Use "true" to enable this as next hop | string | `"false"` | yes | +| next\_hop\_ip | Network IP address of an instance that should handle matching packets | string | - | yes | +| next\_hop\_instance | URL or name of an instance that should handle matching packets. If just name is specified "next\_hop\_instance\_zone" is required | string | - | yes | +| next\_hop\_instance\_zone | The zone of the instance specified in next\_hop\_instance. Only required if next\_hop\_instance is specified as a name | string | - | no | +| next\_hop\_vpn\_tunnel | URL to a VpnTunnel that should handle matching packets | string | - | yes | +| priority | The priority of this route. Priority is used to break ties in cases where there is more than one matching route of equal prefix length. In the case of two routes with equal prefix length, the one with the lowest-numbered priority value wins | string | `"1000"` | yes | diff --git a/modules/routes/main.tf b/modules/routes/main.tf new file mode 100644 index 00000000..cb1edf31 --- /dev/null +++ b/modules/routes/main.tf @@ -0,0 +1,61 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + routes = { + for i, route in var.routes : + lookup(route, "name", format("%s-%s-%d", lower(var.network_name), "route", i)) => route + } +} + +/****************************************** + Routes + *****************************************/ +resource "google_compute_route" "route" { + for_each = local.routes + + project = var.project_id + network = var.network_name + + name = each.key + description = lookup(each.value, "description", null) + tags = compact(split(",", lookup(each.value, "tags", ""))) + dest_range = lookup(each.value, "destination_range", null) + next_hop_gateway = lookup(each.value, "next_hop_internet", "false") == "true" ? "default-internet-gateway" : "" + next_hop_ip = lookup(each.value, "next_hop_ip", null) + next_hop_instance = lookup(each.value, "next_hop_instance", null) + next_hop_instance_zone = lookup(each.value, "next_hop_instance_zone", null) + next_hop_vpn_tunnel = lookup(each.value, "next_hop_vpn_tunnel", null) + priority = lookup(each.value, "priority", null) + + depends_on = [var.module_depends_on] +} + +resource "null_resource" "delete_default_internet_gateway_routes" { + count = var.delete_default_internet_gateway_routes ? 1 : 0 + + provisioner "local-exec" { + command = "${path.module}/scripts/delete-default-gateway-routes.sh ${var.project_id} ${var.network_name}" + } + + triggers = { + number_of_routes = length(var.routes) + } + + depends_on = [ + google_compute_route.route, + ] +} diff --git a/modules/routes/outputs.tf b/modules/routes/outputs.tf new file mode 100644 index 00000000..0f672ec6 --- /dev/null +++ b/modules/routes/outputs.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "routes" { + value = google_compute_route.route + description = "The created routes resources" +} diff --git a/scripts/delete-default-gateway-routes.sh b/modules/routes/scripts/delete-default-gateway-routes.sh similarity index 100% rename from scripts/delete-default-gateway-routes.sh rename to modules/routes/scripts/delete-default-gateway-routes.sh diff --git a/modules/routes/variables.tf b/modules/routes/variables.tf new file mode 100644 index 00000000..8eed495f --- /dev/null +++ b/modules/routes/variables.tf @@ -0,0 +1,40 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "project_id" { + description = "The ID of the project where the routes will be created" +} + +variable "network_name" { + description = "The name of the network where routes will be created" +} + +variable "routes" { + type = list(map(string)) + description = "List of routes being created in this VPC" + default = [] +} + +variable "delete_default_internet_gateway_routes" { + description = "If set, ensure that all routes within the network specified whose names begin with 'default-route' and with a next hop of 'default-internet-gateway' are deleted" + default = "false" +} + +variable "module_depends_on" { + description = "List of modules or resources this module depends on." + type = list + default = [] +} diff --git a/modules/routes/versions.tf b/modules/routes/versions.tf new file mode 100644 index 00000000..5b19897c --- /dev/null +++ b/modules/routes/versions.tf @@ -0,0 +1,22 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_version = "~> 0.12.0" + required_providers { + google = "~> 2.19.0" + } +} diff --git a/modules/subnets/README.md b/modules/subnets/README.md new file mode 100644 index 00000000..d3d764fc --- /dev/null +++ b/modules/subnets/README.md @@ -0,0 +1,87 @@ +# Terraform Network Module + +This submodule is part of the the `terraform-google-network` module. It creates the individual vpc subnets. + +It supports creating: + +- Subnets within vpc network. + +## Usage + +Basic usage of this submodule is as follows: + +```hcl +module "vpc" { + source = "terraform-google-modules/network/google//modules/subnets" + version = "~> 2.0.0" + + project_id = "" + network_name = "example-vpc" + + subnets = [ + { + subnet_name = "subnet-01" + subnet_ip = "10.10.10.0/24" + subnet_region = "us-west1" + }, + { + subnet_name = "subnet-02" + subnet_ip = "10.10.20.0/24" + subnet_region = "us-west1" + subnet_private_access = "true" + subnet_flow_logs = "true" + description = "This subnet has a description" + }, + { + subnet_name = "subnet-03" + subnet_ip = "10.10.30.0/24" + subnet_region = "us-west1" + subnet_flow_logs = "true" + subnet_flow_logs_interval = "INTERVAL_10_MIN" + subnet_flow_logs_sampling = 0.7 + subnet_flow_logs_metadata = "INCLUDE_ALL_METADATA" + } + ] + + secondary_ranges = { + subnet-01 = [ + { + range_name = "subnet-01-secondary-01" + ip_cidr_range = "192.168.64.0/24" + }, + ] + + subnet-02 = [] + } +} +``` + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| network\_name | The name of the network where subnets will be created | string | n/a | yes | +| project\_id | The ID of the project where subnets will be created | string | n/a | yes | +| secondary\_ranges | Secondary ranges that will be used in some of the subnets | object | `` | no | +| subnets | The list of subnets being created | list(map(string)) | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| subnets | The created subnet resources | + + + +### Subnet Inputs + +The subnets list contains maps, where each object represents a subnet. Each map has the following inputs (please see examples folder for additional references): + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| subnet\_name | The name of the subnet being created | string | - | yes | +| subnet\_ip | The IP and CIDR range of the subnet being created | string | - | yes | +| subnet\_region | The region where the subnet will be created | string | - | yes | +| subnet\_private\_access | Whether this subnet will have private Google access enabled | string | `"false"` | no | +| subnet\_flow\_logs | Whether the subnet will record and send flow log data to logging | string | `"false"` | no | diff --git a/modules/subnets/main.tf b/modules/subnets/main.tf new file mode 100644 index 00000000..1f175c09 --- /dev/null +++ b/modules/subnets/main.tf @@ -0,0 +1,59 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + subnets = { + for x in var.subnets : + "${x.subnet_region}/${x.subnet_name}" => x + } +} + + +/****************************************** + Subnet configuration + *****************************************/ +resource "google_compute_subnetwork" "subnetwork" { + for_each = local.subnets + name = each.value.subnet_name + ip_cidr_range = each.value.subnet_ip + region = each.value.subnet_region + private_ip_google_access = lookup(each.value, "subnet_private_access", "false") + dynamic "log_config" { + for_each = lookup(each.value, "subnet_flow_logs", false) ? [{ + aggregation_interval = lookup(each.value, "subnet_flow_logs_interval", null) + flow_sampling = lookup(each.value, "subnet_flow_logs_sampling", null) + metadata = lookup(each.value, "subnet_flow_logs_metadata", null) + }] : [] + content { + aggregation_interval = log_config.value.aggregation_interval + flow_sampling = log_config.value.flow_sampling + metadata = log_config.value.metadata + } + } + network = var.network_name + project = var.project_id + description = lookup(each.value, "description", null) + secondary_ip_range = [ + for i in range( + length( + contains( + keys(var.secondary_ranges), each.value.subnet_name) == true + ? var.secondary_ranges[each.value.subnet_name] + : [] + )) : + var.secondary_ranges[each.value.subnet_name][i] + ] +} diff --git a/modules/subnets/outputs.tf b/modules/subnets/outputs.tf new file mode 100644 index 00000000..6ba07eb1 --- /dev/null +++ b/modules/subnets/outputs.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "subnets" { + value = google_compute_subnetwork.subnetwork + description = "The created subnet resources" +} diff --git a/modules/subnets/variables.tf b/modules/subnets/variables.tf new file mode 100644 index 00000000..84d7b099 --- /dev/null +++ b/modules/subnets/variables.tf @@ -0,0 +1,34 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "project_id" { + description = "The ID of the project where subnets will be created" +} + +variable "network_name" { + description = "The name of the network where subnets will be created" +} + +variable "subnets" { + type = list(map(string)) + description = "The list of subnets being created" +} + +variable "secondary_ranges" { + type = map(list(object({ range_name = string, ip_cidr_range = string }))) + description = "Secondary ranges that will be used in some of the subnets" + default = {} +} diff --git a/modules/subnets/versions.tf b/modules/subnets/versions.tf new file mode 100644 index 00000000..5b19897c --- /dev/null +++ b/modules/subnets/versions.tf @@ -0,0 +1,22 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_version = "~> 0.12.0" + required_providers { + google = "~> 2.19.0" + } +} diff --git a/modules/vpc/README.md b/modules/vpc/README.md new file mode 100644 index 00000000..cae59d02 --- /dev/null +++ b/modules/vpc/README.md @@ -0,0 +1,46 @@ +# Terraform Network Module + +This submodule is part of the the `terraform-google-network` module. It creates a vpc network and optionally enables it as a Shared VPC host project. + +It supports creating: + +- A VPC Network +- Optionally enabling the network as a Shared VPC host + +## Usage + +Basic usage of this submodule is as follows: + +```hcl +module "vpc" { + source = "terraform-google-modules/network/google//modules/vpc" + version = "~> 2.0.0" + + project_id = "" + network_name = "example-vpc" + + shared_vpc_host = false +``` + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| auto\_create\_subnetworks | When set to true, the network is created in 'auto subnet mode' and it will create a subnet for each region automatically across the 10.128.0.0/9 address range. When set to false, the network is created in 'custom subnet mode' so the user can explicitly connect subnetwork resources. | bool | `"false"` | no | +| description | An optional description of this resource. The resource must be recreated to modify this field. | string | `""` | no | +| network\_name | The name of the network being created | string | n/a | yes | +| project\_id | The ID of the project where this VPC will be created | string | n/a | yes | +| routing\_mode | The network routing mode (default 'GLOBAL') | string | `"GLOBAL"` | no | +| shared\_vpc\_host | Makes this project a Shared VPC host if 'true' (default 'false') | bool | `"false"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| network | The VPC resource being created | +| network\_name | The name of the VPC being created | +| network\_self\_link | The URI of the VPC being created | +| project\_id | VPC project id | + + diff --git a/modules/vpc/main.tf b/modules/vpc/main.tf new file mode 100644 index 00000000..55703793 --- /dev/null +++ b/modules/vpc/main.tf @@ -0,0 +1,35 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/****************************************** + VPC configuration + *****************************************/ +resource "google_compute_network" "network" { + name = var.network_name + auto_create_subnetworks = var.auto_create_subnetworks + routing_mode = var.routing_mode + project = var.project_id + description = var.description +} + +/****************************************** + Shared VPC + *****************************************/ +resource "google_compute_shared_vpc_host_project" "shared_vpc_host" { + count = var.shared_vpc_host ? 1 : 0 + project = var.project_id + depends_on = [google_compute_network.network] +} diff --git a/modules/vpc/outputs.tf b/modules/vpc/outputs.tf new file mode 100644 index 00000000..8133169d --- /dev/null +++ b/modules/vpc/outputs.tf @@ -0,0 +1,35 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "network" { + value = google_compute_network.network + description = "The VPC resource being created" +} + +output "network_name" { + value = google_compute_network.network.name + description = "The name of the VPC being created" +} + +output "network_self_link" { + value = google_compute_network.network.self_link + description = "The URI of the VPC being created" +} + +output "project_id" { + value = var.project_id + description = "VPC project id" +} diff --git a/modules/vpc/variables.tf b/modules/vpc/variables.tf new file mode 100644 index 00000000..a96751c4 --- /dev/null +++ b/modules/vpc/variables.tf @@ -0,0 +1,47 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "project_id" { + description = "The ID of the project where this VPC will be created" +} + +variable "network_name" { + description = "The name of the network being created" +} + +variable "routing_mode" { + type = string + default = "GLOBAL" + description = "The network routing mode (default 'GLOBAL')" +} + +variable "shared_vpc_host" { + type = bool + description = "Makes this project a Shared VPC host if 'true' (default 'false')" + default = false +} + +variable "description" { + type = string + description = "An optional description of this resource. The resource must be recreated to modify this field." + default = "" +} + +variable "auto_create_subnetworks" { + type = bool + description = "When set to true, the network is created in 'auto subnet mode' and it will create a subnet for each region automatically across the 10.128.0.0/9 address range. When set to false, the network is created in 'custom subnet mode' so the user can explicitly connect subnetwork resources." + default = false +} diff --git a/modules/vpc/versions.tf b/modules/vpc/versions.tf new file mode 100644 index 00000000..5b19897c --- /dev/null +++ b/modules/vpc/versions.tf @@ -0,0 +1,22 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_version = "~> 0.12.0" + required_providers { + google = "~> 2.19.0" + } +} diff --git a/outputs.tf b/outputs.tf index 11e707a9..44aebcad 100644 --- a/outputs.tf +++ b/outputs.tf @@ -15,56 +15,56 @@ */ output "network_name" { - value = google_compute_network.network.name + value = module.vpc.network_name description = "The name of the VPC being created" } output "network_self_link" { - value = google_compute_network.network.self_link + value = module.vpc.network_self_link description = "The URI of the VPC being created" } -output "svpc_host_project_id" { - value = element(concat(google_compute_shared_vpc_host_project.shared_vpc_host.*.project, list("")), 0) - description = "Shared VPC host project id." +output "project_id" { + value = module.vpc.project_id + description = "VPC project id" } output "subnets_names" { - value = [for network in google_compute_subnetwork.subnetwork : network.name] + value = [for network in module.subnets.subnets : network.name] description = "The names of the subnets being created" } output "subnets_ips" { - value = [for network in google_compute_subnetwork.subnetwork : network.ip_cidr_range] + value = [for network in module.subnets.subnets : network.ip_cidr_range] description = "The IPs and CIDRs of the subnets being created" } output "subnets_self_links" { - value = [for network in google_compute_subnetwork.subnetwork : network.self_link] + value = [for network in module.subnets.subnets : network.self_link] description = "The self-links of subnets being created" } output "subnets_regions" { - value = [for network in google_compute_subnetwork.subnetwork : network.region] + value = [for network in module.subnets.subnets : network.region] description = "The region where the subnets will be created" } output "subnets_private_access" { - value = [for network in google_compute_subnetwork.subnetwork : network.private_ip_google_access] + value = [for network in module.subnets.subnets : network.private_ip_google_access] description = "Whether the subnets will have access to Google API's without a public IP" } output "subnets_flow_logs" { - value = [for network in google_compute_subnetwork.subnetwork : network.enable_flow_logs] + value = [for network in module.subnets.subnets : network.enable_flow_logs] description = "Whether the subnets will have VPC flow logs enabled" } output "subnets_secondary_ranges" { - value = [for network in google_compute_subnetwork.subnetwork : network.secondary_ip_range] + value = [for network in module.subnets.subnets : network.secondary_ip_range] description = "The secondary ranges associated with these subnets" } -output "routes" { - value = google_compute_route.route.*.name - description = "The routes associated with this VPC" +output "route_names" { + value = [for route in module.routes.routes : route.name] + description = "The route names associated with this VPC" } diff --git a/test/fixtures/shared/outputs.tf b/test/fixtures/shared/outputs.tf index fba7438b..651f0e00 100644 --- a/test/fixtures/shared/outputs.tf +++ b/test/fixtures/shared/outputs.tf @@ -34,11 +34,6 @@ output "output_network_self_link" { description = "The URI of the VPC being created" } -output "output_svpc_host_project_id" { - value = module.example.svpc_host_project_id - description = "Shared VPC host project id." -} - output "output_subnets_names" { value = module.example.subnets_names description = "The names of the subnets being created" @@ -49,7 +44,6 @@ output "output_subnets_ips" { description = "The IP and cidrs of the subnets being created" } - output "output_subnets_regions" { value = module.example.subnets_regions description = "The region where subnets will be created" @@ -71,6 +65,6 @@ output "output_subnets_secondary_ranges" { } output "output_routes" { - value = module.example.routes - description = "The routes associated with this VPC" + value = module.example.route_names + description = "The route names associated with this VPC" } diff --git a/test/integration/secondary_ranges/controls/inspec_attributes.rb b/test/integration/secondary_ranges/controls/inspec_attributes.rb index 9e11b6cd..b878c13d 100644 --- a/test/integration/secondary_ranges/controls/inspec_attributes.rb +++ b/test/integration/secondary_ranges/controls/inspec_attributes.rb @@ -55,7 +55,7 @@ it { should eq [{"ip_cidr_range"=>"192.168.64.0/24", "range_name"=>"#{network_name}-subnet-01-01"}, {"ip_cidr_range"=>"192.168.65.0/24", "range_name"=>"#{network_name}-subnet-01-02"}, {"ip_cidr_range"=>"192.168.66.0/24", "range_name"=>"#{network_name}-subnet-03-01"}] } end - describe attribute("output_svpc_host_project_id") do - it { should eq "" } + describe attribute("project_id") do + it { should eq project_id } end end diff --git a/test/integration/secondary_ranges/inspec.yml b/test/integration/secondary_ranges/inspec.yml index ea9c3b05..c11e6612 100644 --- a/test/integration/secondary_ranges/inspec.yml +++ b/test/integration/secondary_ranges/inspec.yml @@ -26,5 +26,5 @@ attributes: required: true - name: output_subnets_secondary_ranges required: true - - name: output_svpc_host_project_id + - name: output_project_id required: true diff --git a/test/integration/submodule_firewall/controls/inspec_attributes.rb b/test/integration/submodule_firewall/controls/inspec_attributes.rb index 05ca71cb..25320c41 100644 --- a/test/integration/submodule_firewall/controls/inspec_attributes.rb +++ b/test/integration/submodule_firewall/controls/inspec_attributes.rb @@ -55,7 +55,7 @@ it { should eq [[],[]] } end - describe attribute("output_svpc_host_project_id") do - it { should eq "" } + describe attribute("output_project_id") do + it { should eq project_id } end end diff --git a/test/integration/submodule_firewall/inspec.yml b/test/integration/submodule_firewall/inspec.yml index 67efdd29..43e9d744 100644 --- a/test/integration/submodule_firewall/inspec.yml +++ b/test/integration/submodule_firewall/inspec.yml @@ -30,5 +30,5 @@ attributes: required: true - name: output_subnets_secondary_ranges required: true - - name: output_svpc_host_project_id + - name: output_project_id required: true diff --git a/test/setup/make_source.sh b/test/setup/make_source.sh deleted file mode 100755 index ffdc48e1..00000000 --- a/test/setup/make_source.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -echo "#!/usr/bin/env bash" > ../source.sh - -project_id=$(terraform output project_id) -echo "export TF_VAR_project_id='$project_id'" >> ../source.sh - -sa_json=$(terraform output sa_key) -# shellcheck disable=SC2086 -echo "export SERVICE_ACCOUNT_JSON='$(echo $sa_json | base64 --decode)'" >> ../source.sh diff --git a/test/setup/versions.tf b/test/setup/versions.tf index 832ec1df..b8dffa42 100644 --- a/test/setup/versions.tf +++ b/test/setup/versions.tf @@ -17,3 +17,11 @@ terraform { required_version = ">= 0.12" } + +provider "google" { + version = "~> 2.18.0" +} + +provider "google-beta" { + version = "~> 2.18.0" +} diff --git a/variables.tf b/variables.tf index 51d1d78b..1770d50f 100644 --- a/variables.tf +++ b/variables.tf @@ -29,9 +29,9 @@ variable "routing_mode" { } variable "shared_vpc_host" { - type = string + type = bool description = "Makes this project a Shared VPC host if 'true' (default 'false')" - default = "false" + default = false } variable "subnets" { diff --git a/versions.tf b/versions.tf index 1fe4caaa..5b19897c 100644 --- a/versions.tf +++ b/versions.tf @@ -16,4 +16,7 @@ terraform { required_version = "~> 0.12.0" + required_providers { + google = "~> 2.19.0" + } }