Skip to content

Commit bd21f7b

Browse files
authored
feat: create service account as part of instance template module (#475)
1 parent 11a9137 commit bd21f7b

File tree

21 files changed

+351
-4
lines changed

21 files changed

+351
-4
lines changed

build/int.cloudbuild.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,25 @@ steps:
258258
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
259259
args: ['/bin/bash', '-c', 'cft test run TestMigStatefulModule --stage destroy --verbose']
260260
timeout: 1800s
261+
- id: it-simple-sa-apply
262+
waitFor:
263+
- destroy-it-simple-local
264+
- destroy-it-additional-disks-local
265+
- destroy-preemptible-and-regular-instance-templates-simple-local
266+
- go-destroy-instance-simple
267+
- destroy-mig-simple-local
268+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
269+
args: ['/bin/bash', '-c', 'cft test run TestInstanceTemplateSimpleSAModule --stage apply --verbose']
270+
- id: it-simple-sa-verify
271+
waitFor:
272+
- it-simple-sa-apply
273+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
274+
args: ['/bin/bash', '-c', 'cft test run TestInstanceTemplateSimpleSAModule --stage verify --verbose']
275+
- id: it-simple-sa-destroy
276+
waitFor:
277+
- it-simple-sa-verify
278+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
279+
args: ['/bin/bash', '-c', 'cft test run TestInstanceTemplateSimpleSAModule --stage destroy --verbose']
261280
tags:
262281
- 'ci'
263282
- 'integration'
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# instance-template-simple
2+
3+
This is a simple, minimal example of how to use the instance_template module.
4+
5+
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
6+
## Inputs
7+
8+
| Name | Description | Type | Default | Required |
9+
|------|-------------|------|---------|:--------:|
10+
| project\_id | The GCP project to use for integration tests | `string` | n/a | yes |
11+
12+
## Outputs
13+
14+
| Name | Description |
15+
|------|-------------|
16+
| name | Name of the instance templates |
17+
| project\_id | The GCP project to use for integration tests |
18+
| self\_link | Self-link to the instance template |
19+
20+
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Copyright 2025 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+
provider "google" {
18+
19+
project = var.project_id
20+
region = "us-central1"
21+
}
22+
23+
resource "google_compute_address" "ip_address" {
24+
name = "external-ip"
25+
}
26+
27+
locals {
28+
access_config = {
29+
nat_ip = google_compute_address.ip_address.address
30+
network_tier = "PREMIUM"
31+
}
32+
}
33+
34+
resource "random_string" "suffix" {
35+
length = 4
36+
special = "false"
37+
upper = "false"
38+
}
39+
40+
resource "google_compute_network" "main" {
41+
project = var.project_id
42+
name = "cft-vm-test-${random_string.suffix.result}"
43+
auto_create_subnetworks = "false"
44+
}
45+
46+
resource "google_compute_subnetwork" "main" {
47+
project = var.project_id
48+
region = "us-central1"
49+
name = "cft-vm-test-${random_string.suffix.result}"
50+
ip_cidr_range = "10.128.0.0/20"
51+
network = google_compute_network.main.self_link
52+
}
53+
54+
module "instance_template" {
55+
source = "terraform-google-modules/vm/google//modules/instance_template"
56+
version = "~> 13.0"
57+
58+
project_id = var.project_id
59+
region = "us-central1"
60+
subnetwork = google_compute_subnetwork.main.self_link
61+
stack_type = "IPV4_ONLY"
62+
name_prefix = "it-simple-sa"
63+
tags = ["foo", "bar", "sa"]
64+
labels = {
65+
environment = "dev"
66+
}
67+
access_config = [local.access_config]
68+
enable_nested_virtualization = false
69+
threads_per_core = null
70+
service_account_project_roles = ["roles/compute.admin"]
71+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright 2025 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 "self_link" {
18+
description = "Self-link to the instance template"
19+
value = module.instance_template.self_link
20+
}
21+
22+
output "name" {
23+
description = "Name of the instance templates"
24+
value = module.instance_template.name
25+
}
26+
27+
output "project_id" {
28+
description = "The GCP project to use for integration tests"
29+
value = var.project_id
30+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Copyright 2025 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+
18+
19+
variable "project_id" {
20+
description = "The GCP project to use for integration tests"
21+
type = string
22+
}

metadata.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ spec:
6464
location: examples/umig/full
6565
- name: healthcheck
6666
location: examples/mig/healthcheck
67+
- name: it_simple_with_sa_creation
68+
location: examples/it_simple_with_sa_creation
6769
- name: mig_stateful
6870
location: examples/mig_stateful
6971
- name: multiple_interfaces
@@ -96,7 +98,9 @@ spec:
9698
- roles/compute.admin
9799
- roles/compute.networkAdmin
98100
- roles/iam.serviceAccountUser
101+
- roles/iam.serviceAccountAdmin
99102
- roles/compute.instanceAdmin
103+
- roles/resourcemanager.projectIamAdmin
100104
services:
101105
- cloudresourcemanager.googleapis.com
102106
- storage-api.googleapis.com

modules/compute_disk_snapshot/metadata.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ spec:
5454
location: examples/umig/full
5555
- name: healthcheck
5656
location: examples/mig/healthcheck
57+
- name: it_simple_with_sa_creation
58+
location: examples/it_simple_with_sa_creation
5759
- name: mig_stateful
5860
location: examples/mig_stateful
5961
- name: multiple_interfaces
@@ -162,7 +164,9 @@ spec:
162164
- roles/compute.admin
163165
- roles/compute.networkAdmin
164166
- roles/iam.serviceAccountUser
167+
- roles/iam.serviceAccountAdmin
165168
- roles/compute.instanceAdmin
169+
- roles/resourcemanager.projectIamAdmin
166170
services:
167171
- cloudresourcemanager.googleapis.com
168172
- storage-api.googleapis.com

modules/compute_instance/metadata.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ spec:
5454
location: examples/umig/full
5555
- name: healthcheck
5656
location: examples/mig/healthcheck
57+
- name: it_simple_with_sa_creation
58+
location: examples/it_simple_with_sa_creation
5759
- name: mig_stateful
5860
location: examples/mig_stateful
5961
- name: multiple_interfaces
@@ -173,7 +175,9 @@ spec:
173175
- roles/compute.admin
174176
- roles/compute.networkAdmin
175177
- roles/iam.serviceAccountUser
178+
- roles/iam.serviceAccountAdmin
176179
- roles/compute.instanceAdmin
180+
- roles/resourcemanager.projectIamAdmin
177181
services:
178182
- cloudresourcemanager.googleapis.com
179183
- storage-api.googleapis.com

modules/instance_template/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ See the [simple](../../examples/instance_template/simple) for a usage example.
2121
| automatic\_restart | (Optional) Specifies whether the instance should be automatically restarted if it is terminated by Compute Engine (not terminated by a user). | `bool` | `true` | no |
2222
| can\_ip\_forward | Enable IP forwarding, for NAT instances for example | `string` | `"false"` | no |
2323
| confidential\_instance\_type | Defines the confidential computing technology the instance uses. If this is set to "SEV\_SNP", var.min\_cpu\_platform will be automatically set to "AMD Milan". See https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_instance#confidential_instance_type. | `string` | `null` | no |
24+
| create\_service\_account | Create a new service account to attach to the instance. This is alternate to providing the service\_account input variable. Please provide the service\_account input if setting this to false. | `bool` | `true` | no |
2425
| description | The template's description | `string` | `""` | no |
2526
| disk\_encryption\_key | The id of the encryption key that is stored in Google Cloud KMS to use to encrypt all the disks on this instance | `string` | `null` | no |
2627
| disk\_labels | Labels to be assigned to boot disk, provided as a map | `map(string)` | `{}` | no |
@@ -47,7 +48,8 @@ See the [simple](../../examples/instance_template/simple) for a usage example.
4748
| project\_id | The GCP project ID | `string` | n/a | yes |
4849
| region | Region where the instance template should be created. | `string` | n/a | yes |
4950
| resource\_policies | A list of self\_links of resource policies to attach to the instance. Modifying this list will cause the instance to recreate. Currently a max of 1 resource policy is supported. | `list(string)` | `[]` | no |
50-
| service\_account | Service account to attach to the instance. See https://www.terraform.io/docs/providers/google/r/compute_instance_template#service_account. | <pre>object({<br> email = string<br> scopes = optional(set(string), ["cloud-platform"])<br> })</pre> | n/a | yes |
51+
| service\_account | Service account to attach to the instance. See https://www.terraform.io/docs/providers/google/r/compute_instance_template#service_account. | <pre>object({<br> email = string<br> scopes = optional(set(string), ["cloud-platform"])<br> })</pre> | `null` | no |
52+
| service\_account\_project\_roles | Roles to grant to the newly created cloud run SA in specified project. Should be used with create\_service\_account set to true and no input for service\_account | `list(string)` | `[]` | no |
5153
| shielded\_instance\_config | Not used unless enable\_shielded\_vm is true. Shielded VM configuration for the instance. | <pre>object({<br> enable_secure_boot = bool<br> enable_vtpm = bool<br> enable_integrity_monitoring = bool<br> })</pre> | <pre>{<br> "enable_integrity_monitoring": true,<br> "enable_secure_boot": true,<br> "enable_vtpm": true<br>}</pre> | no |
5254
| source\_image | Source disk image. If neither source\_image nor source\_image\_family is specified, defaults to the latest public Rocky Linux 9 optimized for GCP image. | `string` | `""` | no |
5355
| source\_image\_family | Source image family. If neither source\_image nor source\_image\_family is specified, defaults to the latest public Rocky Linux 9 optimized for GCP image. | `string` | `"rocky-linux-9-optimized-gcp"` | no |
@@ -69,6 +71,7 @@ See the [simple](../../examples/instance_template/simple) for a usage example.
6971
| name | Name of instance template |
7072
| self\_link | Self-link of instance template |
7173
| self\_link\_unique | Unique self-link of instance template (recommended output to use instead of self\_link) |
74+
| service\_account\_info | Service account id and email |
7275
| tags | Tags that will be associated with instance(s) |
7376

7477
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

modules/instance_template/main.tf

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,43 @@ locals {
6363
# must be true when preemtible or spot is true
6464
var.preemptible || var.spot ? true : false
6565
)
66+
67+
service_account = (
68+
var.service_account != null
69+
? var.service_account
70+
: (
71+
var.create_service_account
72+
? { email : google_service_account.sa[0].email, scopes : ["cloud-platform"] }
73+
: null
74+
)
75+
)
76+
create_service_account = var.create_service_account ? var.service_account == null : false
77+
78+
service_account_prefix = substr("${var.name_prefix}-${var.region}", 0, 27)
79+
service_account_output = local.create_service_account ? {
80+
id = google_service_account.sa[0].account_id,
81+
email = google_service_account.sa[0].email,
82+
member = google_service_account.sa[0].member
83+
} : {}
84+
}
85+
86+
# Service account
87+
resource "google_service_account" "sa" {
88+
provider = google-beta
89+
count = local.create_service_account ? 1 : 0
90+
91+
project = var.project_id
92+
account_id = "${local.service_account_prefix}-sa"
93+
display_name = "Service account for ${var.name_prefix} in ${var.region}"
94+
}
95+
96+
resource "google_project_iam_member" "roles" {
97+
provider = google-beta
98+
for_each = toset(distinct(var.service_account_project_roles))
99+
100+
project = var.project_id
101+
role = each.value
102+
member = "serviceAccount:${local.service_account.email}"
66103
}
67104

68105
####################
@@ -111,7 +148,7 @@ resource "google_compute_instance_template" "tpl" {
111148
}
112149

113150
dynamic "service_account" {
114-
for_each = var.service_account == null ? [] : [var.service_account]
151+
for_each = local.service_account == null ? [] : [local.service_account]
115152
content {
116153
email = lookup(service_account.value, "email", null)
117154
scopes = lookup(service_account.value, "scopes", null)

0 commit comments

Comments
 (0)