diff --git a/.kitchen.yml b/.kitchen.yml index c2149000..d67ab25f 100644 --- a/.kitchen.yml +++ b/.kitchen.yml @@ -64,9 +64,21 @@ suites: color: true systems: - name: local + attrs_outputs: + customized_inspec_attribute: output_network_name + customized_inspec_attribute: output_network_self_link + customized_inspec_attribute: output_subnets_ips + customized_inspec_attribute: output_routes + customized_inspec_attribute: output_subnets_flow_logs + customized_inspec_attribute: output_subnets_names + customized_inspec_attribute: output_subnets_private_access + customized_inspec_attribute: output_subnets_regions + customized_inspec_attribute: output_subnets_secondary_ranges + customized_inspec_attribute: output_project_id backend: local controls: - gcloud + - inspec_attributes - name: "multi_vpc" driver: name: "terraform" @@ -107,9 +119,21 @@ suites: controls: - gcp - name: local + attrs_outputs: + customized_inspec_attribute: output_network_name + customized_inspec_attribute: output_network_self_link + customized_inspec_attribute: output_subnets_ips + customized_inspec_attribute: output_routes + customized_inspec_attribute: output_subnets_flow_logs + customized_inspec_attribute: output_subnets_names + customized_inspec_attribute: output_subnets_private_access + customized_inspec_attribute: output_subnets_regions + customized_inspec_attribute: output_subnets_secondary_ranges + customized_inspec_attribute: output_project_id backend: local controls: - gcloud + - inspec_attributes - name: "submodule_network_peering" driver: name: "terraform" diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f311cd..b9b82378 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog][keepachangelog-site], 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] + ## [1.5.0] - 2019-11-06 diff --git a/Makefile b/Makefile index b415692e..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.5.0 +DOCKER_TAG_VERSION_DEVELOPER_TOOLS := 0.6.0 DOCKER_IMAGE_DEVELOPER_TOOLS := cft/developer-tools REGISTRY_URL := gcr.io/cloud-foundation-cicd diff --git a/README.md b/README.md index 9e472ae1..e4314d2d 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). @@ -38,6 +40,15 @@ module "vpc" { 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 = { @@ -92,7 +103,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 @@ -101,7 +112,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 | @@ -109,11 +121,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 | @@ -125,7 +137,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 | |------|-------------|:----:|:-----:|:-----:| @@ -143,7 +156,7 @@ The routes list contains maps, where each object represents a route. For the nex ## Requirements ### Installed Software - [Terraform](https://www.terraform.io/downloads.html) ~> 0.12.0 -- [Terraform Provider for GCP][terraform-provider-google] ~> 2.10.0 +- [Terraform Provider for GCP][terraform-provider-google] ~> 2.19.0 - [gcloud](https://cloud.google.com/sdk/gcloud/) >243.0.0 ### Configure a Service Account diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index 364eebcd..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.5.0' + _DOCKER_TAG_VERSION_DEVELOPER_TOOLS: '0.6.0' diff --git a/build/lint.cloudbuild.yaml b/build/lint.cloudbuild.yaml index fb860e81..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.5.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 new file mode 100644 index 00000000..14ae83c4 --- /dev/null +++ b/docs/upgrading_to_v2.0.md @@ -0,0 +1,134 @@ +# Upgrading to v2.x + +The v2.x release of _google-network_ is a backwards incompatible +release. + +Because v2.x changed how the subnet resource is iterated on, resources in Terraform state need to be migrated in order to avoid the resources from getting destroyed and recreated. + +## Migration Instructions + +First, upgrade to the new version of this module. + +```diff + module "kubernetes_engine_private_cluster" { + source = "terraform-google-modules/network/google" +- version = "~> 1.5" ++ version = "~> 2.0" + + # ... + } +``` + +If you run `terraform plan` at this point, Terraform will inform you that it will attempt to delete and recreate your existing subnets. This is almost certainly not the behavior you want. + +You will need to migrate your state, either [manually](#manual-migration-steps) or [automatically](#migration-script). + +### Migration Script + +1. Download the script: + + ```sh + curl -O https://raw.githubusercontent.com/terraform-google-modules/terraform-google-network/master/helpers/migrate.py + chmod +x migrate.py + ``` + +2. Back up your Terraform state: + + ```sh + terraform state pull >> state.bak + ``` + +2. Run the script to output the migration commands: + + ```sh + $ ./migrate.py --dryrun + 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 script: + + ```sh + $ ./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-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. + +### Manual Migration Steps + +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_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 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. + +```Shell +$ terraform plan +Refreshing Terraform state in-memory prior to plan... +The refreshed state will be used to calculate this plan, but will not be +persisted to local or remote state storage. + +module.example.module.test-vpc-module.google_compute_network.network: Refreshing state... [id=simple-project-timh] +module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork["us-west1/simple-project-timh-subnet-02"]: Refreshing state... [id=us-west1/simple-project-timh-subnet-02] +module.example.module.test-vpc-module.google_compute_subnetwork.subnetwork["us-west1/simple-project-timh-subnet-01"]: Refreshing state... [id=us-west1/simple-project-timh-subnet-01] + +------------------------------------------------------------------------ + +No changes. Infrastructure is up-to-date. + +This means that Terraform did not detect any differences between your +configuration and real physical resources that exist. As a result, no +actions need to be performed. +``` + +### 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/main.tf b/examples/delete_default_gateway_routes/main.tf index 7b48d150..1269b0e9 100644 --- a/examples/delete_default_gateway_routes/main.tf +++ b/examples/delete_default_gateway_routes/main.tf @@ -15,7 +15,7 @@ */ provider "google" { - version = "~> 2.10.0" + version = "~> 2.19.0" } provider "null" { 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/main.tf b/examples/multi_vpc/main.tf index 7c039a5b..1d0b7247 100644 --- a/examples/multi_vpc/main.tf +++ b/examples/multi_vpc/main.tf @@ -15,7 +15,7 @@ */ provider "google" { - version = "~> 2.10.0" + version = "~> 2.19.0" } provider "null" { 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 b31e8e3c..acca7c73 100644 --- a/examples/secondary_ranges/README.md +++ b/examples/secondary_ranges/README.md @@ -19,7 +19,8 @@ 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 | diff --git a/examples/secondary_ranges/main.tf b/examples/secondary_ranges/main.tf index 1ddcb460..576ff002 100644 --- a/examples/secondary_ranges/main.tf +++ b/examples/secondary_ranges/main.tf @@ -15,7 +15,7 @@ */ provider "google" { - version = "~> 2.10.0" + version = "~> 2.19.0" } provider "null" { diff --git a/examples/secondary_ranges/outputs.tf b/examples/secondary_ranges/outputs.tf index 2ccc964a..6c3f49cb 100644 --- a/examples/secondary_ranges/outputs.tf +++ b/examples/secondary_ranges/outputs.tf @@ -24,6 +24,11 @@ output "network_self_link" { description = "The URI of the VPC being created" } +output "project_id" { + value = module.vpc-secondary-ranges.project_id + description = "VPC project id" +} + output "subnets_names" { value = module.vpc-secondary-ranges.subnets_names description = "The names of the subnets being created" @@ -54,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 583a5c48..a4325668 100644 --- a/examples/simple_project/README.md +++ b/examples/simple_project/README.md @@ -18,7 +18,8 @@ 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 | diff --git a/examples/simple_project/main.tf b/examples/simple_project/main.tf index 13c4a716..40519adc 100644 --- a/examples/simple_project/main.tf +++ b/examples/simple_project/main.tf @@ -15,7 +15,7 @@ */ provider "google" { - version = "~> 2.10.0" + version = "~> 2.19.0" } provider "null" { @@ -25,6 +25,7 @@ provider "null" { locals { subnet_01 = "${var.network_name}-subnet-01" subnet_02 = "${var.network_name}-subnet-02" + subnet_03 = "${var.network_name}-subnet-03" } module "test-vpc-module" { @@ -45,5 +46,14 @@ module "test-vpc-module" { subnet_private_access = "true" subnet_flow_logs = "true" }, + { + subnet_name = "${local.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" + } ] } diff --git a/examples/simple_project/outputs.tf b/examples/simple_project/outputs.tf index 429e0b97..f69ae043 100644 --- a/examples/simple_project/outputs.tf +++ b/examples/simple_project/outputs.tf @@ -24,6 +24,11 @@ output "network_self_link" { description = "The URI of the VPC being created" } +output "project_id" { + value = module.test-vpc-module.project_id + description = "VPC project id" +} + output "subnets_names" { value = module.test-vpc-module.subnets_names description = "The names of the subnets being created" @@ -54,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 898c9e96..354711e2 100644 --- a/examples/simple_project_with_regional_network/README.md +++ b/examples/simple_project_with_regional_network/README.md @@ -18,7 +18,8 @@ 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 | diff --git a/examples/simple_project_with_regional_network/main.tf b/examples/simple_project_with_regional_network/main.tf index 58b0ba4e..583a21db 100644 --- a/examples/simple_project_with_regional_network/main.tf +++ b/examples/simple_project_with_regional_network/main.tf @@ -15,7 +15,7 @@ */ provider "google" { - version = "~> 2.10.0" + version = "~> 2.19.0" } provider "null" { diff --git a/examples/simple_project_with_regional_network/outputs.tf b/examples/simple_project_with_regional_network/outputs.tf index 429e0b97..f69ae043 100644 --- a/examples/simple_project_with_regional_network/outputs.tf +++ b/examples/simple_project_with_regional_network/outputs.tf @@ -24,6 +24,11 @@ output "network_self_link" { description = "The URI of the VPC being created" } +output "project_id" { + value = module.test-vpc-module.project_id + description = "VPC project id" +} + output "subnets_names" { value = module.test-vpc-module.subnets_names description = "The names of the subnets being created" @@ -54,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 1cb360d8..48f2bd1c 100644 --- a/examples/submodule_firewall/README.md +++ b/examples/submodule_firewall/README.md @@ -19,5 +19,14 @@ This VPC has two subnets, with no secondary ranges. | admin\_ranges | Firewall attributes for admin 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 | +| 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 | diff --git a/examples/submodule_firewall/main.tf b/examples/submodule_firewall/main.tf index 8c1ed331..4319be80 100644 --- a/examples/submodule_firewall/main.tf +++ b/examples/submodule_firewall/main.tf @@ -15,7 +15,7 @@ */ provider "google" { - version = "~> 2.10.0" + version = "~> 2.19.0" } provider "null" { diff --git a/examples/submodule_firewall/outputs.tf b/examples/submodule_firewall/outputs.tf index f687da74..182dc845 100644 --- a/examples/submodule_firewall/outputs.tf +++ b/examples/submodule_firewall/outputs.tf @@ -28,3 +28,48 @@ output "admin_ranges" { description = "Firewall attributes for admin ranges." value = module.test-firewall-submodule.admin_ranges } + +output "network_self_link" { + value = module.test-vpc-module.network_self_link + description = "The URI of the VPC being created" +} + +output "project_id" { + value = module.test-vpc-module.project_id + description = "VPC project id" +} + +output "subnets_names" { + value = module.test-vpc-module.subnets_names + description = "The names of the subnets being created" +} + +output "subnets_ips" { + value = module.test-vpc-module.subnets_ips + description = "The IP and cidrs of the subnets being created" +} + +output "subnets_regions" { + value = module.test-vpc-module.subnets_regions + description = "The region where subnets will be created" +} + +output "subnets_private_access" { + value = module.test-vpc-module.subnets_private_access + description = "Whether the subnets will have access to Google API's without a public IP" +} + +output "subnets_flow_logs" { + value = module.test-vpc-module.subnets_flow_logs + description = "Whether the subnets will have VPC flow logs enabled" +} + +output "subnets_secondary_ranges" { + value = module.test-vpc-module.subnets_secondary_ranges + description = "The secondary ranges associated with these subnets" +} + +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..0e158f73 --- /dev/null +++ b/helpers/migrate.py @@ -0,0 +1,423 @@ +#!/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", + "new_plural": False + }, + { + "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.get("module", "none") == 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/main.tf b/main.tf index b7c4b6ed..93794145 100644 --- a/main.tf +++ b/main.tf @@ -14,99 +14,38 @@ * limitations under the License. */ -locals { - network_self_link = var.create_network ? google_compute_network.network[0].self_link : data.google_compute_network.network[0].self_link - network_name = var.create_network ? google_compute_network.network[0].name : data.google_compute_network.network[0].name -} - /****************************************** VPC configuration *****************************************/ -resource "google_compute_network" "network" { - count = var.create_network ? 1 : 0 - 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 -} - -data "google_compute_network" "network" { - count = var.create_network ? 0 : 1 - name = var.network_name - project = var.project_id -} - -/****************************************** - Shared VPC - *****************************************/ -resource "google_compute_shared_vpc_host_project" "shared_vpc_host" { - count = var.shared_vpc_host == "true" ? 1 : 0 - project = var.project_id + shared_vpc_host = var.shared_vpc_host } /****************************************** Subnet configuration *****************************************/ -resource "google_compute_subnetwork" "subnetwork" { - count = length(var.subnets) - - name = var.subnets[count.index]["subnet_name"] - ip_cidr_range = var.subnets[count.index]["subnet_ip"] - region = var.subnets[count.index]["subnet_region"] - private_ip_google_access = lookup(var.subnets[count.index], "subnet_private_access", "false") - enable_flow_logs = lookup(var.subnets[count.index], "subnet_flow_logs", "false") - network = local.network_self_link - project = var.project_id - secondary_ip_range = [for i in range(length(contains(keys(var.secondary_ranges), var.subnets[count.index]["subnet_name"]) == true ? var.secondary_ranges[var.subnets[count.index]["subnet_name"]] : [])) : var.secondary_ranges[var.subnets[count.index]["subnet_name"]][i]] - description = lookup(var.subnets[count.index], "description", null) - depends_on = [google_compute_network.network] -} - -data "google_compute_subnetwork" "created_subnets" { - count = length(var.subnets) - name = element(google_compute_subnetwork.subnetwork.*.name, count.index) - region = element(google_compute_subnetwork.subnetwork.*.region, count.index) - project = var.project_id +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 = local.network_name - name = lookup(var.routes[count.index], "name", format("%s-%s-%d", lower(local.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_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} ${local.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 b65ed7ea..7a8fb0a7 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 34fe81c1..44aebcad 100644 --- a/outputs.tf +++ b/outputs.tf @@ -15,56 +15,56 @@ */ output "network_name" { - value = local.network_name + value = module.vpc.network_name description = "The name of the VPC being created" } output "network_self_link" { - value = local.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 = google_compute_subnetwork.subnetwork.*.name + value = [for network in module.subnets.subnets : network.name] description = "The names of the subnets being created" } output "subnets_ips" { - value = google_compute_subnetwork.subnetwork.*.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 = google_compute_subnetwork.subnetwork.*.self_link + value = [for network in module.subnets.subnets : network.self_link] description = "The self-links of subnets being created" } output "subnets_regions" { - value = google_compute_subnetwork.subnetwork.*.region + value = [for network in module.subnets.subnets : network.region] description = "The region where the subnets will be created" } output "subnets_private_access" { - value = google_compute_subnetwork.subnetwork.*.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 = google_compute_subnetwork.subnetwork.*.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 = google_compute_subnetwork.subnetwork[*].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 68e9e076..651f0e00 100644 --- a/test/fixtures/shared/outputs.tf +++ b/test/fixtures/shared/outputs.tf @@ -23,3 +23,48 @@ output "network_name" { value = module.example.network_name description = "The name of the VPC being created" } + +output "output_network_name" { + value = module.example.network_name + description = "The name of the VPC being created" +} + +output "output_network_self_link" { + value = module.example.network_self_link + description = "The URI of the VPC being created" +} + +output "output_subnets_names" { + value = module.example.subnets_names + description = "The names of the subnets being created" +} + +output "output_subnets_ips" { + value = module.example.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" +} + +output "output_subnets_private_access" { + value = module.example.subnets_private_access + description = "Whether the subnets will have access to Google API's without a public IP" +} + +output "output_subnets_flow_logs" { + value = module.example.subnets_flow_logs + description = "Whether the subnets will have VPC flow logs enabled" +} + +output "output_subnets_secondary_ranges" { + value = module.example.subnets_secondary_ranges + description = "The secondary ranges associated with these subnets" +} + +output "output_routes" { + 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 new file mode 100644 index 00000000..b878c13d --- /dev/null +++ b/test/integration/secondary_ranges/controls/inspec_attributes.rb @@ -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 +# +# 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. + +project_id = attribute('project_id') +network_name = attribute('network_name') + +control "inspec_attributes" do + title "Terraform Outputs" + desc "Terraform Outputs" + + describe attribute("output_network_name") do + it { should eq "#{network_name}" } + end + + describe attribute("output_network_self_link") do + it { should eq "https://www.googleapis.com/compute/v1/projects/#{project_id}/global/networks/#{network_name}" } + end + + describe attribute("output_subnets_ips") do + it { should eq ["10.10.10.0/24", "10.10.20.0/24", "10.10.30.0/24", "10.10.40.0/24"] } + end + + describe attribute("output_routes") do + it { should eq [] } + end + + describe attribute("output_subnets_flow_logs") do + it { should eq [false, true, false, false] } + end + + describe attribute("output_subnets_names") do + it { should eq ["#{network_name}-subnet-01", "#{network_name}-subnet-02", "#{network_name}-subnet-03", "#{network_name}-subnet-04"] } + end + + describe attribute("output_subnets_private_access") do + it { should eq [false, true, false, false] } + end + + describe attribute("output_subnets_regions") do + it { should eq ["us-west1", "us-west1", "us-west1", "us-west1"] } + end + + describe attribute("output_subnets_secondary_ranges") do + 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("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 94f1cbc6..c11e6612 100644 --- a/test/integration/secondary_ranges/inspec.yml +++ b/test/integration/secondary_ranges/inspec.yml @@ -6,3 +6,25 @@ attributes: - name: network_name required: true type: string + - name: output_network_name + required: true + type: string + - name: output_network_self_link + required: true + type: string + - name: output_subnets_ips + required: true + - name: output_routes + required: true + - name: output_subnets_flow_logs + required: true + - name: output_subnets_names + required: true + - name: output_subnets_private_access + required: true + - name: output_subnets_regions + required: true + - name: output_subnets_secondary_ranges + required: true + - name: output_project_id + required: true diff --git a/test/integration/simple_project/controls/gcloud.rb b/test/integration/simple_project/controls/gcloud.rb index a22b2d80..3e7fc768 100644 --- a/test/integration/simple_project/controls/gcloud.rb +++ b/test/integration/simple_project/controls/gcloud.rb @@ -30,12 +30,10 @@ end end - describe "enableFlowLogs" do - it "should be false" do - expect(data).to include( - "enableFlowLogs" => false - ) - end + it "logConfig should not exist" do + expect(data).to_not include( + "logConfig" + ) end end @@ -51,12 +49,39 @@ end end - describe "enableFlowLogs" do - it "should be true" do - expect(data).to include( - "enableFlowLogs" => true - ) + it "Log config should be correct" do + expect(data).to include( + "logConfig" => { + "aggregationInterval" => "INTERVAL_5_SEC", + "enable" => true, + "flowSampling" => 0.5, + "metadata" => "INCLUDE_ALL_METADATA" + } + ) + end + end + + describe command("gcloud compute networks subnets describe #{network_name}-subnet-03 --project=#{project_id} --region=us-west1 --format=json") do + its(:exit_status) { should eq 0 } + its(:stderr) { should eq '' } + + let(:data) do + if subject.exit_status == 0 + JSON.parse(subject.stdout) + else + {} end end + + it "Log config should be correct" do + expect(data).to include( + "logConfig" => { + "aggregationInterval" => "INTERVAL_10_MIN", + "enable" => true, + "flowSampling" => 0.7, + "metadata" => "INCLUDE_ALL_METADATA" + } + ) + end end end diff --git a/test/integration/simple_project/controls/gcp.rb b/test/integration/simple_project/controls/gcp.rb index b62b3f32..d48c79da 100644 --- a/test/integration/simple_project/controls/gcp.rb +++ b/test/integration/simple_project/controls/gcp.rb @@ -44,4 +44,14 @@ its('ip_cidr_range') { should eq "10.10.20.0/24" } its('private_ip_google_access') { should be true } end + + describe google_compute_subnetwork( + project: project_id, + name: "#{network_name}-subnet-03", + region: "us-west1" + ) do + it { should exist } + its('ip_cidr_range') { should eq "10.10.30.0/24" } + its('private_ip_google_access') { should be false } + end end diff --git a/test/integration/submodule_firewall/controls/inspec_attributes.rb b/test/integration/submodule_firewall/controls/inspec_attributes.rb new file mode 100644 index 00000000..25320c41 --- /dev/null +++ b/test/integration/submodule_firewall/controls/inspec_attributes.rb @@ -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 +# +# 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. + +project_id = attribute('project_id') +network_name = attribute('network_name') + +control "inspec_attributes" do + title "Terraform Outputs" + desc "Terraform Outputs" + + describe attribute("output_network_name") do + it { should eq "#{network_name}" } + end + + describe attribute("output_network_self_link") do + it { should eq "https://www.googleapis.com/compute/v1/projects/#{project_id}/global/networks/#{network_name}" } + end + + describe attribute("output_subnets_ips") do + it { should eq ["10.10.10.0/24", "10.10.20.0/24"] } + end + + describe attribute("output_routes") do + it { should eq [] } + end + + describe attribute("output_subnets_flow_logs") do + it { should eq [false, true] } + end + + describe attribute("output_subnets_names") do + it { should eq ["#{network_name}-subnet-01", "#{network_name}-subnet-02"] } + end + + describe attribute("output_subnets_private_access") do + it { should eq [false, true] } + end + + describe attribute("output_subnets_regions") do + it { should eq ["us-west1", "us-west1"] } + end + + describe attribute("output_subnets_secondary_ranges") do + it { should eq [[],[]] } + end + + 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 fc75b013..8f1d70e7 100644 --- a/test/integration/submodule_firewall/inspec.yml +++ b/test/integration/submodule_firewall/inspec.yml @@ -10,3 +10,25 @@ attributes: - name: network_name required: true type: string + - name: output_network_name + required: true + type: string + - name: output_network_self_link + required: true + type: string + - name: output_subnets_ips + required: true + - name: output_routes + required: true + - name: output_subnets_flow_logs + required: true + - name: output_subnets_names + required: true + - name: output_subnets_private_access + required: true + - name: output_subnets_regions + required: true + - name: output_subnets_secondary_ranges + required: true + - name: output_project_id + required: true 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 4902e430..1ff37f8f 100644 --- a/variables.tf +++ b/variables.tf @@ -35,9 +35,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" + } }