Skip to content

Commit 4350c5d

Browse files
authored
fix: Make project service account creation optional (#528)
1 parent e3111d2 commit 4350c5d

File tree

14 files changed

+71
-24
lines changed

14 files changed

+71
-24
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,4 @@ credentials.json
4747
.idea
4848
env/
4949
test/fixtures/shared/terraform.tfvars
50+
.envrc

CONTRIBUTING.md

+25-10
Original file line numberDiff line numberDiff line change
@@ -52,26 +52,40 @@ The general strategy for these tests is to verify the behaviour of the
5252
submodules, and example modules are all functionally correct.
5353

5454
### Test Environment
55-
The easiest way to test the module is in an isolated test project. The setup for such a project is defined in [test/setup](./test/setup/) directory.
5655

57-
To use this setup, you need a service account with Project Creator access on a folder. Export the Service Account credentials to your environment like so:
56+
The easiest way to test the module is in an isolated test project and folder.
57+
The setup for such a project and folder is defined in [test/setup](./test/setup/) directory.
58+
This setup will create a dedicated folder, a project within the folder to hold a service
59+
account that will be used to run the integration tests. It will assign all needed roles
60+
to the service account and will also create a access context manager policy needed for test execution.
5861

59-
```
62+
To use and execute this setup, you need a service account with the following roles:
63+
64+
- Project Creator access on the folder (if you want to delete the setup later ProjectDeleter is also needed).
65+
- Folder Admin on the folder.
66+
- Access Context Manager Editor or Admin on the organization.
67+
- Billing Account Administrator on the billing account or on the organization.
68+
- Organization Administrator on the organization in order to grant the created service account permissions on organization level.
69+
70+
Export the Service Account credentials to your environment like so:
71+
72+
```bash
6073
export SERVICE_ACCOUNT_JSON=$(< credentials.json)
6174
```
6275

6376
You will also need to set a few environment variables:
64-
```
77+
78+
```bash
6579
export TF_VAR_org_id="your_org_id"
6680
export TF_VAR_folder_id="your_folder_id"
6781
export TF_VAR_billing_account="your_billing_account_id"
6882
export TF_VAR_gsuite_admin_email="your_gsuite_admin_email"
6983
export TF_VAR_gsuite_domain="your_gsuite_domain"
7084
```
7185

72-
With these settings in place, you can prepare a test project using Docker:
86+
With these settings in place, you can prepare the test setup using Docker:
7387

74-
```
88+
```bash
7589
make docker_test_prepare
7690
```
7791

@@ -109,7 +123,7 @@ Run `make docker_test_lint`.
109123
New versions can be released by pushing tags to this repository's origin on
110124
GitHub. There is a Make target to facilitate the process:
111125

112-
```
126+
```bash
113127
make release-new-version
114128
```
115129

@@ -140,14 +154,14 @@ Two test-kitchen instances are defined:
140154
- `full-local` - Test coverage for all project-factory features.
141155
- `full-minimal` - Test coverage for a minimal set of project-factory features.
142156

143-
#### Setup
157+
### Setup
144158

145159
1. Configure the [test fixtures](#test-configuration).
146160
2. Download a Service Account key with the necessary [permissions](#permissions)
147161
and put it in the module's root directory with the name `credentials.json`.
148162
3. Add appropriate variables to your environment
149163

150-
```
164+
```bash
151165
export BILLING_ACCOUNT_ID="YOUR_BILLUNG_ACCOUNT"
152166
export DOMAIN="YOUR_DOMAIN"
153167
export FOLDER_ID="YOUR_FOLDER_ID"
@@ -160,7 +174,8 @@ Two test-kitchen instances are defined:
160174
```
161175

162176
4. Run the testing container in interactive mode.
163-
```
177+
178+
```bash
164179
make docker_run
165180
```
166181

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ determining that location is as follows:
121121
| budget\_alert\_spent\_percents | A list of percentages of the budget to alert on when threshold is exceeded | `list(number)` | <pre>[<br> 0.5,<br> 0.7,<br> 1<br>]</pre> | no |
122122
| budget\_amount | The amount to use for a budget alert | `number` | `null` | no |
123123
| budget\_monitoring\_notification\_channels | A list of monitoring notification channels in the form `[projects/{project_id}/notificationChannels/{channel_id}]`. A maximum of 5 channels are allowed. | `list(string)` | `[]` | no |
124+
| create\_project\_sa | Whether the default service account for the project shall be created | `bool` | `true` | no |
124125
| credentials\_path | Path to a service account credentials file with rights to run the Project Factory. If this file is absent Terraform will fall back to Application Default Credentials. | `string` | `""` | no |
125126
| default\_service\_account | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | `string` | `"disable"` | no |
126127
| disable\_dependent\_services | Whether services that are enabled and which depend on this service should also be disabled when this service is destroyed. | `bool` | `true` | no |

main.tf

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module "project-factory" {
4141
enable_shared_vpc_host_project = var.enable_shared_vpc_host_project
4242
billing_account = var.billing_account
4343
folder_id = var.folder_id
44+
create_project_sa = var.create_project_sa
4445
sa_role = var.sa_role
4546
activate_apis = var.activate_apis
4647
activate_api_identities = var.activate_api_identities

modules/core_project_factory/main.tf

+10-9
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ locals {
3434
local.base_project_id,
3535
random_id.random_project_id_suffix.hex,
3636
) : local.base_project_id
37-
s_account_fmt = format(
37+
s_account_fmt = var.create_project_sa ? format(
3838
"serviceAccount:%s",
39-
google_service_account.default_service_account.email,
40-
)
39+
google_service_account.default_service_account[0].email,
40+
) : ""
4141
api_s_account = format(
4242
"%s@cloudservices.gserviceaccount.com",
4343
google_project.main.number,
@@ -56,7 +56,7 @@ locals {
5656
)
5757

5858
# Workaround for https://github.com/hashicorp/terraform/issues/10857
59-
shared_vpc_users_length = 3
59+
shared_vpc_users_length = var.create_project_sa ? 3 : 2
6060
}
6161

6262
/*******************************************
@@ -129,6 +129,7 @@ resource "google_project_default_service_accounts" "default_service_accounts" {
129129
Default Service Account configuration
130130
*****************************************/
131131
resource "google_service_account" "default_service_account" {
132+
count = var.create_project_sa ? 1 : 0
132133
account_id = "project-service-account"
133134
display_name = "${var.name} Project Service Account"
134135
project = google_project.main.project_id
@@ -138,7 +139,7 @@ resource "google_service_account" "default_service_account" {
138139
Policy to operate instances in shared subnetwork
139140
*************************************************/
140141
resource "google_project_iam_member" "default_service_account_membership" {
141-
count = var.sa_role != "" ? 1 : 0
142+
count = var.sa_role != "" && var.create_project_sa ? 1 : 0
142143
project = google_project.main.project_id
143144
role = var.sa_role
144145

@@ -160,12 +161,12 @@ resource "google_project_iam_member" "gsuite_group_role" {
160161
Granting serviceAccountUser to group
161162
*****************************************/
162163
resource "google_service_account_iam_member" "service_account_grant_to_group" {
163-
count = var.manage_group ? 1 : 0
164+
count = var.manage_group && var.create_project_sa ? 1 : 0
164165

165166
member = local.group_id
166167
role = "roles/iam.serviceAccountUser"
167168

168-
service_account_id = "projects/${google_project.main.project_id}/serviceAccounts/${google_service_account.default_service_account.email}"
169+
service_account_id = "projects/${google_project.main.project_id}/serviceAccounts/${google_service_account.default_service_account[0].email}"
169170
}
170171

171172
/******************************************************************************************************************
@@ -188,7 +189,7 @@ resource "google_project_iam_member" "controlling_group_vpc_membership" {
188189
*************************************************************************************/
189190
resource "google_compute_subnetwork_iam_member" "service_account_role_to_vpc_subnets" {
190191
provider = google-beta
191-
count = var.enable_shared_vpc_service_project && length(var.shared_vpc_subnets) > 0 ? length(var.shared_vpc_subnets) : 0
192+
count = var.enable_shared_vpc_service_project && length(var.shared_vpc_subnets) > 0 && var.create_project_sa ? length(var.shared_vpc_subnets) : 0
192193

193194
subnetwork = element(
194195
split("/", var.shared_vpc_subnets[count.index]),
@@ -301,7 +302,7 @@ resource "google_storage_bucket_iam_member" "group_storage_admin_on_project_buck
301302
Project's bucket storage.admin granting to default compute service account
302303
***********************************************/
303304
resource "google_storage_bucket_iam_member" "s_account_storage_admin_on_project_bucket" {
304-
count = local.create_bucket ? 1 : 0
305+
count = local.create_bucket && var.create_project_sa ? 1 : 0
305306

306307
bucket = google_storage_bucket.project_bucket[0].name
307308
role = "roles/storage.admin"

modules/core_project_factory/outputs.tf

+5-5
Original file line numberDiff line numberDiff line change
@@ -37,27 +37,27 @@ output "project_number" {
3737
}
3838

3939
output "service_account_id" {
40-
value = google_service_account.default_service_account.account_id
40+
value = var.create_project_sa ? google_service_account.default_service_account[0].account_id : ""
4141
description = "The id of the default service account"
4242
}
4343

4444
output "service_account_display_name" {
45-
value = google_service_account.default_service_account.display_name
45+
value = var.create_project_sa ? google_service_account.default_service_account[0].display_name : ""
4646
description = "The display name of the default service account"
4747
}
4848

4949
output "service_account_email" {
50-
value = google_service_account.default_service_account.email
50+
value = var.create_project_sa ? google_service_account.default_service_account[0].email : ""
5151
description = "The email of the default service account"
5252
}
5353

5454
output "service_account_name" {
55-
value = google_service_account.default_service_account.name
55+
value = var.create_project_sa ? google_service_account.default_service_account[0].name : ""
5656
description = "The fully-qualified name of the default service account"
5757
}
5858

5959
output "service_account_unique_id" {
60-
value = google_service_account.default_service_account.unique_id
60+
value = var.create_project_sa ? google_service_account.default_service_account[0].unique_id : ""
6161
description = "The unique id of the default service account"
6262
}
6363

modules/core_project_factory/variables.tf

+6
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ variable "folder_id" {
7777
default = ""
7878
}
7979

80+
variable "create_project_sa" {
81+
description = "Whether the default service account for the project shall be created"
82+
type = bool
83+
default = true
84+
}
85+
8086
variable "sa_role" {
8187
description = "A role to give the default Service Account for the project (defaults to none)"
8288
type = string

modules/gsuite_enabled/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ The roles granted are specifically:
7171
| budget\_amount | The amount to use for a budget alert | `number` | `null` | no |
7272
| budget\_monitoring\_notification\_channels | A list of monitoring notification channels in the form `[projects/{project_id}/notificationChannels/{channel_id}]`. A maximum of 5 channels are allowed. | `list(string)` | `[]` | no |
7373
| create\_group | Whether to create the group or not | `bool` | `false` | no |
74+
| create\_project\_sa | Whether the default service account for the project shall be created | `bool` | `true` | no |
7475
| credentials\_path | Path to a service account credentials file with rights to run the Project Factory. If this file is absent Terraform will fall back to Application Default Credentials. | `string` | `""` | no |
7576
| default\_service\_account | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | `string` | `"disable"` | no |
7677
| disable\_dependent\_services | Whether services that are enabled and which depend on this service should also be disabled when this service is destroyed. | `string` | `"true"` | no |

modules/gsuite_enabled/main.tf

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ module "project-factory" {
8383
enable_shared_vpc_host_project = var.enable_shared_vpc_host_project
8484
billing_account = var.billing_account
8585
folder_id = var.folder_id
86+
create_project_sa = var.create_project_sa
8687
sa_role = var.sa_role
8788
activate_apis = var.activate_apis
8889
usage_bucket_name = var.usage_bucket_name

modules/gsuite_enabled/variables.tf

+6
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ variable "sa_group" {
7878
default = ""
7979
}
8080

81+
variable "create_project_sa" {
82+
description = "Whether the default service account for the project shall be created"
83+
type = bool
84+
default = true
85+
}
86+
8187
variable "sa_role" {
8288
description = "A role to give the default Service Account for the project (defaults to none)"
8389
default = ""

modules/svpc_service_project/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ module "service-project" {
4343
| budget\_alert\_spent\_percents | A list of percentages of the budget to alert on when threshold is exceeded | `list(number)` | <pre>[<br> 0.5,<br> 0.7,<br> 1<br>]</pre> | no |
4444
| budget\_amount | The amount to use for a budget alert | `number` | `null` | no |
4545
| budget\_monitoring\_notification\_channels | A list of monitoring notification channels in the form `[projects/{project_id}/notificationChannels/{channel_id}]`. A maximum of 5 channels are allowed. | `list(string)` | `[]` | no |
46+
| create\_project\_sa | Whether the default service account for the project shall be created | `bool` | `true` | no |
4647
| credentials\_path | Path to a service account credentials file with rights to run the Project Factory. If this file is absent Terraform will fall back to Application Default Credentials. | `string` | `""` | no |
4748
| default\_service\_account | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | `string` | `"disable"` | no |
4849
| disable\_dependent\_services | Whether services that are enabled and which depend on this service should also be disabled when this service is destroyed. | `bool` | `true` | no |

modules/svpc_service_project/main.tf

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ module "project-factory" {
4040
enable_shared_vpc_service_project = true
4141
billing_account = var.billing_account
4242
folder_id = var.folder_id
43+
create_project_sa = var.create_project_sa
4344
sa_role = var.sa_role
4445
activate_apis = var.activate_apis
4546
activate_api_identities = var.activate_api_identities

modules/svpc_service_project/variables.tf

+6
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ variable "group_role" {
7171
default = "roles/editor"
7272
}
7373

74+
variable "create_project_sa" {
75+
description = "Whether the default service account for the project shall be created"
76+
type = bool
77+
default = true
78+
}
79+
7480
variable "sa_role" {
7581
description = "A role to give the default Service Account for the project (defaults to none)"
7682
default = ""

variables.tf

+6
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ variable "group_role" {
7777
default = "roles/editor"
7878
}
7979

80+
variable "create_project_sa" {
81+
description = "Whether the default service account for the project shall be created"
82+
type = bool
83+
default = true
84+
}
85+
8086
variable "sa_role" {
8187
description = "A role to give the default Service Account for the project (defaults to none)"
8288
type = string

0 commit comments

Comments
 (0)