Skip to content

Commit 9110314

Browse files
authored
fix: add resource_keys variable to handle dynamic resources (#81)
* fix: add resource_keys variable to handle dynamic resources * chore: add dynamic example to showcase resource_keys * fix: add resource_keys to bridge submodule * chore: add upgrade docs * fmt * fmt * chore: regenerate docs * fix trailing whitespace * fmt * Explicitly specify resources * increase version * docs
1 parent 09cb2dc commit 9110314

File tree

13 files changed

+330
-4
lines changed

13 files changed

+330
-4
lines changed

docs/upgrading_to_v4.0.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Upgrading to v4.x
2+
3+
The v4.x release is a backwards-incompatible release.
4+
5+
The `resources` inside perimeters have been split into their [own Terraform resource](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/access_context_manager_service_perimeter_resource).
6+
This allows you to add resources (projects) to the perimeter from *outside* the module.
7+
8+
However, this change has a few implications:
9+
1. Resources added to the perimeter out-of-band will no longer be removed by Terraform.
10+
You will need to develop an alternative system for dealing with these.
11+
2. The location of resources has moved in the state file.
12+
3. Because resources are now created using `for_each`, if the underlying project is created in the **same**
13+
Terraform configuration as the perimeter, you will need to provide a `resource_keys` variable.
14+
15+
## Dynamic resources
16+
If resources are created inside the same configuration as the perimeter, you will received an error that their value cannot be determined until apply:
17+
```
18+
│ Error: Invalid for_each argument
19+
20+
│ on ../../modules/regular_service_perimeter/main.tf line 195, in resource "google_access_context_manager_service_perimeter_resource" "service_perimeter_resource":
21+
│ 195: for_each = local.resources
22+
│ ├────────────────
23+
│ │ local.resources will be known only after apply
24+
```
25+
26+
To work around this, you need to provide a `resource_keys` variable input with keys for each resource. These keys are only used by Terraform and can be any alphanumeric string.
27+
28+
```diff
29+
module "regular_service_perimeter_2" {
30+
source = "terraform-google-modules/vpc-service-controls/google//modules/regular_service_perimeter"
31+
- version = "~> 3.0"
32+
+ version = "~> 4.0"
33+
34+
...
35+
36+
resources = [module.project_two.project_number, module.project_three.project_number]
37+
+ resource_keys = ["two", "three"]
38+
}
39+
40+
module "bridge" {
41+
source = "terraform-google-modules/vpc-service-controls/google//modules/bridge_service_perimeter"
42+
- version = "~> 3.0"
43+
+ version = "~> 4.0"
44+
45+
...
46+
47+
resources = concat(
48+
module.regular_service_perimeter_1.shared_resources["all"],
49+
module.regular_service_perimeter_2.shared_resources["all"],
50+
)
51+
+ resource_keys = ["one", "two", "three"]
52+
}
53+
```
54+
55+
## State migration
56+
57+
If you run `terraform plan` on an upgraded module, you will notice that Terraform wants to add `service_perimeter` resources.
58+
59+
```
60+
Terraform will perform the following actions:
61+
62+
# module.bridge.google_access_context_manager_service_perimeter_resource.service_perimeter_resource["projects/34502780858"] will be created
63+
+ resource "google_access_context_manager_service_perimeter_resource" "service_perimeter_resource" {
64+
+ id = (known after apply)
65+
+ perimeter_name = "accessPolicies/209696272439/servicePerimeters/bridge_perimeter_1"
66+
+ resource = "projects/34502780858"
67+
}
68+
69+
# module.bridge.google_access_context_manager_service_perimeter_resource.service_perimeter_resource["projects/843696391937"] will be created
70+
+ resource "google_access_context_manager_service_perimeter_resource" "service_perimeter_resource" {
71+
+ id = (known after apply)
72+
+ perimeter_name = "accessPolicies/209696272439/servicePerimeters/bridge_perimeter_1"
73+
+ resource = "projects/843696391937"
74+
}
75+
76+
# module.regular_service_perimeter_1.google_access_context_manager_service_perimeter_resource.service_perimeter_resource["34502780858"] will be created
77+
+ resource "google_access_context_manager_service_perimeter_resource" "service_perimeter_resource" {
78+
+ id = (known after apply)
79+
+ perimeter_name = "accessPolicies/209696272439/servicePerimeters/regular_perimeter_1"
80+
+ resource = "projects/34502780858"
81+
}
82+
83+
Plan: 3 to add, 0 to change, 0 to destroy.
84+
```
85+
86+
For each resource, you will need to import it into the Terraform config:
87+
88+
```
89+
terraform import 'module.bridge.google_access_context_manager_service_perimeter_resource.service_perimeter_resource["one"]' 'accessPolicies/209696272439/servicePerimeters/bridge_perimeter_1/projects/34502780858'
90+
terraform import 'module.bridge.google_access_context_manager_service_perimeter_resource.service_perimeter_resource["two"]' 'accessPolicies/209696272439/servicePerimeters/bridge_perimeter_1/projects/843696391937'
91+
terraform import 'module.regular_service_perimeter_1.google_access_context_manager_service_perimeter_resource.service_perimeter_resource["34502780858"]' 'accessPolicies/209696272439/servicePerimeters/regular_perimeter_1/projects/34502780858'
92+
```
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Simple Example Dynamic
2+
3+
This example illustrates how to use the module to create two perimeters, with projects inside of them, and a bridge between the two.
4+
5+
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
6+
## Inputs
7+
8+
| Name | Description | Type | Default | Required |
9+
|------|-------------|------|---------|:--------:|
10+
| billing\_account | The billing account to use for creating projects | `string` | n/a | yes |
11+
| 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 |
12+
| policy\_name | The policy's name. | `string` | `"vpc-sc"` | no |
13+
14+
## Outputs
15+
16+
| Name | Description |
17+
|------|-------------|
18+
| policy\_id | The ID of the created policy |
19+
20+
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
21+
22+
To provision this example, run the following from within this directory:
23+
- `terraform init` to get the plugins
24+
- `terraform plan` to see the infrastructure plan
25+
- `terraform apply` to apply the infrastructure build
26+
- `terraform destroy` to destroy the built infrastructure
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
module "access_context_manager_policy" {
18+
source = "../.."
19+
parent_id = var.parent_id
20+
policy_name = var.policy_name
21+
}
22+
23+
module "bridge" {
24+
source = "../../modules/bridge_service_perimeter"
25+
policy = module.access_context_manager_policy.policy_id
26+
perimeter_name = "bridge_perimeter_1"
27+
description = "Some description"
28+
29+
resources = [module.project_one.project_number, module.project_two.project_number, module.project_three.project_number]
30+
resource_keys = ["one", "two", "three"]
31+
32+
depends_on = [
33+
module.regular_service_perimeter_1,
34+
module.regular_service_perimeter_2
35+
]
36+
}
37+
38+
module "regular_service_perimeter_1" {
39+
source = "../../modules/regular_service_perimeter"
40+
policy = module.access_context_manager_policy.policy_id
41+
perimeter_name = "regular_perimeter_1"
42+
description = "Some description"
43+
resources = [module.project_one.project_number]
44+
45+
restricted_services = ["bigquery.googleapis.com", "storage.googleapis.com"]
46+
47+
shared_resources = {
48+
all = [module.project_one.project_number]
49+
}
50+
}
51+
52+
module "regular_service_perimeter_2" {
53+
source = "../../modules/regular_service_perimeter"
54+
policy = module.access_context_manager_policy.policy_id
55+
perimeter_name = "regular_perimeter_2"
56+
description = "Some description"
57+
resources = [module.project_two.project_number, module.project_three.project_number]
58+
resource_keys = ["two", "three"]
59+
60+
restricted_services = ["storage.googleapis.com"]
61+
62+
shared_resources = {
63+
all = [module.project_two.project_number, module.project_three.project_number]
64+
}
65+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
output "policy_id" {
18+
description = "The ID of the created policy"
19+
value = module.access_context_manager_policy.policy_id
20+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
module "project_one" {
18+
source = "terraform-google-modules/project-factory/google"
19+
version = "~> 10.0"
20+
21+
name = "vpcsc-test-one"
22+
random_project_id = true
23+
org_id = var.parent_id
24+
billing_account = var.billing_account
25+
}
26+
27+
module "project_two" {
28+
source = "terraform-google-modules/project-factory/google"
29+
version = "~> 10.0"
30+
31+
name = "vpcsc-test-two"
32+
random_project_id = true
33+
org_id = var.parent_id
34+
billing_account = var.billing_account
35+
}
36+
37+
module "project_three" {
38+
source = "terraform-google-modules/project-factory/google"
39+
version = "~> 10.0"
40+
41+
name = "vpcsc-test-two"
42+
random_project_id = true
43+
org_id = var.parent_id
44+
billing_account = var.billing_account
45+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
variable "parent_id" {
18+
description = "The parent of this AccessPolicy in the Cloud Resource Hierarchy. As of now, only organization are accepted as parent."
19+
type = string
20+
}
21+
22+
variable "policy_name" {
23+
description = "The policy's name."
24+
type = string
25+
default = "vpc-sc"
26+
}
27+
28+
variable "billing_account" {
29+
description = "The billing account to use for creating projects"
30+
type = string
31+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
terraform {
18+
required_version = ">= 0.13"
19+
}

modules/bridge_service_perimeter/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ module "regular_service_perimeter_2" {
6363
| description | Description of the bridge perimeter | `string` | `""` | no |
6464
| perimeter\_name | Name of the perimeter. Should be one unified string. Must only be letters, numbers and underscores | `string` | n/a | yes |
6565
| policy | Name of the parent policy | `string` | n/a | yes |
66+
| resource\_keys | A list of keys to use for the Terraform state. The order should correspond to var.resources and the keys must not be dynamically computed. If `null`, var.resources will be used as keys. | `list(string)` | `null` | no |
6667
| resources | A list of GCP resources that are inside of the service perimeter. Currently only projects are allowed. | `list(string)` | n/a | yes |
6768

6869
## Outputs

modules/bridge_service_perimeter/main.tf

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,16 @@ resource "google_access_context_manager_service_perimeter" "bridge_service_perim
2626
}
2727
}
2828

29+
locals {
30+
resource_keys = var.resource_keys != null ? var.resource_keys : var.resources
31+
resources = {
32+
for rk in local.resource_keys :
33+
rk => var.resources[index(local.resource_keys, rk)]
34+
}
35+
}
2936

3037
resource "google_access_context_manager_service_perimeter_resource" "service_perimeter_resource" {
31-
for_each = toset(formatlist("projects/%s", var.resources))
38+
for_each = local.resources
3239
perimeter_name = google_access_context_manager_service_perimeter.bridge_service_perimeter.name
33-
resource = each.key
40+
resource = "projects/${each.value}"
3441
}

modules/bridge_service_perimeter/variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,9 @@ variable "resources" {
3434
description = "A list of GCP resources that are inside of the service perimeter. Currently only projects are allowed."
3535
type = list(string)
3636
}
37+
38+
variable "resource_keys" {
39+
description = "A list of keys to use for the Terraform state. The order should correspond to var.resources and the keys must not be dynamically computed. If `null`, var.resources will be used as keys."
40+
type = list(string)
41+
default = null
42+
}

0 commit comments

Comments
 (0)