diff --git a/CHANGELOG.md b/CHANGELOG.md index fe8fe1b..f29edbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [0.2.0] 2019-YY-ZZ + +### Added + - Supported version of Terraform is 0.12. [#10] + ## [0.1.0] 2019-05-15 ### Added @@ -15,3 +20,5 @@ project adheres to [Semantic Versioning](http://semver.org/). [Unreleased]: https://github.com/terraform-google-modules/terraform-google-vpc-service-controls/compare/v0.1.0...HEAD [0.1.0]: https://github.com/terraform-google-modules/terraform-google-vpc-service-controls/releases/tag/v0.1.0 +[0.2.0]: https://github.com/terraform-google-modules/terraform-google-vpc-service-controls/releases/tag/v0.2.0 +[#10]: https://github.com/terraform-google-modules/terraform-google-vpc-service-controls/pull/10 \ No newline at end of file diff --git a/Makefile b/Makefile index b29ab75..99b8967 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ SHELL := /usr/bin/env bash # Docker build config variables CREDENTIALS_PATH ?= /cft/workdir/credentials.json DOCKER_ORG := gcr.io/cloud-foundation-cicd -DOCKER_TAG_BASE_KITCHEN_TERRAFORM ?= 1.0.1 +DOCKER_TAG_BASE_KITCHEN_TERRAFORM ?= 2.3.0 DOCKER_REPO_BASE_KITCHEN_TERRAFORM := ${DOCKER_ORG}/cft/kitchen-terraform:${DOCKER_TAG_BASE_KITCHEN_TERRAFORM} # All is the first target in the file so it will get picked up when you just run 'make' on its own diff --git a/README.md b/README.md index affd3d2..10acd8a 100644 --- a/README.md +++ b/README.md @@ -2,40 +2,41 @@ This module handles opiniated VPC Service Controls and Access Context Manager configuration and deployments. + +## 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.1.0](https://registry.terraform.io/modules/terraform-google-modules/vpc-service-controls/google/0.1.0). + ## Usage The root module only handles the configuration of the [access_context_manager_policy resource](https://www.terraform.io/docs/providers/google/r/access_context_manager_access_policy.html). For examples on how to use the root module with along with other submodules to configure all of VPC Service Controls and Access Context Manager resources, see the [examples](./examples/) folder and the [modules](./modules/) folder ```hcl provider "google" { - version = "~> 2.5.0" - credentials = "${file("${var.credentials_path}")}" + version = "~> 2.5.0" } module "org_policy" { source = "terraform-google-modules/vpc-service-controls/google" - parent_id = "${var.parent_id}" - policy_name = "${var.policy_name}" + parent_id = var.parent_id + policy_name = var.policy_name } module "access_level_members" { - source = "terraform-google-modules/vpc-service-controls/google//modules/access_level" - policy = "${module.org_policy.policy_id}" - name = "terraform_members" - members = "${var.members}" + source = "terraform-google-modules/vpc-service-controls/google//modules/access_level" + policy = module.org_policy.policy_id + name = "terraform_members" + members = var.members } module "regular_service_perimeter_1" { - source = "terraform-google-modules/vpc-service-controls/google//modules/regular_service_perimeter" - policy = "${module.org_policy.policy_id}" - perimeter_name = "regular_perimeter_1" - - description = "Perimeter shielding projects" - resources = ["1111111"] - - access_levels = ["${module.access_level_members.name}"] + source = "terraform-google-modules/vpc-service-controls/google//modules/regular_service_perimeter" + policy = module.org_policy.policy_id + perimeter_name = "regular_perimeter_1" + description = "Perimeter shielding projects" + resources = ["1111111"] + access_levels = [module.access_level_members.name] restricted_services = ["bigquery.googleapis.com", "storage.googleapis.com"] - - shared_resources = { + shared_resources = { all = ["11111111"] } } @@ -53,8 +54,7 @@ Then perform the following commands on the root folder: The [Access Context Manager API](https://cloud.google.com/access-context-manager/docs/) guarantees that resources will be created, but there may be a delay between a successful response and the change taking effect. For example, ["after you create a service perimeter, it may take up to 30 minutes for the changes to propagate and take effect"](https://cloud.google.com/vpc-service-controls/docs/create-service-perimeters). Because of these limitations in the API, you may first get an error when running `terraform apply` for the first time. However, for the [examples](./examples/) you should be able to succesfully deploy all resources by running `terraform apply` a second about 15 seconds after running it for the first time. -[^]: (autogen_docs_start) - + ## Inputs | Name | Description | Type | Default | Required | @@ -69,7 +69,7 @@ Because of these limitations in the API, you may first get an error when running | policy\_id | Resource name of the AccessPolicy. | | policy\_name | The policy's name. | -[^]: (autogen_docs_end) + ## Requirements @@ -83,8 +83,8 @@ The [project factory](https://github.com/terraform-google-modules/terraform-goog ### Software Dependencies ### Terraform -- [Terraform](https://www.terraform.io/downloads.html) 0.11.x -- [terraform-provider-google](https://github.com/terraform-providers/terraform-provider-google) plugin v2.0.0 +- [Terraform](https://www.terraform.io/downloads.html) >= 0.12.0 +- [terraform-provider-google](https://github.com/terraform-providers/terraform-provider-google) >= v2.5.0 ### Configure a Service Account @@ -117,7 +117,7 @@ In order to operate with the Service Account you must activate the following API ## Install -Be sure you have the correct Terraform version (0.11.x), you can choose the binary here: +Be sure you have the correct Terraform version (0.12.x), you can choose the binary here: - https://releases.hashicorp.com/terraform/ diff --git a/examples/simple_example/README.md b/examples/simple_example/README.md index 8f602fe..b99c033 100644 --- a/examples/simple_example/README.md +++ b/examples/simple_example/README.md @@ -19,17 +19,15 @@ You may use the following gcloud commands: -[^]: (autogen_docs_start) - + ## Inputs | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| credentials\_path | Path to credentials.json key for service account deploying resources | string | n/a | yes | -| members | An allowed list of members (users, service accounts). The signed-in identity originating the request must be a part of one of the provided members. If not specified, a request may come from any user (logged in/not logged in, etc.). Formats: user:{emailid}, serviceAccount:{emailid} | list | `` | no | +| members | An allowed list of members (users, service accounts). The signed-in identity originating the request must be a part of one of the provided members. If not specified, a request may come from any user (logged in/not logged in, etc.). Formats: user:{emailid}, serviceAccount:{emailid} | list(string) | n/a | yes | | parent\_id | The parent of this AccessPolicy in the Cloud Resource Hierarchy. As of now, only organization are accepted as parent. | string | n/a | yes | | policy\_name | The policy's name. | string | n/a | yes | -| protected\_project\_ids | Project id and number of the project INSIDE the regular service perimeter. This map variable expects an "id" for the project id and "number" key for the project number. | map | n/a | yes | +| protected\_project\_ids | Project id and number of the project INSIDE the regular service perimeter. This map variable expects an "id" for the project id and "number" key for the project number. | object | n/a | yes | ## Outputs @@ -40,7 +38,7 @@ You may use the following gcloud commands: | protected\_project\_id | Project id of the project INSIDE the regular service perimeter | | table\_id | Unique id for the BigQuery table being provisioned | -[^]: (autogen_docs_end) + To provision this example, run the following from within this directory: - `terraform init` to get the plugins diff --git a/examples/simple_example/main.tf b/examples/simple_example/main.tf index fe10f01..0664b20 100644 --- a/examples/simple_example/main.tf +++ b/examples/simple_example/main.tf @@ -15,52 +15,49 @@ */ provider "google" { - version = "~> 2.5.0" - credentials = "${file("${var.credentials_path}")}" + version = "~> 2.5.0" } -module "org_policy" { +module "access_context_manager_policy" { source = "../.." - parent_id = "${var.parent_id}" - policy_name = "${var.policy_name}" + parent_id = var.parent_id + policy_name = var.policy_name } module "access_level_members" { - source = "../../modules/access_level" - policy = "${module.org_policy.policy_id}" - name = "terraform_members" - members = "${var.members}" + source = "../../modules/access_level" + description = "Simple Example Access Level" + policy = module.access_context_manager_policy.policy_id + name = "terraform_members" + members = var.members } module "regular_service_perimeter_1" { source = "../../modules/regular_service_perimeter" - policy = "${module.org_policy.policy_id}" + policy = module.access_context_manager_policy.policy_id perimeter_name = "regular_perimeter_1" description = "Perimeter shielding bigquery project" - resources = ["${var.protected_project_ids["number"]}"] + resources = [var.protected_project_ids["number"]] - access_levels = ["${module.access_level_members.name}"] + access_levels = [module.access_level_members.name] restricted_services = ["bigquery.googleapis.com", "storage.googleapis.com"] shared_resources = { - all = ["${var.protected_project_ids["number"]}"] + all = [var.protected_project_ids["number"]] } } module "bigquery" { - source = "terraform-google-modules/bigquery/google" - version = "0.1.0" - + source = "terraform-google-modules/bigquery/google" + version = "2.0.0" dataset_id = "sample_dataset" dataset_name = "sample_dataset" description = "Dataset with a single table with one field" expiration = "3600000" - project_id = "${var.protected_project_ids["id"]}" + project_id = var.protected_project_ids["id"] location = "US" - table_id = "example_table" time_partitioning = "DAY" - schema_file = "sample_bq_schema.json" dataset_labels = { env = "dev" @@ -68,9 +65,13 @@ module "bigquery" { owner = "janesmith" } - table_labels = { - env = "dev" - billable = "true" - owner = "joedoe" - } + tables = [{ + table_id = "example_table", + schema = "sample_bq_schema.json", + labels = { + env = "dev" + billable = "true" + owner = "joedoe" + }, + }, ] } diff --git a/examples/simple_example/outputs.tf b/examples/simple_example/outputs.tf index ea16e74..779d1a4 100644 --- a/examples/simple_example/outputs.tf +++ b/examples/simple_example/outputs.tf @@ -16,20 +16,20 @@ output "policy_name" { description = "Name of the parent policy" - value = "${var.policy_name}" + value = var.policy_name } output "protected_project_id" { description = "Project id of the project INSIDE the regular service perimeter" - value = "${var.protected_project_ids["id"]}" + value = var.protected_project_ids["id"] } output "dataset_id" { description = "Unique id for the BigQuery dataset being provisioned" - value = "${module.bigquery.dataset_id}" + value = module.bigquery.dataset_id } output "table_id" { description = "Unique id for the BigQuery table being provisioned" - value = "${module.bigquery.table_id}" + value = module.bigquery.table_id } diff --git a/examples/simple_example/variables.tf b/examples/simple_example/variables.tf index a84e6f5..9be77f6 100644 --- a/examples/simple_example/variables.tf +++ b/examples/simple_example/variables.tf @@ -16,23 +16,20 @@ variable "parent_id" { description = "The parent of this AccessPolicy in the Cloud Resource Hierarchy. As of now, only organization are accepted as parent." + type = string } variable "policy_name" { description = "The policy's name." + type = string } variable "protected_project_ids" { description = "Project id and number of the project INSIDE the regular service perimeter. This map variable expects an \"id\" for the project id and \"number\" key for the project number." - type = "map" + type = object({ id = string, number = number }) } variable "members" { description = "An allowed list of members (users, service accounts). The signed-in identity originating the request must be a part of one of the provided members. If not specified, a request may come from any user (logged in/not logged in, etc.). Formats: user:{emailid}, serviceAccount:{emailid}" - type = "list" - default = [] -} - -variable "credentials_path" { - description = "Path to credentials.json key for service account deploying resources" + type = list(string) } diff --git a/examples/simple_example/versions.tf b/examples/simple_example/versions.tf new file mode 100644 index 0000000..2970427 --- /dev/null +++ b/examples/simple_example/versions.tf @@ -0,0 +1,19 @@ +/** + * 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" +} diff --git a/examples/simple_example_access_level/README.md b/examples/simple_example_access_level/README.md index 2c9c3ec..c2a8911 100644 --- a/examples/simple_example_access_level/README.md +++ b/examples/simple_example_access_level/README.md @@ -7,17 +7,15 @@ This example illustrates how to use the `vpc-service-controls` module to configu -[^]: (autogen_docs_start) - + ## Inputs | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| credentials\_path | Path to credentials.json key for service account deploying resources | string | n/a | yes | -| ip\_subnetworks | A list of CIDR block IP subnetwork specification. May be IPv4 or IPv6. Note that for a CIDR IP address block, the specified IP address portion must be properly truncated (i.e. all the host bits must be zero) or the input is considered malformed. For example, "192.0.2.0/24" is accepted but "192.0.2.1/24" is not. Similarly, for IPv6, "2001:db8::/32" is accepted whereas "2001:db8::1/32" is not. The originating IP of a request must be in one of the listed subnets in order for this Condition to be true. If empty, all IP addresses are allowed. | list | n/a | yes | +| ip\_subnetworks | A list of CIDR block IP subnetwork specification. May be IPv4 or IPv6. Note that for a CIDR IP address block, the specified IP address portion must be properly truncated (i.e. all the host bits must be zero) or the input is considered malformed. For example, "192.0.2.0/24" is accepted but "192.0.2.1/24" is not. Similarly, for IPv6, "2001:db8::/32" is accepted whereas "2001:db8::1/32" is not. The originating IP of a request must be in one of the listed subnets in order for this Condition to be true. If empty, all IP addresses are allowed. | list(string) | n/a | yes | | parent\_id | The parent of this AccessPolicy in the Cloud Resource Hierarchy. As of now, only organization are accepted as parent. | string | n/a | yes | | policy\_name | The policy's name. | string | n/a | yes | -| protected\_project\_ids | Project id and number of the project INSIDE the regular service perimeter. This map variable expects an "id" for the project id and "number" key for the project number. | map | n/a | yes | +| protected\_project\_ids | Project id and number of the project INSIDE the regular service perimeter. This map variable expects an "id" for the project id and "number" key for the project number. | object | n/a | yes | ## Outputs @@ -25,4 +23,4 @@ This example illustrates how to use the `vpc-service-controls` module to configu |------|-------------| | policy\_name | | -[^]: (autogen_docs_end) + diff --git a/examples/simple_example_access_level/main.tf b/examples/simple_example_access_level/main.tf index 9f445ac..5290b96 100644 --- a/examples/simple_example_access_level/main.tf +++ b/examples/simple_example_access_level/main.tf @@ -15,35 +15,35 @@ */ provider "google" { - version = "~> 2.5.0" - credentials = "${file("${var.credentials_path}")}" + version = "~> 2.5.0" } -module "org_policy" { +module "access_context_manager_policy" { source = "../.." - parent_id = "${var.parent_id}" - policy_name = "${var.policy_name}" + parent_id = var.parent_id + policy_name = var.policy_name } module "access_level_1" { source = "../../modules/access_level" - policy = "${module.org_policy.policy_id}" + policy = module.access_context_manager_policy.policy_id name = "single_ip_policy" - ip_subnetworks = "${var.ip_subnetworks}" + ip_subnetworks = var.ip_subnetworks + description = "Some description" } module "regular_service_perimeter_1" { source = "../../modules/regular_service_perimeter" - policy = "${module.org_policy.policy_id}" + policy = module.access_context_manager_policy.policy_id perimeter_name = "regular_perimeter_1" description = "Some description" - resources = ["${var.protected_project_ids["number"]}"] + resources = [var.protected_project_ids["number"]] restricted_services = ["bigquery.googleapis.com", "storage.googleapis.com"] - access_levels = ["${module.access_level_1.name}"] + access_levels = [module.access_level_1.name] shared_resources = { - all = ["${var.protected_project_ids["number"]}"] + all = [var.protected_project_ids["number"]] } } diff --git a/examples/simple_example_access_level/outputs.tf b/examples/simple_example_access_level/outputs.tf index 0809ceb..c086a36 100644 --- a/examples/simple_example_access_level/outputs.tf +++ b/examples/simple_example_access_level/outputs.tf @@ -15,5 +15,5 @@ */ output "policy_name" { - value = "${var.policy_name}" + value = var.policy_name } diff --git a/examples/simple_example_access_level/variables.tf b/examples/simple_example_access_level/variables.tf index 230b6b1..42b1478 100644 --- a/examples/simple_example_access_level/variables.tf +++ b/examples/simple_example_access_level/variables.tf @@ -16,22 +16,20 @@ variable "parent_id" { description = "The parent of this AccessPolicy in the Cloud Resource Hierarchy. As of now, only organization are accepted as parent." + type = string } variable "policy_name" { description = "The policy's name." + type = string } variable "protected_project_ids" { description = "Project id and number of the project INSIDE the regular service perimeter. This map variable expects an \"id\" for the project id and \"number\" key for the project number." - type = "map" -} - -variable "credentials_path" { - description = "Path to credentials.json key for service account deploying resources" + type = object({ id = string, number = number }) } variable "ip_subnetworks" { description = "A list of CIDR block IP subnetwork specification. May be IPv4 or IPv6. Note that for a CIDR IP address block, the specified IP address portion must be properly truncated (i.e. all the host bits must be zero) or the input is considered malformed. For example, \"192.0.2.0/24\" is accepted but \"192.0.2.1/24\" is not. Similarly, for IPv6, \"2001:db8::/32\" is accepted whereas \"2001:db8::1/32\" is not. The originating IP of a request must be in one of the listed subnets in order for this Condition to be true. If empty, all IP addresses are allowed." - type = "list" + type = list(string) } diff --git a/examples/simple_example_access_level/versions.tf b/examples/simple_example_access_level/versions.tf new file mode 100644 index 0000000..2970427 --- /dev/null +++ b/examples/simple_example_access_level/versions.tf @@ -0,0 +1,19 @@ +/** + * 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" +} diff --git a/examples/simple_example_bridge/README.md b/examples/simple_example_bridge/README.md index 6229801..d1baae7 100644 --- a/examples/simple_example_bridge/README.md +++ b/examples/simple_example_bridge/README.md @@ -14,17 +14,15 @@ You may use the following gcloud commands: `gcloud projects add-iam-policy-binding --member=serviceAccount: --role=roles/bigquery.jobUser` `gcloud projects add-iam-policy-binding --member=serviceAccount: --role=roles/bigquery.dataOwner` -[^]: (autogen_docs_start) - + ## Inputs | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| credentials\_path | Path to credentials.json key for service account deploying resources | string | n/a | yes | | parent\_id | The parent of this AccessPolicy in the Cloud Resource Hierarchy. As of now, only organization are accepted as parent. | string | n/a | yes | | policy\_name | The policy's name. | string | n/a | yes | -| protected\_project\_ids | Project id and number of the project INSIDE the regular service perimeter. This map variable expects an "id" for the project id and "number" key for the project number. | map | n/a | yes | -| public\_project\_ids | Project id and number of the project OUTSIDE of the regular service perimeter. This variable is only necessary for running integration tests. This map variable expects an "id" for the project id and "number" key for the project number. | map | n/a | yes | +| protected\_project\_ids | Project id and number of the project INSIDE the regular service perimeter. This map variable expects an "id" for the project id and "number" key for the project number. | object | n/a | yes | +| public\_project\_ids | Project id and number of the project OUTSIDE of the regular service perimeter. This variable is only necessary for running integration tests. This map variable expects an "id" for the project id and "number" key for the project number. | object | n/a | yes | ## Outputs @@ -32,7 +30,7 @@ You may use the following gcloud commands: |------|-------------| | policy\_name | | -[^]: (autogen_docs_end) + To provision this example, run the following from within this directory: - `terraform init` to get the plugins diff --git a/examples/simple_example_bridge/main.tf b/examples/simple_example_bridge/main.tf index c5afc7b..5afd511 100644 --- a/examples/simple_example_bridge/main.tf +++ b/examples/simple_example_bridge/main.tf @@ -15,52 +15,51 @@ */ provider "google" { - version = "~> 2.5.0" - credentials = "${file("${var.credentials_path}")}" + version = "~> 2.5.0" } -module "org_policy" { +module "access_context_manager_policy" { source = "../.." - parent_id = "${var.parent_id}" - policy_name = "${var.policy_name}" + parent_id = var.parent_id + policy_name = var.policy_name } module "bridge_service_perimeter_1" { source = "../../modules/bridge_service_perimeter" - policy = "${module.org_policy.policy_id}" + policy = module.access_context_manager_policy.policy_id perimeter_name = "bridge_perimeter_1" description = "Some description" - resources = [ - "${module.regular_service_perimeter_1.shared_resources["all"]}", - "${module.regular_service_perimeter_2.shared_resources["all"]}", - ] + resources = concat( + module.regular_service_perimeter_1.shared_resources["all"], + module.regular_service_perimeter_2.shared_resources["all"], + ) } module "regular_service_perimeter_1" { source = "../../modules/regular_service_perimeter" - policy = "${module.org_policy.policy_id}" + policy = module.access_context_manager_policy.policy_id perimeter_name = "regular_perimeter_1" description = "Some description" - resources = ["${var.protected_project_ids["number"]}"] + resources = [var.protected_project_ids["number"]] restricted_services = ["bigquery.googleapis.com", "storage.googleapis.com"] shared_resources = { - all = ["${var.protected_project_ids["number"]}"] + all = [var.protected_project_ids["number"]] } } module "regular_service_perimeter_2" { source = "../../modules/regular_service_perimeter" - policy = "${module.org_policy.policy_id}" + policy = module.access_context_manager_policy.policy_id perimeter_name = "regular_perimeter_2" description = "Some description" - resources = ["${var.public_project_ids["number"]}"] + resources = [var.public_project_ids["number"]] restricted_services = ["storage.googleapis.com"] shared_resources = { - all = ["${var.public_project_ids["number"]}"] + all = [var.public_project_ids["number"]] } } diff --git a/examples/simple_example_bridge/outputs.tf b/examples/simple_example_bridge/outputs.tf index 0809ceb..c086a36 100644 --- a/examples/simple_example_bridge/outputs.tf +++ b/examples/simple_example_bridge/outputs.tf @@ -15,5 +15,5 @@ */ output "policy_name" { - value = "${var.policy_name}" + value = var.policy_name } diff --git a/examples/simple_example_bridge/variables.tf b/examples/simple_example_bridge/variables.tf index da0ac6d..bd06f0c 100644 --- a/examples/simple_example_bridge/variables.tf +++ b/examples/simple_example_bridge/variables.tf @@ -16,22 +16,20 @@ variable "parent_id" { description = "The parent of this AccessPolicy in the Cloud Resource Hierarchy. As of now, only organization are accepted as parent." + type = string } variable "policy_name" { description = "The policy's name." + type = string } variable "protected_project_ids" { description = "Project id and number of the project INSIDE the regular service perimeter. This map variable expects an \"id\" for the project id and \"number\" key for the project number." - type = "map" + type = object({ id = string, number = number }) } variable "public_project_ids" { description = "Project id and number of the project OUTSIDE of the regular service perimeter. This variable is only necessary for running integration tests. This map variable expects an \"id\" for the project id and \"number\" key for the project number." - type = "map" -} - -variable "credentials_path" { - description = "Path to credentials.json key for service account deploying resources" + type = object({ id = string, number = number }) } diff --git a/examples/simple_example_bridge/versions.tf b/examples/simple_example_bridge/versions.tf new file mode 100644 index 0000000..2970427 --- /dev/null +++ b/examples/simple_example_bridge/versions.tf @@ -0,0 +1,19 @@ +/** + * 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" +} diff --git a/helpers/combine_docfiles.py b/helpers/combine_docfiles.py deleted file mode 100755 index 1c077c3..0000000 --- a/helpers/combine_docfiles.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/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. - -# Please note that this file was generated from -# [terraform-google-module-template](https://github.com/terraform-google-modules/terraform-google-module-template). -# Please make sure to contribute relevant changes upstream! - -''' Combine file from: - * script argument 1 - with content of file from: - * script argument 2 - using the beginning of line separators - hardcoded using regexes in this file: - - We exclude any text using the separate - regex specified here -''' - -import os -import re -import sys - -insert_separator_regex = r'(.*?\[\^\]\:\ \(autogen_docs_start\))(.*?)(\n\[\^\]\:\ \(autogen_docs_end\).*?$)' # noqa: E501 -exclude_separator_regex = r'(.*?)Copyright 20\d\d Google LLC.*?limitations under the License.(.*?)$' # noqa: E501 - -if len(sys.argv) != 3: - sys.exit(1) - -if not os.path.isfile(sys.argv[1]): - sys.exit(0) - -input = open(sys.argv[1], "r").read() -replace_content = open(sys.argv[2], "r").read() - -# Exclude the specified content from the replacement content -groups = re.match( - exclude_separator_regex, - replace_content, - re.DOTALL -).groups(0) -replace_content = groups[0] + groups[1] - -# Find where to put the replacement content, overwrite the input file -match = re.match(insert_separator_regex, input, re.DOTALL) -if match is None: - print("ERROR: Could not find autogen docs anchors in", sys.argv[1]) - print("To fix this, insert the following anchors in your README where " - "module inputs and outputs should be documented.") - print("[^]: (autogen_docs_start)") - print("[^]: (autogen_docs_end)") - sys.exit(1) -groups = match.groups(0) -output = groups[0] + replace_content + groups[2] + "\n" -open(sys.argv[1], "w").write(output) diff --git a/helpers/terraform_docs b/helpers/terraform_docs new file mode 100755 index 0000000..0935b69 --- /dev/null +++ b/helpers/terraform_docs @@ -0,0 +1,694 @@ +#!/usr/bin/env bash + +set -e + +main() { + declare argv + argv=$(getopt -o a: --long args: -- "$@") || return + eval "set -- $argv" + + declare args + declare files + + for argv; do + case $argv in + (-a|--args) + shift + args="$1" + shift + ;; + (--) + shift + files="$@" + break + ;; + esac + done + + local hack_terraform_docs=$(terraform version | head -1 | grep -c 0.12) + + if [[ "$hack_terraform_docs" == "1" ]]; then + which awk 2>&1 >/dev/null || ( echo "awk is required for terraform-docs hack to work with Terraform 0.12"; exit 1) + + tmp_file_awk=$(mktemp "${TMPDIR:-/tmp}/terraform-docs-XXXXXXXXXX") + terraform_docs_awk "$tmp_file_awk" + terraform_docs "$tmp_file_awk" "$args" "$files" + rm -f "$tmp_file_awk" + else + terraform_docs "0" "$args" "$files" + fi + +} + +terraform_docs() { + readonly terraform_docs_awk_file="$1" + readonly args="$2" + readonly files="$3" + + declare -a paths + declare -a tfvars_files + + index=0 + + for file_with_path in $files; do + file_with_path="${file_with_path// /__REPLACED__SPACE__}" + + paths[index]=$(dirname "$file_with_path") + + if [[ "$file_with_path" == *".tfvars" ]]; then + tfvars_files+=("$file_with_path") + fi + + ((index+=1)) + done + + readonly tmp_file=$(mktemp) + readonly text_file="README.md" + + for path_uniq in $(echo "${paths[*]}" | tr ' ' '\n' | sort -u); do + path_uniq="${path_uniq//__REPLACED__SPACE__/ }" + + pushd "$path_uniq" > /dev/null + + if [[ ! -f "$text_file" ]]; then + popd > /dev/null + continue + fi + + if [[ "$terraform_docs_awk_file" == "0" ]]; then + terraform-docs $args md ./ > "$tmp_file" + else + # Can't append extension for mktemp, so renaming instead + tmp_file_docs=$(mktemp "${TMPDIR:-/tmp}/terraform-docs-XXXXXXXXXX") + mv "$tmp_file_docs" "$tmp_file_docs.tf" + tmp_file_docs_tf="$tmp_file_docs.tf" + + awk -f "$terraform_docs_awk_file" ./*.tf > "$tmp_file_docs_tf" + terraform-docs $args md "$tmp_file_docs_tf" > "$tmp_file" + rm -f "$tmp_file_docs_tf" + fi + + # Replace content between markers with the placeholder - https://stackoverflow.com/questions/1212799/how-do-i-extract-lines-between-two-line-delimiters-in-perl#1212834 + perl -i -ne 'if (/BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK/../END OF PRE-COMMIT-TERRAFORM DOCS HOOK/) { print $_ if /BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK/; print "I_WANT_TO_BE_REPLACED\n$_" if /END OF PRE-COMMIT-TERRAFORM DOCS HOOK/;} else { print $_ }' "$text_file" + + # Replace placeholder with the content of the file + perl -i -e 'open(F, "'"$tmp_file"'"); $f = join "", ; while(<>){if (/I_WANT_TO_BE_REPLACED/) {print $f} else {print $_};}' "$text_file" + + rm -f "$tmp_file" + + popd > /dev/null + done +} + +terraform_docs_awk() { + readonly output_file=$1 + + cat <<"EOF" > $output_file +# This script converts Terraform 0.12 variables/outputs to something suitable for `terraform-docs` +# As of terraform-docs v0.6.0, HCL2 is not supported. This script is a *dirty hack* to get around it. +# https://github.com/segmentio/terraform-docs/ +# https://github.com/segmentio/terraform-docs/issues/62 + +# Script was originally found here: https://github.com/cloudposse/build-harness/blob/master/bin/terraform-docs.awk + +{ + if ( $0 ~ /\{/ ) { + braceCnt++ + } + + if ( $0 ~ /\}/ ) { + braceCnt-- + } + + # [START] variable or output block started + if ($0 ~ /^[[:space:]]*(variable|output)[[:space:]][[:space:]]*"(.*?)"/) { + # Normalize the braceCnt (should be 1 now) + braceCnt = 1 + # [CLOSE] "default" block + if (blockDefCnt > 0) { + blockDefCnt = 0 + } + blockCnt++ + print $0 + } + + # [START] multiline default statement started + if (blockCnt > 0) { + if ($0 ~ /^[[:space:]][[:space:]]*(default)[[:space:]][[:space:]]*=/) { + if ($3 ~ "null") { + print " default = \"null\"" + } else { + print $0 + blockDefCnt++ + blockDefStart=1 + } + } + } + + # [PRINT] single line "description" + if (blockCnt > 0) { + if (blockDefCnt == 0) { + if ($0 ~ /^[[:space:]][[:space:]]*description[[:space:]][[:space:]]*=/) { + # [CLOSE] "default" block + if (blockDefCnt > 0) { + blockDefCnt = 0 + } + print $0 + } + } + } + + # [PRINT] single line "type" + if (blockCnt > 0) { + if ($0 ~ /^[[:space:]][[:space:]]*type[[:space:]][[:space:]]*=/ ) { + # [CLOSE] "default" block + if (blockDefCnt > 0) { + blockDefCnt = 0 + } + type=$3 + if (type ~ "object") { + print " type = \"object\"" + } else { + # legacy quoted types: "string", "list", and "map" + if ($3 ~ /^[[:space:]]*"(.*?)"[[:space:]]*$/) { + print " type = " $3 + } else { + print " type = \"" $3 "\"" + } + } + } + } + + # [CLOSE] variable/output block + if (blockCnt > 0) { + if (braceCnt == 0 && blockCnt > 0) { + blockCnt-- + print $0 + } + } + + # [PRINT] Multiline "default" statement + if (blockCnt > 0 && blockDefCnt > 0) { + if (blockDefStart == 1) { + blockDefStart = 0 + } else { + print $0 + } + } +} +EOF + +} + +getopt() { + # pure-getopt, a drop-in replacement for GNU getopt in pure Bash. + # version 1.4.3 + # + # Copyright 2012-2018 Aron Griffis + # + # Permission is hereby granted, free of charge, to any person obtaining + # a copy of this software and associated documentation files (the + # "Software"), to deal in the Software without restriction, including + # without limitation the rights to use, copy, modify, merge, publish, + # distribute, sublicense, and/or sell copies of the Software, and to + # permit persons to whom the Software is furnished to do so, subject to + # the following conditions: + # + # The above copyright notice and this permission notice shall be included + # in all copies or substantial portions of the Software. + # + # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + _getopt_main() { + # Returns one of the following statuses: + # 0 success + # 1 error parsing parameters + # 2 error in getopt invocation + # 3 internal error + # 4 reserved for -T + # + # For statuses 0 and 1, generates normalized and shell-quoted + # "options -- parameters" on stdout. + + declare parsed status + declare short long name flags + declare have_short=false + + # Synopsis from getopt man-page: + # + # getopt optstring parameters + # getopt [options] [--] optstring parameters + # getopt [options] -o|--options optstring [options] [--] parameters + # + # The first form can be normalized to the third form which + # _getopt_parse() understands. The second form can be recognized after + # first parse when $short hasn't been set. + + if [[ -n ${GETOPT_COMPATIBLE+isset} || $1 == [^-]* ]]; then + # Enable compatibility mode + flags=c$flags + # Normalize first to third synopsis form + set -- -o "$1" -- "${@:2}" + fi + + # First parse always uses flags=p since getopt always parses its own + # arguments effectively in this mode. + parsed=$(_getopt_parse getopt ahl:n:o:qQs:TuV \ + alternative,help,longoptions:,name:,options:,quiet,quiet-output,shell:,test,version \ + p "$@") + status=$? + if [[ $status != 0 ]]; then + if [[ $status == 1 ]]; then + echo "Try \`getopt --help' for more information." >&2 + # Since this is the first parse, convert status 1 to 2 + status=2 + fi + return $status + fi + eval "set -- $parsed" + + while [[ $# -gt 0 ]]; do + case $1 in + (-a|--alternative) + flags=a$flags ;; + + (-h|--help) + _getopt_help + return 2 # as does GNU getopt + ;; + + (-l|--longoptions) + long="$long${long:+,}$2" + shift ;; + + (-n|--name) + name=$2 + shift ;; + + (-o|--options) + short=$2 + have_short=true + shift ;; + + (-q|--quiet) + flags=q$flags ;; + + (-Q|--quiet-output) + flags=Q$flags ;; + + (-s|--shell) + case $2 in + (sh|bash) + flags=${flags//t/} ;; + (csh|tcsh) + flags=t$flags ;; + (*) + echo 'getopt: unknown shell after -s or --shell argument' >&2 + echo "Try \`getopt --help' for more information." >&2 + return 2 ;; + esac + shift ;; + + (-u|--unquoted) + flags=u$flags ;; + + (-T|--test) + return 4 ;; + + (-V|--version) + echo "pure-getopt 1.4.3" + return 0 ;; + + (--) + shift + break ;; + esac + + shift + done + + if ! $have_short; then + # $short was declared but never set, not even to an empty string. + # This implies the second form in the synopsis. + if [[ $# == 0 ]]; then + echo 'getopt: missing optstring argument' >&2 + echo "Try \`getopt --help' for more information." >&2 + return 2 + fi + short=$1 + have_short=true + shift + fi + + if [[ $short == -* ]]; then + # Leading dash means generate output in place rather than reordering, + # unless we're already in compatibility mode. + [[ $flags == *c* ]] || flags=i$flags + short=${short#?} + elif [[ $short == +* ]]; then + # Leading plus means POSIXLY_CORRECT, unless we're already in + # compatibility mode. + [[ $flags == *c* ]] || flags=p$flags + short=${short#?} + fi + + # This should fire if POSIXLY_CORRECT is in the environment, even if + # it's an empty string. That's the difference between :+ and + + flags=${POSIXLY_CORRECT+p}$flags + + _getopt_parse "${name:-getopt}" "$short" "$long" "$flags" "$@" + } + + _getopt_parse() { + # Inner getopt parser, used for both first parse and second parse. + # Returns 0 for success, 1 for error parsing, 3 for internal error. + # In the case of status 1, still generates stdout with whatever could + # be parsed. + # + # $flags is a string of characters with the following meanings: + # a - alternative parsing mode + # c - GETOPT_COMPATIBLE + # i - generate output in place rather than reordering + # p - POSIXLY_CORRECT + # q - disable error reporting + # Q - disable normal output + # t - quote for csh/tcsh + # u - unquoted output + + declare name="$1" short="$2" long="$3" flags="$4" + shift 4 + + # Split $long on commas, prepend double-dashes, strip colons; + # for use with _getopt_resolve_abbrev + declare -a longarr + _getopt_split longarr "$long" + longarr=( "${longarr[@]/#/--}" ) + longarr=( "${longarr[@]%:}" ) + longarr=( "${longarr[@]%:}" ) + + # Parse and collect options and parameters + declare -a opts params + declare o alt_recycled=false error=0 + + while [[ $# -gt 0 ]]; do + case $1 in + (--) + params=( "${params[@]}" "${@:2}" ) + break ;; + + (--*=*) + o=${1%%=*} + if ! o=$(_getopt_resolve_abbrev "$o" "${longarr[@]}"); then + error=1 + elif [[ ,"$long", == *,"${o#--}"::,* ]]; then + opts=( "${opts[@]}" "$o" "${1#*=}" ) + elif [[ ,"$long", == *,"${o#--}":,* ]]; then + opts=( "${opts[@]}" "$o" "${1#*=}" ) + elif [[ ,"$long", == *,"${o#--}",* ]]; then + if $alt_recycled; then o=${o#-}; fi + _getopt_err "$name: option '$o' doesn't allow an argument" + error=1 + else + echo "getopt: assertion failed (1)" >&2 + return 3 + fi + alt_recycled=false + ;; + + (--?*) + o=$1 + if ! o=$(_getopt_resolve_abbrev "$o" "${longarr[@]}"); then + error=1 + elif [[ ,"$long", == *,"${o#--}",* ]]; then + opts=( "${opts[@]}" "$o" ) + elif [[ ,"$long", == *,"${o#--}::",* ]]; then + opts=( "${opts[@]}" "$o" '' ) + elif [[ ,"$long", == *,"${o#--}:",* ]]; then + if [[ $# -ge 2 ]]; then + shift + opts=( "${opts[@]}" "$o" "$1" ) + else + if $alt_recycled; then o=${o#-}; fi + _getopt_err "$name: option '$o' requires an argument" + error=1 + fi + else + echo "getopt: assertion failed (2)" >&2 + return 3 + fi + alt_recycled=false + ;; + + (-*) + if [[ $flags == *a* ]]; then + # Alternative parsing mode! + # Try to handle as a long option if any of the following apply: + # 1. There's an equals sign in the mix -x=3 or -xy=3 + # 2. There's 2+ letters and an abbreviated long match -xy + # 3. There's a single letter and an exact long match + # 4. There's a single letter and no short match + o=${1::2} # temp for testing #4 + if [[ $1 == *=* || $1 == -?? || \ + ,$long, == *,"${1#-}"[:,]* || \ + ,$short, != *,"${o#-}"[:,]* ]]; then + o=$(_getopt_resolve_abbrev "${1%%=*}" "${longarr[@]}" 2>/dev/null) + case $? in + (0) + # Unambiguous match. Let the long options parser handle + # it, with a flag to get the right error message. + set -- "-$1" "${@:2}" + alt_recycled=true + continue ;; + (1) + # Ambiguous match, generate error and continue. + _getopt_resolve_abbrev "${1%%=*}" "${longarr[@]}" >/dev/null + error=1 + shift + continue ;; + (2) + # No match, fall through to single-character check. + true ;; + (*) + echo "getopt: assertion failed (3)" >&2 + return 3 ;; + esac + fi + fi + + o=${1::2} + if [[ "$short" == *"${o#-}"::* ]]; then + if [[ ${#1} -gt 2 ]]; then + opts=( "${opts[@]}" "$o" "${1:2}" ) + else + opts=( "${opts[@]}" "$o" '' ) + fi + elif [[ "$short" == *"${o#-}":* ]]; then + if [[ ${#1} -gt 2 ]]; then + opts=( "${opts[@]}" "$o" "${1:2}" ) + elif [[ $# -ge 2 ]]; then + shift + opts=( "${opts[@]}" "$o" "$1" ) + else + _getopt_err "$name: option requires an argument -- '${o#-}'" + error=1 + fi + elif [[ "$short" == *"${o#-}"* ]]; then + opts=( "${opts[@]}" "$o" ) + if [[ ${#1} -gt 2 ]]; then + set -- "$o" "-${1:2}" "${@:2}" + fi + else + if [[ $flags == *a* ]]; then + # Alternative parsing mode! Report on the entire failed + # option. GNU includes =value but we omit it for sanity with + # very long values. + _getopt_err "$name: unrecognized option '${1%%=*}'" + else + _getopt_err "$name: invalid option -- '${o#-}'" + if [[ ${#1} -gt 2 ]]; then + set -- "$o" "-${1:2}" "${@:2}" + fi + fi + error=1 + fi ;; + + (*) + # GNU getopt in-place mode (leading dash on short options) + # overrides POSIXLY_CORRECT + if [[ $flags == *i* ]]; then + opts=( "${opts[@]}" "$1" ) + elif [[ $flags == *p* ]]; then + params=( "${params[@]}" "$@" ) + break + else + params=( "${params[@]}" "$1" ) + fi + esac + + shift + done + + if [[ $flags == *Q* ]]; then + true # generate no output + else + echo -n ' ' + if [[ $flags == *[cu]* ]]; then + printf '%s -- %s' "${opts[*]}" "${params[*]}" + else + if [[ $flags == *t* ]]; then + _getopt_quote_csh "${opts[@]}" -- "${params[@]}" + else + _getopt_quote "${opts[@]}" -- "${params[@]}" + fi + fi + echo + fi + + return $error + } + + _getopt_err() { + if [[ $flags != *q* ]]; then + printf '%s\n' "$1" >&2 + fi + } + + _getopt_resolve_abbrev() { + # Resolves an abbrevation from a list of possibilities. + # If the abbreviation is unambiguous, echoes the expansion on stdout + # and returns 0. If the abbreviation is ambiguous, prints a message on + # stderr and returns 1. (For first parse this should convert to exit + # status 2.) If there is no match at all, prints a message on stderr + # and returns 2. + declare a q="$1" + declare -a matches + shift + for a; do + if [[ $q == "$a" ]]; then + # Exact match. Squash any other partial matches. + matches=( "$a" ) + break + elif [[ $flags == *a* && $q == -[^-]* && $a == -"$q" ]]; then + # Exact alternative match. Squash any other partial matches. + matches=( "$a" ) + break + elif [[ $a == "$q"* ]]; then + # Abbreviated match. + matches=( "${matches[@]}" "$a" ) + elif [[ $flags == *a* && $q == -[^-]* && $a == -"$q"* ]]; then + # Abbreviated alternative match. + matches=( "${matches[@]}" "${a#-}" ) + fi + done + case ${#matches[@]} in + (0) + [[ $flags == *q* ]] || \ + printf "$name: unrecognized option %s\\n" >&2 \ + "$(_getopt_quote "$q")" + return 2 ;; + (1) + printf '%s' "${matches[0]}"; return 0 ;; + (*) + [[ $flags == *q* ]] || \ + printf "$name: option %s is ambiguous; possibilities: %s\\n" >&2 \ + "$(_getopt_quote "$q")" "$(_getopt_quote "${matches[@]}")" + return 1 ;; + esac + } + + _getopt_split() { + # Splits $2 at commas to build array specified by $1 + declare IFS=, + eval "$1=( \$2 )" + } + + _getopt_quote() { + # Quotes arguments with single quotes, escaping inner single quotes + declare s space q=\' + for s; do + printf "$space'%s'" "${s//$q/$q\\$q$q}" + space=' ' + done + } + + _getopt_quote_csh() { + # Quotes arguments with single quotes, escaping inner single quotes, + # bangs, backslashes and newlines + declare s i c space + for s; do + echo -n "$space'" + for ((i=0; i<${#s}; i++)); do + c=${s:i:1} + case $c in + (\\|\'|!) + echo -n "'\\$c'" ;; + ($'\n') + echo -n "\\$c" ;; + (*) + echo -n "$c" ;; + esac + done + echo -n \' + space=' ' + done + } + + _getopt_help() { + cat <<-EOT >&2 + + Usage: + getopt + getopt [options] [--] + getopt [options] -o|--options [options] [--] + + Parse command options. + + Options: + -a, --alternative allow long options starting with single - + -l, --longoptions the long options to be recognized + -n, --name the name under which errors are reported + -o, --options the short options to be recognized + -q, --quiet disable error reporting by getopt(3) + -Q, --quiet-output no normal output + -s, --shell set quoting conventions to those of + -T, --test test for getopt(1) version + -u, --unquoted do not quote the output + + -h, --help display this help and exit + -V, --version output version information and exit + + For more details see getopt(1). + EOT + } + + _getopt_version_check() { + if [[ -z $BASH_VERSION ]]; then + echo "getopt: unknown version of bash might not be compatible" >&2 + return 1 + fi + + # This is a lexical comparison that should be sufficient forever. + if [[ $BASH_VERSION < 2.05b ]]; then + echo "getopt: bash $BASH_VERSION might not be compatible" >&2 + return 1 + fi + + return 0 + } + + _getopt_version_check + _getopt_main "$@" + declare status=$? + unset -f _getopt_main _getopt_err _getopt_parse _getopt_quote \ + _getopt_quote_csh _getopt_resolve_abbrev _getopt_split _getopt_help \ + _getopt_version_check + return $status +} + +[[ $BASH_SOURCE != "$0" ]] || main "$@" diff --git a/helpers/terraform_validate b/helpers/terraform_validate new file mode 100755 index 0000000..7f60982 --- /dev/null +++ b/helpers/terraform_validate @@ -0,0 +1,23 @@ +#! /bin/bash +# +# Copyright 2019 Google LLC. This software is provided as-is, without warranty +# or representation for any use or purpose. Your use of it is subject to your +# agreement with Google. +# +# This script initializes modules so that terraform validate as of 0.12 behaves +# as expected and does not issue errors such as: +# +# Error: Module not installed +# +# on test/fixtures/shared_vpc_no_subnets/main.tf line 37: +# 37: module "project-factory" { +# +# This module is not yet installed. Run "terraform init" to install all modules +# required by this configuration. + +# The first and only argument to this script is the directory containing *.tf +# files to validate. This directory is assumed to be a root module. + +cd "$1" +terraform init -backend=false +terraform validate diff --git a/main.tf b/main.tf index c018b9d..5ea3ba1 100644 --- a/main.tf +++ b/main.tf @@ -15,7 +15,7 @@ */ resource "google_access_context_manager_access_policy" "access_policy" { - provider = "google" + provider = google parent = "organizations/${var.parent_id}" - title = "${var.policy_name}" + title = var.policy_name } diff --git a/modules/access_level/README.md b/modules/access_level/README.md index b97127c..348aedf 100644 --- a/modules/access_level/README.md +++ b/modules/access_level/README.md @@ -2,46 +2,44 @@ This module handles opiniated configuration and deployment of [access_context_manager_level](https://www.terraform.io/docs/providers/google/r/access_context_manager_access_level.html) resource. -## Usage +## Usage ```hcl provider "google" { version = "~> 2.5.0" - credentials = "${file("${var.credentials_path}")}" } module "org_policy" { source = "terraform-google-modules/vpc-service-controls/google" - parent_id = "${var.parent_id}" - policy_name = "${var.policy_name}" + parent_id = var.parent_id + policy_name = var.policy_name } module "access_level_members" { source = "terraform-google-modules/vpc-service-controls/google/modules/access_level" - policy = "${module.org_policy.policy_id}" + policy = module.org_policy.policy_id name = "terraform_members" members = ["serviceAccount:", "user:"] } ``` -[^]: (autogen_docs_start) - + ## Inputs | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| allowed\_device\_management\_levels | Condition - A list of allowed device management levels. An empty list allows all management levels. | list | `` | no | -| allowed\_encryption\_statuses | Condition - A list of allowed encryptions statuses. An empty list allows all statuses. | list | `` | no | +| allowed\_device\_management\_levels | Condition - A list of allowed device management levels. An empty list allows all management levels. | list(string) | `` | no | +| allowed\_encryption\_statuses | Condition - A list of allowed encryptions statuses. An empty list allows all statuses. | list(string) | `` | no | | combining\_function | How the conditions list should be combined to determine if a request is granted this AccessLevel. If AND is used, each Condition must be satisfied for the AccessLevel to be applied. If OR is used, at least one Condition must be satisfied for the AccessLevel to be applied. | string | `"AND"` | no | -| description | Description of the access level | string | `""` | no | -| ip\_subnetworks | Condition - A list of CIDR block IP subnetwork specification. May be IPv4 or IPv6. Note that for a CIDR IP address block, the specified IP address portion must be properly truncated (i.e. all the host bits must be zero) or the input is considered malformed. For example, "192.0.2.0/24" is accepted but "192.0.2.1/24" is not. Similarly, for IPv6, "2001:db8::/32" is accepted whereas "2001:db8::1/32" is not. The originating IP of a request must be in one of the listed subnets in order for this Condition to be true. If empty, all IP addresses are allowed. | list | `` | no | -| members | Condition - An allowed list of members (users, service accounts). The signed-in identity originating the request must be a part of one of the provided members. If not specified, a request may come from any user (logged in/not logged in, etc.). Formats: user:{emailid}, serviceAccount:{emailid} | list | `` | no | +| description | Description of the access level | string | n/a | yes | +| ip\_subnetworks | Condition - A list of CIDR block IP subnetwork specification. May be IPv4 or IPv6. Note that for a CIDR IP address block, the specified IP address portion must be properly truncated (i.e. all the host bits must be zero) or the input is considered malformed. For example, "192.0.2.0/24" is accepted but "192.0.2.1/24" is not. Similarly, for IPv6, "2001:db8::/32" is accepted whereas "2001:db8::1/32" is not. The originating IP of a request must be in one of the listed subnets in order for this Condition to be true. If empty, all IP addresses are allowed. | list(string) | `` | no | +| members | Condition - An allowed list of members (users, service accounts). The signed-in identity originating the request must be a part of one of the provided members. If not specified, a request may come from any user (logged in/not logged in, etc.). Formats: user:{emailid}, serviceAccount:{emailid} | list(string) | `` | no | | minimum\_version | The minimum allowed OS version. If not set, any version of this OS satisfies the constraint. Format: "major.minor.patch" such as "10.5.301", "9.2.1". | string | `""` | no | | name | Description of the AccessLevel and its use. Does not affect behavior. | string | n/a | yes | -| negate | Whether to negate the Condition. If true, the Condition becomes a NAND over its non-empty fields, each field must be false for the Condition overall to be satisfied. | string | `"false"` | no | +| negate | Whether to negate the Condition. If true, the Condition becomes a NAND over its non-empty fields, each field must be false for the Condition overall to be satisfied. | bool | `"false"` | no | | os\_type | The operating system type of the device. | string | `""` | no | | policy | Name of the parent policy | string | n/a | yes | -| require\_screen\_lock | Condition - Whether or not screenlock is required for the DevicePolicy to be true. | string | `"false"` | no | -| required\_access\_levels | Condition - A list of other access levels defined in the same Policy, referenced by resource name. Referencing an AccessLevel which does not exist is an error. All access levels listed must be granted for the Condition to be true. | list | `` | no | +| require\_screen\_lock | Condition - Whether or not screenlock is required for the DevicePolicy to be true. | bool | `"false"` | no | +| required\_access\_levels | Condition - A list of other access levels defined in the same Policy, referenced by resource name. Referencing an AccessLevel which does not exist is an error. All access levels listed must be granted for the Condition to be true. | list(string) | `` | no | ## Outputs @@ -49,4 +47,4 @@ module "access_level_members" { |------|-------------| | name | Description of the AccessLevel and its use. Does not affect behavior. | -[^]: (autogen_docs_end) + diff --git a/modules/access_level/main.tf b/modules/access_level/main.tf index 8bfee67..335d8fe 100644 --- a/modules/access_level/main.tf +++ b/modules/access_level/main.tf @@ -15,33 +15,31 @@ */ resource "google_access_context_manager_access_level" "access_level" { - provider = "google" + provider = google parent = "accessPolicies/${var.policy}" name = "accessPolicies/${var.policy}/accessLevels/${var.name}" - title = "${var.name}" - description = "${var.description}" + title = var.name + description = var.description - basic = [ - { - conditions = [{ - ip_subnetworks = "${var.ip_subnetworks}" - required_access_levels = "${var.required_access_levels}" - members = "${var.members}" - negate = "${var.negate}" + basic { + conditions { + ip_subnetworks = var.ip_subnetworks + required_access_levels = var.required_access_levels + members = var.members + negate = var.negate - device_policy { - require_screen_lock = "${var.require_screen_lock}" - allowed_encryption_statuses = "${var.allowed_encryption_statuses}" - allowed_device_management_levels = "${var.allowed_device_management_levels}" + device_policy { + require_screen_lock = var.require_screen_lock + allowed_encryption_statuses = var.allowed_encryption_statuses + allowed_device_management_levels = var.allowed_device_management_levels - os_constraints = [{ - minimum_version = "${var.minimum_version}" - os_type = "${var.os_type}" - }] + os_constraints { + minimum_version = var.minimum_version + os_type = var.os_type } - }] + } + } - combining_function = "${var.combining_function}" - }, - ] + combining_function = var.combining_function + } } diff --git a/modules/access_level/outputs.tf b/modules/access_level/outputs.tf index 5a9aa87..52aceff 100644 --- a/modules/access_level/outputs.tf +++ b/modules/access_level/outputs.tf @@ -16,5 +16,5 @@ output "name" { description = "Description of the AccessLevel and its use. Does not affect behavior." - value = "${var.name}" + value = var.name } diff --git a/modules/access_level/variables.tf b/modules/access_level/variables.tf index 840f147..0558492 100644 --- a/modules/access_level/variables.tf +++ b/modules/access_level/variables.tf @@ -16,68 +16,76 @@ variable "name" { description = "Description of the AccessLevel and its use. Does not affect behavior." + type = string } variable "policy" { description = "Name of the parent policy" + type = string } variable "combining_function" { description = "How the conditions list should be combined to determine if a request is granted this AccessLevel. If AND is used, each Condition must be satisfied for the AccessLevel to be applied. If OR is used, at least one Condition must be satisfied for the AccessLevel to be applied." + type = string default = "AND" } variable "description" { description = "Description of the access level" + type = string default = "" } variable "ip_subnetworks" { description = "Condition - A list of CIDR block IP subnetwork specification. May be IPv4 or IPv6. Note that for a CIDR IP address block, the specified IP address portion must be properly truncated (i.e. all the host bits must be zero) or the input is considered malformed. For example, \"192.0.2.0/24\" is accepted but \"192.0.2.1/24\" is not. Similarly, for IPv6, \"2001:db8::/32\" is accepted whereas \"2001:db8::1/32\" is not. The originating IP of a request must be in one of the listed subnets in order for this Condition to be true. If empty, all IP addresses are allowed." - type = "list" + type = list(string) default = [] } variable "required_access_levels" { description = "Condition - A list of other access levels defined in the same Policy, referenced by resource name. Referencing an AccessLevel which does not exist is an error. All access levels listed must be granted for the Condition to be true." - type = "list" + type = list(string) default = [] } variable "members" { description = "Condition - An allowed list of members (users, service accounts). The signed-in identity originating the request must be a part of one of the provided members. If not specified, a request may come from any user (logged in/not logged in, etc.). Formats: user:{emailid}, serviceAccount:{emailid}" - type = "list" + type = list(string) default = [] } variable "negate" { description = "Whether to negate the Condition. If true, the Condition becomes a NAND over its non-empty fields, each field must be false for the Condition overall to be satisfied." - default = "false" + type = bool + default = false } variable "require_screen_lock" { description = "Condition - Whether or not screenlock is required for the DevicePolicy to be true." - default = "false" + type = bool + default = false } variable "allowed_encryption_statuses" { description = "Condition - A list of allowed encryptions statuses. An empty list allows all statuses." - type = "list" + type = list(string) default = [] } variable "allowed_device_management_levels" { description = "Condition - A list of allowed device management levels. An empty list allows all management levels." - type = "list" + type = list(string) default = [] } variable "minimum_version" { description = "The minimum allowed OS version. If not set, any version of this OS satisfies the constraint. Format: \"major.minor.patch\" such as \"10.5.301\", \"9.2.1\"." + type = string default = "" } variable "os_type" { description = "The operating system type of the device." + type = string default = "" } diff --git a/modules/bridge_service_perimeter/README.md b/modules/bridge_service_perimeter/README.md index 42cb8a8..a0de402 100644 --- a/modules/bridge_service_perimeter/README.md +++ b/modules/bridge_service_perimeter/README.md @@ -6,30 +6,29 @@ This module handles opiniated configuration and deployment of a [access_context_ ```hcl provider "google" { version = "~> 2.5.0" - credentials = "${file("${var.credentials_path}")}" } module "org_policy" { - source = "terraform-google-modules/vpc-service-controls/google//modules/policy" - parent_id = "${var.parent_id}" - policy_name = "${var.policy_name}" + source = "terraform-google-modules/vpc-service-controls/google/modules/policy" + parent_id = var.parent_id + policy_name = var.policy_name } module "bridge_service_perimeter_1" { - source = "terraform-google-modules/vpc-service-controls/google//modules/bridge_service_perimeter" - policy = "${module.org_policy.policy_id}" + source = "terraform-google-modules/vpc-service-controls/google/modules/bridge_service_perimeter" + policy = module.org_policy.policy_id perimeter_name = "bridge_perimeter_1" description = "Some description" resources = [ - "${module.regular_service_perimeter_1.shared_resources["all"]}", - "${module.regular_service_perimeter_2.shared_resources["all"]}", + module.regular_service_perimeter_1.shared_resources["all"], + module.regular_service_perimeter_2.shared_resources["all"], ] } module "regular_service_perimeter_1" { - source = "terraform-google-modules/vpc-service-controls/google//modules/regular_service_perimeter" - policy = "${module.org_policy.policy_id}" + source = "terraform-google-modules/vpc-service-controls/google/modules/regular_service_perimeter" + policy = module.org_policy.policy_id perimeter_name = "regular_perimeter_1" description = "Some description" resources = ["1111111111"] @@ -42,8 +41,8 @@ module "regular_service_perimeter_1" { } module "regular_service_perimeter_2" { - source = "terraform-google-modules/vpc-service-controls/google//modules/regular_service_perimeter" - policy = "${module.org_policy.policy_id}" + source = "terraform-google-modules/vpc-service-controls/google/modules/regular_service_perimeter" + policy = module.org_policy.policy_id perimeter_name = "regular_perimeter_2" description = "Some description" resources = ["222222222222"] @@ -56,8 +55,7 @@ module "regular_service_perimeter_2" { } ``` -[^]: (autogen_docs_start) - + ## Inputs | Name | Description | Type | Default | Required | @@ -65,7 +63,7 @@ module "regular_service_perimeter_2" { | description | Description of the bridge perimeter | string | n/a | yes | | perimeter\_name | Name of the perimeter. Should be one unified string. Must only be letters, numbers and underscores | string | n/a | yes | | policy | Name of the parent policy | string | n/a | yes | -| resources | A list of GCP resources that are inside of the service perimeter. Currently only projects are allowed. | list | `` | no | +| resources | A list of GCP resources that are inside of the service perimeter. Currently only projects are allowed. | list(string) | n/a | yes | ## Outputs @@ -73,4 +71,4 @@ module "regular_service_perimeter_2" { |------|-------------| | resources | A list of GCP resources that are inside of the service perimeter. Currently only projects are allowed. | -[^]: (autogen_docs_end) + diff --git a/modules/bridge_service_perimeter/main.tf b/modules/bridge_service_perimeter/main.tf index 814ad3d..7395d28 100644 --- a/modules/bridge_service_perimeter/main.tf +++ b/modules/bridge_service_perimeter/main.tf @@ -15,13 +15,13 @@ */ resource "google_access_context_manager_service_perimeter" "bridge_service_perimeter" { - provider = "google" + provider = google parent = "accessPolicies/${var.policy}" perimeter_type = "PERIMETER_TYPE_BRIDGE" name = "accessPolicies/${var.policy}/servicePerimeters/${var.perimeter_name}" - title = "${var.perimeter_name}" + title = var.perimeter_name status { - resources = "${formatlist("projects/%s", var.resources)}" + resources = formatlist("projects/%s", var.resources) } } diff --git a/modules/bridge_service_perimeter/outputs.tf b/modules/bridge_service_perimeter/outputs.tf index 24b0df1..4c166cc 100644 --- a/modules/bridge_service_perimeter/outputs.tf +++ b/modules/bridge_service_perimeter/outputs.tf @@ -16,5 +16,5 @@ output "resources" { description = "A list of GCP resources that are inside of the service perimeter. Currently only projects are allowed." - value = "${var.resources}" + value = var.resources } diff --git a/modules/bridge_service_perimeter/variables.tf b/modules/bridge_service_perimeter/variables.tf index 0d383a2..f5e0060 100644 --- a/modules/bridge_service_perimeter/variables.tf +++ b/modules/bridge_service_perimeter/variables.tf @@ -16,18 +16,21 @@ variable "policy" { description = "Name of the parent policy" + type = string } variable "description" { description = "Description of the bridge perimeter" + type = string + default = "" } variable "perimeter_name" { description = "Name of the perimeter. Should be one unified string. Must only be letters, numbers and underscores" + type = string } variable "resources" { description = "A list of GCP resources that are inside of the service perimeter. Currently only projects are allowed." - type = "list" - default = [] + type = list(string) } diff --git a/modules/regular_service_perimeter/README.md b/modules/regular_service_perimeter/README.md index bd87bcd..0001daa 100644 --- a/modules/regular_service_perimeter/README.md +++ b/modules/regular_service_perimeter/README.md @@ -6,18 +6,17 @@ This module handles opiniated configuration and deployment of a [access_context_ ```hcl provider "google" { version = "~> 2.5.0" - credentials = "${file("${var.credentials_path}")}" } module "org_policy" { source = "terraform-google-modules/vpc-service-controls/google/modules/policy" - parent_id = "${var.parent_id}" - policy_name = "${var.policy_name}" + parent_id = var.parent_id + policy_name = var.policy_name } module "regular_service_perimeter_1" { source = "terraform-google-modules/vpc-service-controls/google/modules/regular_service_perimeter" - policy = "${module.org_policy.policy_id}" + policy = module.org_policy.policy_id perimeter_name = "regular_perimeter_1" description = "Some description" resources = ["1111111111"] @@ -30,19 +29,18 @@ module "regular_service_perimeter_1" { } ``` -[^]: (autogen_docs_start) - + ## Inputs | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| access\_levels | A list of AccessLevel resource names that allow resources within the ServicePerimeter to be accessed from the internet. AccessLevels listed must be in the same policy as this ServicePerimeter. Referencing a nonexistent AccessLevel is a syntax error. If no AccessLevel names are listed, resources within the perimeter can only be accessed via GCP calls with request origins within the perimeter. | list | `` | no | +| access\_levels | A list of AccessLevel resource names that allow resources within the ServicePerimeter to be accessed from the internet. AccessLevels listed must be in the same policy as this ServicePerimeter. Referencing a nonexistent AccessLevel is a syntax error. If no AccessLevel names are listed, resources within the perimeter can only be accessed via GCP calls with request origins within the perimeter. Example: 'accessPolicies/MY_POLICY/accessLevels/MY_LEVEL'. For Service Perimeter Bridge, must be empty. | list(string) | `` | no | | description | Description of the regular perimeter | string | n/a | yes | | perimeter\_name | Name of the perimeter. Should be one unified string. Must only be letters, numbers and underscores | string | n/a | yes | | policy | Name of the parent policy | string | n/a | yes | -| resources | A list of GCP resources that are inside of the service perimeter. Currently only projects are allowed. | list | `` | no | -| restricted\_services | GCP services that are subject to the Service Perimeter restrictions. Must contain a list of services. For example, if storage.googleapis.com is specified, access to the storage buckets inside the perimeter must meet the perimeter's access restrictions. | list | `` | no | -| shared\_resources | A map of lists of resources to share in a Bridge perimeter module. Each list should contain all or a subset of the perimeters resources | map | `` | no | +| resources | A list of GCP resources that are inside of the service perimeter. Currently only projects are allowed. | list(string) | `` | no | +| restricted\_services | GCP services that are subject to the Service Perimeter restrictions. Must contain a list of services. For example, if storage.googleapis.com is specified, access to the storage buckets inside the perimeter must meet the perimeter's access restrictions. | list(string) | `` | no | +| shared\_resources | A map of lists of resources to share in a Bridge perimeter module. Each list should contain all or a subset of the perimeters resources | object | `` | no | ## Outputs @@ -50,4 +48,4 @@ module "regular_service_perimeter_1" { |------|-------------| | shared\_resources | A map of lists of resources to share in a Bridge perimeter module. Each list should contain all or a subset of the perimeters resources | -[^]: (autogen_docs_end) + diff --git a/modules/regular_service_perimeter/main.tf b/modules/regular_service_perimeter/main.tf index 5963be4..e7b3f98 100644 --- a/modules/regular_service_perimeter/main.tf +++ b/modules/regular_service_perimeter/main.tf @@ -15,15 +15,18 @@ */ resource "google_access_context_manager_service_perimeter" "regular_service_perimeter" { - provider = "google" + provider = google parent = "accessPolicies/${var.policy}" perimeter_type = "PERIMETER_TYPE_REGULAR" name = "accessPolicies/${var.policy}/servicePerimeters/${var.perimeter_name}" - title = "${var.perimeter_name}" + title = var.perimeter_name status { - restricted_services = "${var.restricted_services}" - resources = ["${formatlist("projects/%s", var.resources)}"] - access_levels = ["${formatlist("accessPolicies/${var.policy}/accessLevels/%s", var.access_levels)}"] + restricted_services = var.restricted_services + resources = formatlist("projects/%s", var.resources) + access_levels = formatlist( + "accessPolicies/${var.policy}/accessLevels/%s", + var.access_levels + ) } } diff --git a/modules/regular_service_perimeter/outputs.tf b/modules/regular_service_perimeter/outputs.tf index 8d0de85..756159d 100644 --- a/modules/regular_service_perimeter/outputs.tf +++ b/modules/regular_service_perimeter/outputs.tf @@ -16,5 +16,5 @@ output "shared_resources" { description = "A map of lists of resources to share in a Bridge perimeter module. Each list should contain all or a subset of the perimeters resources" - value = "${var.shared_resources}" + value = var.shared_resources } diff --git a/modules/regular_service_perimeter/variables.tf b/modules/regular_service_perimeter/variables.tf index c67aaaf..ae803d6 100644 --- a/modules/regular_service_perimeter/variables.tf +++ b/modules/regular_service_perimeter/variables.tf @@ -16,10 +16,12 @@ variable "policy" { description = "Name of the parent policy" + type = string } variable "description" { description = "Description of the regular perimeter" + type = string } variable "perimeter_name" { @@ -28,22 +30,24 @@ variable "perimeter_name" { variable "restricted_services" { description = "GCP services that are subject to the Service Perimeter restrictions. Must contain a list of services. For example, if storage.googleapis.com is specified, access to the storage buckets inside the perimeter must meet the perimeter's access restrictions." - type = "list" + type = list(string) default = [] } variable "resources" { description = "A list of GCP resources that are inside of the service perimeter. Currently only projects are allowed." - type = "list" + type = list(string) default = [] } variable "access_levels" { - description = "A list of AccessLevel resource names that allow resources within the ServicePerimeter to be accessed from the internet. AccessLevels listed must be in the same policy as this ServicePerimeter. Referencing a nonexistent AccessLevel is a syntax error. If no AccessLevel names are listed, resources within the perimeter can only be accessed via GCP calls with request origins within the perimeter. " + description = "A list of AccessLevel resource names that allow resources within the ServicePerimeter to be accessed from the internet. AccessLevels listed must be in the same policy as this ServicePerimeter. Referencing a nonexistent AccessLevel is a syntax error. If no AccessLevel names are listed, resources within the perimeter can only be accessed via GCP calls with request origins within the perimeter. Example: 'accessPolicies/MY_POLICY/accessLevels/MY_LEVEL'. For Service Perimeter Bridge, must be empty." + type = list(string) default = [] } variable "shared_resources" { description = "A map of lists of resources to share in a Bridge perimeter module. Each list should contain all or a subset of the perimeters resources" - default = {} + type = object({ all = list(string) }) + default = { all = [] } } diff --git a/outputs.tf b/outputs.tf index 22f930f..611591d 100644 --- a/outputs.tf +++ b/outputs.tf @@ -16,10 +16,10 @@ output "policy_id" { description = "Resource name of the AccessPolicy." - value = "${google_access_context_manager_access_policy.access_policy.name}" + value = google_access_context_manager_access_policy.access_policy.name } output "policy_name" { description = "The policy's name." - value = "${var.policy_name}" + value = var.policy_name } diff --git a/test/fixtures/shared/outputs.tf b/test/fixtures/shared/outputs.tf index 4f07bdd..5901f7a 100644 --- a/test/fixtures/shared/outputs.tf +++ b/test/fixtures/shared/outputs.tf @@ -15,25 +15,17 @@ */ output "parent_id" { - value = "${var.parent_id}" + value = var.parent_id } output "policy_name" { - value = "${var.policy_name}" + value = var.policy_name } output "protected_project_id" { - value = "${var.protected_project_ids["id"]}" + value = var.protected_project_ids["id"] } output "public_project_id" { - value = "${var.public_project_ids["id"]}" -} - -output "dataset_id" { - value = "${module.example.dataset_id}" -} - -output "table_id" { - value = "${module.example.table_id}" + value = var.public_project_ids["id"] } diff --git a/test/fixtures/shared/variables.tf b/test/fixtures/shared/variables.tf index 272631c..7e0042d 100644 --- a/test/fixtures/shared/variables.tf +++ b/test/fixtures/shared/variables.tf @@ -14,29 +14,32 @@ * limitations under the License. */ +variable "project_id" { + description = "The project ID" + type = string +} + variable "parent_id" { description = "The parent of this AccessPolicy in the Cloud Resource Hierarchy. As of now, only organization are accepted as parent." + type = string } variable "policy_name" { description = "The policy's name." + type = string } variable "protected_project_ids" { description = "Project id and number of the project within the regular service perimeter" - type = "map" + type = object({ id = string, number = number }) } variable "public_project_ids" { description = "Project id and number of the project OUTSIDE of the regular service perimeter. This variable is only necessary for running integration tests. This map variable expects an \"id\" for the project id and \"number\" key for the project number." - type = "map" + type = object({ id = string, number = number }) } variable "members" { - type = "list" - default = [] -} - -variable "credentials_path" { - description = "Path to credentials.json key for service account deploying resources" + description = "An allowed list of members (users, service accounts). The signed-in identity originating the request must be a part of one of the provided members. If not specified, a request may come from any user (logged in/not logged in, etc.). Formats: user:{emailid}, serviceAccount:{emailid}" + type = list(string) } diff --git a/test/fixtures/simple_example/main.tf b/test/fixtures/simple_example/main.tf index ac062e4..a5b815b 100644 --- a/test/fixtures/simple_example/main.tf +++ b/test/fixtures/simple_example/main.tf @@ -15,9 +15,8 @@ */ module "example" { source = "../../../examples/simple_example" - parent_id = "${var.parent_id}" - policy_name = "${var.policy_name}" - protected_project_ids = "${var.protected_project_ids}" - members = "${var.members}" - credentials_path = "${var.credentials_path}" + parent_id = var.parent_id + policy_name = var.policy_name + protected_project_ids = var.protected_project_ids + members = var.members } diff --git a/test/make.sh b/test/make.sh index 5a9cd37..ee471fa 100755 --- a/test/make.sh +++ b/test/make.sh @@ -35,13 +35,18 @@ maketemp() { find_files() { local pth="$1" shift - find "${pth}" '(' -path '*/.git' -o -path '*/.terraform' ')' \ + find "${pth}" '(' \ + -path '*/.git' \ + -o -path '*/.terraform' \ + -o -path '*/.kitchen' \ + -o -path '*/.idea' \ + ')' \ -prune -o -type f "$@" } # Compatibility with both GNU and BSD style xargs. compat_xargs() { - local compat=() + local compat=() rval # Test if xargs is GNU or BSD style. GNU xargs will succeed with status 0 # when given --no-run-if-empty and no input on STDIN. BSD xargs will fail and # exit status non-zero If xargs fails, assume it is BSD style and proceed. @@ -50,6 +55,11 @@ compat_xargs() { compat=("--no-run-if-empty") fi xargs "${compat[@]}" "$@" + rval="$?" + if [[ -z "${NOWARN:-}" ]] && [[ "${rval}" -gt 0 ]]; then + echo "Warning: compat_xargs $* failed with exit code ${rval}" >&2 + fi + return "${rval}" } # This function makes sure that the required files for @@ -73,12 +83,25 @@ function docker() { # This function runs 'terraform validate' against all # directory paths which contain *.tf files. function check_terraform() { - echo "Running terraform validate" + local rval=125 + # fmt is before validate for faster feedback, validate requires terraform + # init which takes time. + echo "Running terraform fmt" find_files . -name "*.tf" -print0 \ | compat_xargs -0 -n1 dirname \ | sort -u \ - | grep -xv './test/fixtures/shared' \ - | compat_xargs -t -n1 terraform validate --check-variables=false + | compat_xargs -t -n1 terraform fmt -diff -check=true -write=false + rval="$?" + if [[ "${rval}" -gt 0 ]]; then + echo "Error: terraform fmt failed with exit code ${rval}" >&2 + echo "Check the output for diffs and correct using terraform fmt " >&2 + return "${rval}" + fi + echo "Running terraform validate" + find_files . -not -path "./test/fixtures/shared/*" -name "*.tf" -print0 \ + | compat_xargs -0 -n1 dirname \ + | sort -u \ + | compat_xargs -t -n1 helpers/terraform_validate } # This function runs 'go fmt' and 'go vet' on every file @@ -112,7 +135,7 @@ function check_trailing_whitespace() { echo "Checking for trailing whitespace" find_files . -print \ | grep -v -E '\.(pyc|png)$' \ - | compat_xargs grep -H -n '[[:blank:]]$' + | NOWARN=1 compat_xargs grep -H -n '[[:blank:]]$' rc=$? if [[ ${rc} -eq 0 ]]; then return 1 @@ -121,16 +144,18 @@ function check_trailing_whitespace() { function generate_docs() { echo "Generating markdown docs with terraform-docs" - local path tmpfile - while read -r path; do - if [[ -e "${path}/README.md" ]]; then - # shellcheck disable=SC2119 - tmpfile="$(maketemp)" - echo "terraform-docs markdown ${path}" - terraform-docs markdown "${path}" > "${tmpfile}" - helpers/combine_docfiles.py "${path}"/README.md "${tmpfile}" + local pth helper_dir rval + helper_dir="$(pwd)/helpers" + while read -r pth; do + if [[ -e "${pth}/README.md" ]]; then + (cd "${pth}" || return 3; "${helper_dir}"/terraform_docs .;) + rval="$?" + if [[ "${rval}" -gt 0 ]]; then + echo "Error: terraform_docs in ${pth} exit code: ${rval}" >&2 + return "${rval}" + fi else - echo "Skipping ${path} because README.md does not exist." + echo "Skipping ${pth} because README.md does not exist." fi done < <(find_files . -name '*.tf' -print0 \ | compat_xargs -0 -n1 dirname \ diff --git a/test/verify_boilerplate.py b/test/verify_boilerplate.py index 4c8bfa9..7237631 100644 --- a/test/verify_boilerplate.py +++ b/test/verify_boilerplate.py @@ -57,7 +57,7 @@ def get_args(): return parser.parse_args() -def get_refs(ARGS): +def get_refs(args): """Converts the directory of boilerplate files into a map keyed by file extension. @@ -73,7 +73,7 @@ def get_refs(ARGS): # Find and iterate over the absolute path for each boilerplate template for path in glob.glob(os.path.join( - ARGS.boilerplate_dir, + args.boilerplate_dir, "boilerplate.*.txt")): extension = os.path.basename(path).split(".")[1] ref_file = open(path, 'r') @@ -181,11 +181,11 @@ def normalize_files(files): newfiles.append(pathname) for idx, pathname in enumerate(newfiles): if not os.path.isabs(pathname): - newfiles[idx] = os.path.join(ARGS.rootdir, pathname) + newfiles[idx] = os.path.join(args.rootdir, pathname) return newfiles -def get_files(extensions, ARGS): +def get_files(extensions, args): """Generates a list of paths whose boilerplate should be verified. If a list of file names has been provided on the command line, it will be @@ -204,10 +204,10 @@ def get_files(extensions, ARGS): A list of absolute file paths """ files = [] - if ARGS.filenames: - files = ARGS.filenames + if args.filenames: + files = args.filenames else: - for root, dirs, walkfiles in os.walk(ARGS.rootdir): + for root, dirs, walkfiles in os.walk(args.rootdir): # don't visit certain dirs. This is just a performance improvement # as we would prune these later in normalize_files(). But doing it # cuts down the amount of filesystem walking we do and cuts down @@ -279,5 +279,5 @@ def main(args): if __name__ == "__main__": - ARGS = get_args() - main(ARGS) + args = get_args() + main(args) diff --git a/variables.tf b/variables.tf index 3e96d1c..f7aa9a0 100644 --- a/variables.tf +++ b/variables.tf @@ -16,8 +16,10 @@ variable "parent_id" { description = "The parent of this AccessPolicy in the Cloud Resource Hierarchy. As of now, only organization are accepted as parent." + type = string } variable "policy_name" { description = "The policy's name." + type = string } diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..2970427 --- /dev/null +++ b/versions.tf @@ -0,0 +1,19 @@ +/** + * 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" +}