diff --git a/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/onprem.tf b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/onprem.tf
index 3a82a81ef..22943359c 100644
--- a/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/onprem.tf
+++ b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/onprem.tf
@@ -87,7 +87,7 @@ module "instance_template" {
image = "projects/cos-cloud/global/images/family/cos-stable"
}
}
- create_template = true
+ create_template = {}
metadata = {
user-data = module.cos-nginx.cloud_config
}
diff --git a/blueprints/gcve/monitoring/main.tf b/blueprints/gcve/monitoring/main.tf
index a725d2a59..095aadb20 100644
--- a/blueprints/gcve/monitoring/main.tf
+++ b/blueprints/gcve/monitoring/main.tf
@@ -66,7 +66,7 @@ module "gcve-mon-template" {
name = "gcve-mon-template"
zone = var.vm_mon_config.vm_mon_zone
instance_type = var.vm_mon_config.vm_mon_type
- create_template = true
+ create_template = {}
can_ip_forward = false
network_interfaces = [
{
diff --git a/blueprints/networking/glb-and-armor/main.tf b/blueprints/networking/glb-and-armor/main.tf
index d65de5ce5..942f6af6d 100644
--- a/blueprints/networking/glb-and-armor/main.tf
+++ b/blueprints/networking/glb-and-armor/main.tf
@@ -95,7 +95,7 @@ module "instance_template_ew1" {
metadata = {
startup-script-url = "gs://cloud-training/gcpnet/httplb/startup.sh"
}
- create_template = true
+ create_template = {}
tags = [
"http-server"
]
@@ -113,7 +113,7 @@ module "instance_template_ue1" {
metadata = {
startup-script-url = "gs://cloud-training/gcpnet/httplb/startup.sh"
}
- create_template = true
+ create_template = {}
tags = [
"http-server"
]
diff --git a/blueprints/networking/glb-hybrid-neg-internal/nva.tf b/blueprints/networking/glb-hybrid-neg-internal/nva.tf
index 3f61fa563..eef894745 100644
--- a/blueprints/networking/glb-hybrid-neg-internal/nva.tf
+++ b/blueprints/networking/glb-hybrid-neg-internal/nva.tf
@@ -21,7 +21,7 @@ module "nva_instance_templates" {
source = "../../../modules/compute-vm"
project_id = module.project_landing.project_id
can_ip_forward = true
- create_template = true
+ create_template = {}
name = "nva-${each.value}"
service_account = {
auto_create = true
diff --git a/blueprints/networking/glb-hybrid-neg-internal/spoke.tf b/blueprints/networking/glb-hybrid-neg-internal/spoke.tf
index 9e6349f3a..d6937c4ff 100644
--- a/blueprints/networking/glb-hybrid-neg-internal/spoke.tf
+++ b/blueprints/networking/glb-hybrid-neg-internal/spoke.tf
@@ -110,7 +110,7 @@ module "test_vms" {
"https-server",
"ssh"
]
- create_template = var.ilb_create
+ create_template = var.ilb_create ? {} : null
}
module "test_vm_migs" {
diff --git a/blueprints/third-party-solutions/gitlab-runner/runner.tf b/blueprints/third-party-solutions/gitlab-runner/runner.tf
index 69720a9de..84306b2e7 100644
--- a/blueprints/third-party-solutions/gitlab-runner/runner.tf
+++ b/blueprints/third-party-solutions/gitlab-runner/runner.tf
@@ -107,7 +107,7 @@ module "gitlab-runner-template" {
service_account = {
email = module.runner-mig-sa.0.email
}
- create_template = true
+ create_template = {}
}
module "gitlab-runner-mig" {
diff --git a/fast/stages/2-networking-b-nva/nva-regional-vpc.tf b/fast/stages/2-networking-b-nva/nva-regional-vpc.tf
index 95dc28f9c..af0d09786 100644
--- a/fast/stages/2-networking-b-nva/nva-regional-vpc.tf
+++ b/fast/stages/2-networking-b-nva/nva-regional-vpc.tf
@@ -95,7 +95,7 @@ module "nva-regional-template" {
zone = "${each.value}-${local.nva_zones[0]}"
instance_type = "e2-standard-4"
tags = ["nva"]
- create_template = true
+ create_template = {}
can_ip_forward = true
network_interfaces = [
{
diff --git a/fast/stages/2-networking-b-nva/nva-simple.tf b/fast/stages/2-networking-b-nva/nva-simple.tf
index ab794fd2d..8ca9758c1 100644
--- a/fast/stages/2-networking-b-nva/nva-simple.tf
+++ b/fast/stages/2-networking-b-nva/nva-simple.tf
@@ -76,7 +76,7 @@ module "nva-simple-template" {
zone = "${each.value.region}-${each.value.zone}"
instance_type = "e2-micro"
tags = ["nva"]
- create_template = true
+ create_template = {}
can_ip_forward = true
network_interfaces = [
{
diff --git a/modules/compute-mig/README.md b/modules/compute-mig/README.md
index 01aec0163..8ec74e2b6 100644
--- a/modules/compute-mig/README.md
+++ b/modules/compute-mig/README.md
@@ -48,7 +48,7 @@ module "nginx-template" {
image = "projects/cos-cloud/global/images/family/cos-stable"
}
}
- create_template = true
+ create_template = {}
metadata = {
user-data = module.cos-nginx.cloud_config
}
@@ -91,7 +91,7 @@ module "nginx-template" {
image = "projects/cos-cloud/global/images/family/cos-stable"
}
}
- create_template = true
+ create_template = {}
metadata = {
user-data = module.cos-nginx.cloud_config
}
@@ -142,7 +142,7 @@ module "nginx-template" {
image = "projects/cos-cloud/global/images/family/cos-stable"
}
}
- create_template = true
+ create_template = {}
metadata = {
user-data = module.cos-nginx.cloud_config
}
@@ -194,7 +194,7 @@ module "nginx-template" {
image = "projects/cos-cloud/global/images/family/cos-stable"
}
}
- create_template = true
+ create_template = {}
metadata = {
user-data = module.cos-nginx.cloud_config
}
@@ -245,7 +245,7 @@ module "nginx-template" {
image = "projects/cos-cloud/global/images/family/cos-stable"
}
}
- create_template = true
+ create_template = {}
metadata = {
user-data = module.cos-nginx.cloud_config
}
@@ -307,7 +307,7 @@ module "nginx-template" {
size = 10
source = google_compute_disk.test-disk.name
}]
- create_template = true
+ create_template = {}
metadata = {
user-data = module.cos-nginx.cloud_config
}
@@ -358,7 +358,7 @@ module "nginx-template" {
size = 10
source = google_compute_disk.test-disk.name
}]
- create_template = true
+ create_template = {}
metadata = {
user-data = module.cos-nginx.cloud_config
}
diff --git a/modules/compute-vm/README.md b/modules/compute-vm/README.md
index 09b682647..7678f1a45 100644
--- a/modules/compute-vm/README.md
+++ b/modules/compute-vm/README.md
@@ -33,6 +33,8 @@ In both modes, an optional service account can be created and assigned to either
- [Disk encryption with Cloud KMS](#disk-encryption-with-cloud-kms)
- [Advanced machine features](#advanced-machine-features)
- [Instance template](#instance-template)
+ - [Global template](#global-template)
+ - [Regional template](#regional-template)
- [Instance group](#instance-group)
- [Instance Schedule](#instance-schedule)
- [Snapshot Schedules](#snapshot-schedules)
@@ -206,7 +208,7 @@ module "vm-disks-example" {
service_account = {
auto_create = true
}
- create_template = true
+ create_template = {}
}
# tftest modules=1 resources=2
```
@@ -526,7 +528,7 @@ module "template-confidential-example" {
zone = "${var.region}-b"
name = "confidential-template"
confidential_compute = true
- create_template = true
+ create_template = {}
instance_type = "n2d-standard-2"
boot_disk = {
initialize_params = {
@@ -641,7 +643,9 @@ module "simple-vm-example" {
### Instance template
-This example shows how to use the module to manage an instance template that defines an additional attached disk for each instance, and overrides defaults for the boot disk image and service account.
+#### Global template
+
+This example shows how to use the module to manage an instance template that defines an additional attached disk for each instance, and overrides defaults for the boot disk image and service account. Instance templates are created global by default.
```hcl
module "cos-test" {
@@ -664,9 +668,41 @@ module "cos-test" {
service_account = {
email = module.iam-service-account.email
}
- create_template = true
+ create_template = {}
}
-# tftest inventory=template.yaml fixtures=fixtures/iam-service-account.tf e2e
+# tftest inventory=template.yaml fixtures=fixtures/iam-service-account.tf e2e
+```
+
+#### Regional template
+
+A regional template can be created by setting `var.create_template.regional`.
+
+```hcl
+module "cos-test" {
+ source = "./fabric/modules/compute-vm"
+ project_id = var.project_id
+ zone = "${var.region}-b"
+ name = "test"
+ network_interfaces = [{
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ }]
+ boot_disk = {
+ initialize_params = {
+ image = "projects/cos-cloud/global/images/family/cos-stable"
+ }
+ }
+ attached_disks = [
+ { size = 10 }
+ ]
+ service_account = {
+ email = module.iam-service-account.email
+ }
+ create_template = {
+ regional = true
+ }
+}
+# tftest inventory=template-regional.yaml fixtures=fixtures/iam-service-account.tf
```
### Instance group
@@ -905,39 +941,39 @@ module "sole-tenancy" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L270) | Instance name. | string | ✓ | |
-| [network_interfaces](variables.tf#L282) | Network interfaces configuration. Use self links for Shared VPC, set addresses to null if not needed. | list(object({…})) | ✓ | |
-| [project_id](variables.tf#L362) | Project id. | string | ✓ | |
-| [zone](variables.tf#L475) | Compute zone. | string | ✓ | |
+| [name](variables.tf#L273) | Instance name. | string | ✓ | |
+| [network_interfaces](variables.tf#L285) | Network interfaces configuration. Use self links for Shared VPC, set addresses to null if not needed. | list(object({…})) | ✓ | |
+| [project_id](variables.tf#L365) | Project id. | string | ✓ | |
+| [zone](variables.tf#L478) | Compute zone. | string | ✓ | |
| [attached_disk_defaults](variables.tf#L17) | Defaults for attached disks options. | object({…}) | | {…} |
| [attached_disks](variables.tf#L37) | Additional disks, if options is null defaults will be used in its place. Source type is one of 'image' (zonal disks in vms and template), 'snapshot' (vm), 'existing', and null. | list(object({…})) | | [] |
| [boot_disk](variables.tf#L82) | Boot disk properties. | object({…}) | | {…} |
| [can_ip_forward](variables.tf#L116) | Enable IP forwarding. | bool | | false |
| [confidential_compute](variables.tf#L122) | Enable Confidential Compute for these instances. | bool | | false |
-| [create_template](variables.tf#L128) | Create instance template instead of instances. | bool | | false |
-| [description](variables.tf#L134) | Description of a Compute Instance. | string | | "Managed by the compute-vm Terraform module." |
-| [enable_display](variables.tf#L140) | Enable virtual display on the instances. | bool | | false |
-| [encryption](variables.tf#L146) | Encryption options. Only one of kms_key_self_link and disk_encryption_key_raw may be set. If needed, you can specify to encrypt or not the boot disk. | object({…}) | | null |
-| [gpu](variables.tf#L156) | GPU information. Based on https://cloud.google.com/compute/docs/gpus. | object({…}) | | null |
-| [group](variables.tf#L191) | Define this variable to create an instance group for instances. Disabled for template use. | object({…}) | | null |
-| [hostname](variables.tf#L199) | Instance FQDN name. | string | | null |
-| [iam](variables.tf#L205) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
-| [instance_schedule](variables.tf#L211) | Assign or create and assign an instance schedule policy. Either resource policy id or create_config must be specified if not null. Set active to null to dtach a policy from vm before destroying. | object({…}) | | null |
-| [instance_type](variables.tf#L246) | Instance type. | string | | "f1-micro" |
-| [labels](variables.tf#L252) | Instance labels. | map(string) | | {} |
-| [metadata](variables.tf#L258) | Instance metadata. | map(string) | | {} |
-| [min_cpu_platform](variables.tf#L264) | Minimum CPU platform. | string | | null |
-| [network_attached_interfaces](variables.tf#L275) | Network interfaces using network attachments. | list(string) | | [] |
-| [network_tag_bindings](variables.tf#L298) | Resource manager tag bindings in arbitrary key => tag key or value id format. Set on both the instance only for networking purposes, and modifiable without impacting the main resource lifecycle. | map(string) | | {} |
-| [options](variables.tf#L305) | Instance options. | object({…}) | | {…} |
-| [project_number](variables.tf#L367) | Project number. Used in tag bindings to avoid a permadiff. | string | | null |
-| [scratch_disks](variables.tf#L373) | Scratch disks configuration. | object({…}) | | {…} |
-| [service_account](variables.tf#L385) | Service account email and scopes. If email is null, the default Compute service account will be used unless auto_create is true, in which case a service account will be created. Set the variable to null to avoid attaching a service account. | object({…}) | | {} |
-| [shielded_config](variables.tf#L395) | Shielded VM configuration of the instances. | object({…}) | | null |
-| [snapshot_schedules](variables.tf#L405) | Snapshot schedule resource policies that can be attached to disks. | map(object({…})) | | {} |
-| [tag_bindings](variables.tf#L448) | Resource manager tag bindings in arbitrary key => tag key or value id format. Set on both the instance and zonal disks, and modifiable without impacting the main resource lifecycle. | map(string) | | {} |
-| [tag_bindings_immutable](variables.tf#L455) | Immutable resource manager tag bindings, in tagKeys/id => tagValues/id format. These are set on the instance or instance template at creation time, and trigger recreation if changed. | map(string) | | null |
-| [tags](variables.tf#L469) | Instance network tags for firewall rule targets. | list(string) | | [] |
+| [create_template](variables.tf#L128) | Create instance template instead of instances. Defaults to a global template. | object({…}) | | null |
+| [description](variables.tf#L137) | Description of a Compute Instance. | string | | "Managed by the compute-vm Terraform module." |
+| [enable_display](variables.tf#L143) | Enable virtual display on the instances. | bool | | false |
+| [encryption](variables.tf#L149) | Encryption options. Only one of kms_key_self_link and disk_encryption_key_raw may be set. If needed, you can specify to encrypt or not the boot disk. | object({…}) | | null |
+| [gpu](variables.tf#L159) | GPU information. Based on https://cloud.google.com/compute/docs/gpus. | object({…}) | | null |
+| [group](variables.tf#L194) | Define this variable to create an instance group for instances. Disabled for template use. | object({…}) | | null |
+| [hostname](variables.tf#L202) | Instance FQDN name. | string | | null |
+| [iam](variables.tf#L208) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
+| [instance_schedule](variables.tf#L214) | Assign or create and assign an instance schedule policy. Either resource policy id or create_config must be specified if not null. Set active to null to dtach a policy from vm before destroying. | object({…}) | | null |
+| [instance_type](variables.tf#L249) | Instance type. | string | | "f1-micro" |
+| [labels](variables.tf#L255) | Instance labels. | map(string) | | {} |
+| [metadata](variables.tf#L261) | Instance metadata. | map(string) | | {} |
+| [min_cpu_platform](variables.tf#L267) | Minimum CPU platform. | string | | null |
+| [network_attached_interfaces](variables.tf#L278) | Network interfaces using network attachments. | list(string) | | [] |
+| [network_tag_bindings](variables.tf#L301) | Resource manager tag bindings in arbitrary key => tag key or value id format. Set on both the instance only for networking purposes, and modifiable without impacting the main resource lifecycle. | map(string) | | {} |
+| [options](variables.tf#L308) | Instance options. | object({…}) | | {…} |
+| [project_number](variables.tf#L370) | Project number. Used in tag bindings to avoid a permadiff. | string | | null |
+| [scratch_disks](variables.tf#L376) | Scratch disks configuration. | object({…}) | | {…} |
+| [service_account](variables.tf#L388) | Service account email and scopes. If email is null, the default Compute service account will be used unless auto_create is true, in which case a service account will be created. Set the variable to null to avoid attaching a service account. | object({…}) | | {} |
+| [shielded_config](variables.tf#L398) | Shielded VM configuration of the instances. | object({…}) | | null |
+| [snapshot_schedules](variables.tf#L408) | Snapshot schedule resource policies that can be attached to disks. | map(object({…})) | | {} |
+| [tag_bindings](variables.tf#L451) | Resource manager tag bindings in arbitrary key => tag key or value id format. Set on both the instance and zonal disks, and modifiable without impacting the main resource lifecycle. | map(string) | | {} |
+| [tag_bindings_immutable](variables.tf#L458) | Immutable resource manager tag bindings, in tagKeys/id => tagValues/id format. These are set on the instance or instance template at creation time, and trigger recreation if changed. | map(string) | | null |
+| [tags](variables.tf#L472) | Instance network tags for firewall rule targets. | list(string) | | [] |
## Outputs
diff --git a/modules/compute-vm/main.tf b/modules/compute-vm/main.tf
index ecfd68be6..e05d7e49e 100644
--- a/modules/compute-vm/main.tf
+++ b/modules/compute-vm/main.tf
@@ -66,7 +66,7 @@ locals {
}
resource "google_compute_disk" "boot" {
- count = !var.create_template && var.boot_disk.use_independent_disk ? 1 : 0
+ count = !local.template_create && var.boot_disk.use_independent_disk ? 1 : 0
project = var.project_id
zone = var.zone
# by default, GCP creates boot disks with the same name as instance, the deviation here is kept for backwards
@@ -89,7 +89,7 @@ resource "google_compute_disk" "boot" {
}
resource "google_compute_disk" "disks" {
- for_each = var.create_template ? {} : {
+ for_each = local.template_create ? {} : {
for k, v in local.attached_disks_zonal :
k => v if v.source_type != "attach"
}
@@ -115,7 +115,7 @@ resource "google_compute_disk" "disks" {
resource "google_compute_region_disk" "disks" {
provider = google-beta
- for_each = var.create_template ? {} : {
+ for_each = local.template_create ? {} : {
for k, v in local.attached_disks_regional :
k => v if v.source_type != "attach"
}
@@ -143,7 +143,7 @@ resource "google_compute_region_disk" "disks" {
resource "google_compute_instance" "default" {
provider = google-beta
- count = var.create_template ? 0 : 1
+ count = local.template_create ? 0 : 1
project = var.project_id
zone = var.zone
name = var.name
@@ -376,199 +376,8 @@ resource "google_compute_instance_iam_binding" "default" {
depends_on = [google_compute_instance.default]
}
-resource "google_compute_instance_template" "default" {
- provider = google-beta
- count = var.create_template ? 1 : 0
- project = var.project_id
- region = local.region
- name_prefix = "${var.name}-"
- description = var.description
- tags = var.tags
- machine_type = var.instance_type
- min_cpu_platform = var.min_cpu_platform
- can_ip_forward = var.can_ip_forward
- metadata = var.metadata
- labels = var.labels
- resource_manager_tags = var.tag_bindings_immutable
-
- dynamic "advanced_machine_features" {
- for_each = local.advanced_mf != null ? [""] : []
- content {
- enable_nested_virtualization = local.advanced_mf.enable_nested_virtualization
- enable_uefi_networking = local.advanced_mf.enable_uefi_networking
- performance_monitoring_unit = local.advanced_mf.performance_monitoring_unit
- threads_per_core = local.advanced_mf.threads_per_core
- turbo_mode = (
- local.advanced_mf.enable_turbo_mode ? "ALL_CORE_MAX" : null
- )
- visible_core_count = local.advanced_mf.visible_core_count
- }
- }
-
- disk {
- auto_delete = var.boot_disk.auto_delete
- boot = true
- disk_size_gb = var.boot_disk.initialize_params.size
- disk_type = var.boot_disk.initialize_params.type
- resource_manager_tags = var.tag_bindings_immutable
- source_image = var.boot_disk.initialize_params.image
-
- dynamic "disk_encryption_key" {
- for_each = var.encryption != null ? [""] : []
- content {
- kms_key_self_link = var.encryption.kms_key_self_link
- }
- }
- }
-
- dynamic "confidential_instance_config" {
- for_each = var.confidential_compute ? [""] : []
- content {
- enable_confidential_compute = true
- }
- }
-
- dynamic "guest_accelerator" {
- for_each = local.gpu ? [var.gpu] : []
- content {
- type = guest_accelerator.value.type
- count = guest_accelerator.value.count
- }
- }
- dynamic "disk" {
- for_each = local.attached_disks
- iterator = config
- content {
- auto_delete = config.value.options.auto_delete
- device_name = coalesce(
- config.value.device_name, config.value.name, config.key
- )
- # Cannot use `source` with any of the fields in
- # [disk_size_gb disk_name disk_type source_image labels]
- disk_type = (
- config.value.source_type != "attach" ? config.value.options.type : null
- )
- disk_size_gb = (
- config.value.source_type != "attach" ? config.value.size : null
- )
- mode = config.value.options.mode
- source_image = (
- config.value.source_type == "image" ? config.value.source : null
- )
- source = (
- config.value.source_type == "attach" ? config.value.source : null
- )
- disk_name = (
- config.value.source_type != "attach" ? config.value.name : null
- )
- resource_manager_tags = var.tag_bindings_immutable
- type = "PERSISTENT"
- dynamic "disk_encryption_key" {
- for_each = var.encryption != null ? [""] : []
- content {
- kms_key_self_link = var.encryption.kms_key_self_link
- }
- }
- }
- }
-
- dynamic "network_interface" {
- for_each = var.network_interfaces
- iterator = config
- content {
- network = config.value.network
- subnetwork = config.value.subnetwork
- network_ip = try(config.value.addresses.internal, null)
- nic_type = config.value.nic_type
- stack_type = config.value.stack_type
- dynamic "access_config" {
- for_each = config.value.nat ? [""] : []
- content {
- nat_ip = try(config.value.addresses.external, null)
- }
- }
- dynamic "alias_ip_range" {
- for_each = config.value.alias_ips
- iterator = config_alias
- content {
- subnetwork_range_name = config_alias.key
- ip_cidr_range = config_alias.value
- }
- }
- }
- }
-
- dynamic "network_interface" {
- for_each = var.network_attached_interfaces
- content {
- network_attachment = network_interface.value
- }
- }
-
- scheduling {
- automatic_restart = !var.options.spot
- instance_termination_action = local.termination_action
- on_host_maintenance = local.on_host_maintenance
- preemptible = var.options.spot
- provisioning_model = var.options.spot ? "SPOT" : "STANDARD"
- dynamic "max_run_duration" {
- for_each = var.options.max_run_duration == null ? [] : [""]
- content {
- nanos = var.options.max_run_duration.nanos
- seconds = var.options.max_run_duration.seconds
- }
- }
-
- dynamic "node_affinities" {
- for_each = var.options.node_affinities
- iterator = affinity
- content {
- key = affinity.key
- operator = affinity.value.in ? "IN" : "NOT_IN"
- values = affinity.value.values
- }
- }
-
- dynamic "graceful_shutdown" {
- for_each = var.options.graceful_shutdown != null ? [""] : []
- content {
- enabled = var.options.graceful_shutdown.enabled
- dynamic "max_duration" {
- for_each = var.options.graceful_shutdown.enabled == true && var.options.graceful_shutdown.max_duration_secs != null ? [""] : []
- content {
- seconds = var.options.graceful_shutdown.max_duration_secs
- nanos = 0
- }
- }
- }
- }
- }
-
- dynamic "service_account" {
- for_each = var.service_account == null ? [] : [""]
- content {
- email = local.service_account.email
- scopes = local.service_account.scopes
- }
- }
-
- dynamic "shielded_instance_config" {
- for_each = var.shielded_config != null ? [var.shielded_config] : []
- iterator = config
- content {
- enable_secure_boot = config.value.enable_secure_boot
- enable_vtpm = config.value.enable_vtpm
- enable_integrity_monitoring = config.value.enable_integrity_monitoring
- }
- }
-
- lifecycle {
- create_before_destroy = true
- }
-}
-
resource "google_compute_instance_group" "unmanaged" {
- count = var.group != null && !var.create_template ? 1 : 0
+ count = var.group != null && !local.template_create ? 1 : 0
project = var.project_id
network = (
length(var.network_interfaces) > 0
diff --git a/modules/compute-vm/resource-policies.tf b/modules/compute-vm/resource-policies.tf
index 40ce2d4ba..f949e3140 100644
--- a/modules/compute-vm/resource-policies.tf
+++ b/modules/compute-vm/resource-policies.tf
@@ -149,7 +149,11 @@ resource "google_compute_disk_resource_policy_attachment" "boot" {
each.value
)
# if independent disk is used for boot disk it will have a different name compared to when created implicitly
- disk = !var.create_template && var.boot_disk.use_independent_disk ? google_compute_disk.boot[0].name : var.name
+ disk = (
+ !local.template_create && var.boot_disk.use_independent_disk
+ ? google_compute_disk.boot[0].name
+ : var.name
+ )
depends_on = [google_compute_instance.default]
}
diff --git a/modules/compute-vm/tags.tf b/modules/compute-vm/tags.tf
index 681f82ee5..e06c9f8c0 100644
--- a/modules/compute-vm/tags.tf
+++ b/modules/compute-vm/tags.tf
@@ -53,7 +53,7 @@ locals {
# use a different resource to avoid overlapping key issues
resource "google_tags_location_tag_binding" "network" {
- for_each = var.create_template ? {} : var.network_tag_bindings
+ for_each = local.template_create ? {} : var.network_tag_bindings
parent = (
"${local.tag_parent_base}/zones/${var.zone}/instances/${google_compute_instance.default[0].instance_id}"
)
@@ -62,7 +62,7 @@ resource "google_tags_location_tag_binding" "network" {
}
resource "google_tags_location_tag_binding" "instance" {
- for_each = var.create_template ? {} : var.tag_bindings
+ for_each = local.template_create ? {} : var.tag_bindings
parent = (
"${local.tag_parent_base}/zones/${var.zone}/instances/${google_compute_instance.default[0].instance_id}"
)
@@ -72,7 +72,7 @@ resource "google_tags_location_tag_binding" "instance" {
resource "google_tags_location_tag_binding" "boot_disks" {
for_each = (
- var.create_template ? {} : { for v in local.boot_disk_tags : v.key => v }
+ local.template_create ? {} : { for v in local.boot_disk_tags : v.key => v }
)
parent = (
"${local.tag_parent_base}/zones/${var.zone}/disks/${each.value.disk_id}"
@@ -83,7 +83,7 @@ resource "google_tags_location_tag_binding" "boot_disks" {
resource "google_tags_location_tag_binding" "disks" {
for_each = (
- var.create_template ? {} : { for v in local.disk_tags : v.key => v }
+ local.template_create ? {} : { for v in local.disk_tags : v.key => v }
)
parent = (
"${local.tag_parent_base}/zones/${var.zone}/disks/${each.value.disk_id}"
@@ -94,7 +94,7 @@ resource "google_tags_location_tag_binding" "disks" {
resource "google_tags_location_tag_binding" "disks_regional" {
for_each = (
- var.create_template ? {} : { for v in local.region_disk_tags : v.key => v }
+ local.template_create ? {} : { for v in local.region_disk_tags : v.key => v }
)
parent = (
"${local.tag_parent_base}/regions/${local.region}/disks/${each.value.disk_id}"
@@ -106,7 +106,7 @@ resource "google_tags_location_tag_binding" "disks_regional" {
# TODO: enable once the template id is available
# resource "google_tags_location_tag_binding" "template" {
-# for_each = var.create_template ? var.tag_bindings : {}
+# for_each = local.template_create ? var.tag_bindings : {}
# parent = (
# "${local.tag_parent_base}/regions/${local.region}/instanceTemplates/${google_compute_instance.default[0].instance_id}"
# )
diff --git a/modules/compute-vm/template.tf b/modules/compute-vm/template.tf
new file mode 100644
index 000000000..5e5bc2fc5
--- /dev/null
+++ b/modules/compute-vm/template.tf
@@ -0,0 +1,402 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+locals {
+ template_create = var.create_template != null
+ template_regional = try(var.create_template.regional, null) == true
+}
+
+resource "google_compute_instance_template" "default" {
+ provider = google-beta
+ count = local.template_create && !local.template_regional ? 1 : 0
+ project = var.project_id
+ region = local.region
+ name_prefix = "${var.name}-"
+ description = var.description
+ tags = var.tags
+ machine_type = var.instance_type
+ min_cpu_platform = var.min_cpu_platform
+ can_ip_forward = var.can_ip_forward
+ metadata = var.metadata
+ labels = var.labels
+ resource_manager_tags = var.tag_bindings_immutable
+
+ dynamic "advanced_machine_features" {
+ for_each = local.advanced_mf != null ? [""] : []
+ content {
+ enable_nested_virtualization = local.advanced_mf.enable_nested_virtualization
+ enable_uefi_networking = local.advanced_mf.enable_uefi_networking
+ performance_monitoring_unit = local.advanced_mf.performance_monitoring_unit
+ threads_per_core = local.advanced_mf.threads_per_core
+ turbo_mode = (
+ local.advanced_mf.enable_turbo_mode ? "ALL_CORE_MAX" : null
+ )
+ visible_core_count = local.advanced_mf.visible_core_count
+ }
+ }
+
+ disk {
+ auto_delete = var.boot_disk.auto_delete
+ boot = true
+ disk_size_gb = var.boot_disk.initialize_params.size
+ disk_type = var.boot_disk.initialize_params.type
+ resource_manager_tags = var.tag_bindings_immutable
+ source_image = var.boot_disk.initialize_params.image
+
+ dynamic "disk_encryption_key" {
+ for_each = var.encryption != null ? [""] : []
+ content {
+ kms_key_self_link = var.encryption.kms_key_self_link
+ }
+ }
+ }
+
+ dynamic "confidential_instance_config" {
+ for_each = var.confidential_compute ? [""] : []
+ content {
+ enable_confidential_compute = true
+ }
+ }
+
+ dynamic "guest_accelerator" {
+ for_each = local.gpu ? [var.gpu] : []
+ content {
+ type = guest_accelerator.value.type
+ count = guest_accelerator.value.count
+ }
+ }
+ dynamic "disk" {
+ for_each = local.attached_disks
+ iterator = config
+ content {
+ auto_delete = config.value.options.auto_delete
+ device_name = coalesce(
+ config.value.device_name, config.value.name, config.key
+ )
+ # Cannot use `source` with any of the fields in
+ # [disk_size_gb disk_name disk_type source_image labels]
+ disk_type = (
+ config.value.source_type != "attach" ? config.value.options.type : null
+ )
+ disk_size_gb = (
+ config.value.source_type != "attach" ? config.value.size : null
+ )
+ mode = config.value.options.mode
+ source_image = (
+ config.value.source_type == "image" ? config.value.source : null
+ )
+ source = (
+ config.value.source_type == "attach" ? config.value.source : null
+ )
+ disk_name = (
+ config.value.source_type != "attach" ? config.value.name : null
+ )
+ resource_manager_tags = var.tag_bindings_immutable
+ type = "PERSISTENT"
+ dynamic "disk_encryption_key" {
+ for_each = var.encryption != null ? [""] : []
+ content {
+ kms_key_self_link = var.encryption.kms_key_self_link
+ }
+ }
+ }
+ }
+
+ dynamic "network_interface" {
+ for_each = var.network_interfaces
+ iterator = config
+ content {
+ network = config.value.network
+ subnetwork = config.value.subnetwork
+ network_ip = try(config.value.addresses.internal, null)
+ nic_type = config.value.nic_type
+ stack_type = config.value.stack_type
+ dynamic "access_config" {
+ for_each = config.value.nat ? [""] : []
+ content {
+ nat_ip = try(config.value.addresses.external, null)
+ }
+ }
+ dynamic "alias_ip_range" {
+ for_each = config.value.alias_ips
+ iterator = config_alias
+ content {
+ subnetwork_range_name = config_alias.key
+ ip_cidr_range = config_alias.value
+ }
+ }
+ }
+ }
+
+ dynamic "network_interface" {
+ for_each = var.network_attached_interfaces
+ content {
+ network_attachment = network_interface.value
+ }
+ }
+
+ scheduling {
+ automatic_restart = !var.options.spot
+ instance_termination_action = local.termination_action
+ on_host_maintenance = local.on_host_maintenance
+ preemptible = var.options.spot
+ provisioning_model = var.options.spot ? "SPOT" : "STANDARD"
+ dynamic "max_run_duration" {
+ for_each = var.options.max_run_duration == null ? [] : [""]
+ content {
+ nanos = var.options.max_run_duration.nanos
+ seconds = var.options.max_run_duration.seconds
+ }
+ }
+
+ dynamic "node_affinities" {
+ for_each = var.options.node_affinities
+ iterator = affinity
+ content {
+ key = affinity.key
+ operator = affinity.value.in ? "IN" : "NOT_IN"
+ values = affinity.value.values
+ }
+ }
+
+ dynamic "graceful_shutdown" {
+ for_each = var.options.graceful_shutdown != null ? [""] : []
+ content {
+ enabled = var.options.graceful_shutdown.enabled
+ dynamic "max_duration" {
+ for_each = var.options.graceful_shutdown.enabled == true && var.options.graceful_shutdown.max_duration_secs != null ? [""] : []
+ content {
+ seconds = var.options.graceful_shutdown.max_duration_secs
+ nanos = 0
+ }
+ }
+ }
+ }
+ }
+
+ dynamic "service_account" {
+ for_each = var.service_account == null ? [] : [""]
+ content {
+ email = local.service_account.email
+ scopes = local.service_account.scopes
+ }
+ }
+
+ dynamic "shielded_instance_config" {
+ for_each = var.shielded_config != null ? [var.shielded_config] : []
+ iterator = config
+ content {
+ enable_secure_boot = config.value.enable_secure_boot
+ enable_vtpm = config.value.enable_vtpm
+ enable_integrity_monitoring = config.value.enable_integrity_monitoring
+ }
+ }
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
+
+resource "google_compute_region_instance_template" "default" {
+ provider = google-beta
+ count = local.template_create && local.template_regional ? 1 : 0
+ project = var.project_id
+ region = local.region
+ name_prefix = "${var.name}-"
+ description = var.description
+ tags = var.tags
+ machine_type = var.instance_type
+ min_cpu_platform = var.min_cpu_platform
+ can_ip_forward = var.can_ip_forward
+ metadata = var.metadata
+ labels = var.labels
+ resource_manager_tags = var.tag_bindings_immutable
+
+ dynamic "advanced_machine_features" {
+ for_each = local.advanced_mf != null ? [""] : []
+ content {
+ enable_nested_virtualization = local.advanced_mf.enable_nested_virtualization
+ enable_uefi_networking = local.advanced_mf.enable_uefi_networking
+ performance_monitoring_unit = local.advanced_mf.performance_monitoring_unit
+ threads_per_core = local.advanced_mf.threads_per_core
+ turbo_mode = (
+ local.advanced_mf.enable_turbo_mode ? "ALL_CORE_MAX" : null
+ )
+ visible_core_count = local.advanced_mf.visible_core_count
+ }
+ }
+
+ disk {
+ auto_delete = var.boot_disk.auto_delete
+ boot = true
+ disk_size_gb = var.boot_disk.initialize_params.size
+ disk_type = var.boot_disk.initialize_params.type
+ resource_manager_tags = var.tag_bindings_immutable
+ source_image = var.boot_disk.initialize_params.image
+
+ dynamic "disk_encryption_key" {
+ for_each = var.encryption != null ? [""] : []
+ content {
+ kms_key_self_link = var.encryption.kms_key_self_link
+ }
+ }
+ }
+
+ dynamic "confidential_instance_config" {
+ for_each = var.confidential_compute ? [""] : []
+ content {
+ enable_confidential_compute = true
+ }
+ }
+
+ dynamic "guest_accelerator" {
+ for_each = local.gpu ? [var.gpu] : []
+ content {
+ type = guest_accelerator.value.type
+ count = guest_accelerator.value.count
+ }
+ }
+ dynamic "disk" {
+ for_each = local.attached_disks
+ iterator = config
+ content {
+ auto_delete = config.value.options.auto_delete
+ device_name = coalesce(
+ config.value.device_name, config.value.name, config.key
+ )
+ # Cannot use `source` with any of the fields in
+ # [disk_size_gb disk_name disk_type source_image labels]
+ disk_type = (
+ config.value.source_type != "attach" ? config.value.options.type : null
+ )
+ disk_size_gb = (
+ config.value.source_type != "attach" ? config.value.size : null
+ )
+ mode = config.value.options.mode
+ source_image = (
+ config.value.source_type == "image" ? config.value.source : null
+ )
+ source = (
+ config.value.source_type == "attach" ? config.value.source : null
+ )
+ disk_name = (
+ config.value.source_type != "attach" ? config.value.name : null
+ )
+ resource_manager_tags = var.tag_bindings_immutable
+ type = "PERSISTENT"
+ dynamic "disk_encryption_key" {
+ for_each = var.encryption != null ? [""] : []
+ content {
+ kms_key_self_link = var.encryption.kms_key_self_link
+ }
+ }
+ }
+ }
+
+ dynamic "network_interface" {
+ for_each = var.network_interfaces
+ iterator = config
+ content {
+ network = config.value.network
+ subnetwork = config.value.subnetwork
+ network_ip = try(config.value.addresses.internal, null)
+ nic_type = config.value.nic_type
+ stack_type = config.value.stack_type
+ dynamic "access_config" {
+ for_each = config.value.nat ? [""] : []
+ content {
+ nat_ip = try(config.value.addresses.external, null)
+ }
+ }
+ dynamic "alias_ip_range" {
+ for_each = config.value.alias_ips
+ iterator = config_alias
+ content {
+ subnetwork_range_name = config_alias.key
+ ip_cidr_range = config_alias.value
+ }
+ }
+ }
+ }
+
+ dynamic "network_interface" {
+ for_each = var.network_attached_interfaces
+ content {
+ network_attachment = network_interface.value
+ }
+ }
+
+ scheduling {
+ automatic_restart = !var.options.spot
+ instance_termination_action = local.termination_action
+ on_host_maintenance = local.on_host_maintenance
+ preemptible = var.options.spot
+ provisioning_model = var.options.spot ? "SPOT" : "STANDARD"
+ dynamic "max_run_duration" {
+ for_each = var.options.max_run_duration == null ? [] : [""]
+ content {
+ nanos = var.options.max_run_duration.nanos
+ seconds = var.options.max_run_duration.seconds
+ }
+ }
+
+ dynamic "node_affinities" {
+ for_each = var.options.node_affinities
+ iterator = affinity
+ content {
+ key = affinity.key
+ operator = affinity.value.in ? "IN" : "NOT_IN"
+ values = affinity.value.values
+ }
+ }
+
+ dynamic "graceful_shutdown" {
+ for_each = var.options.graceful_shutdown != null ? [""] : []
+ content {
+ enabled = var.options.graceful_shutdown.enabled
+ dynamic "max_duration" {
+ for_each = var.options.graceful_shutdown.enabled == true && var.options.graceful_shutdown.max_duration_secs != null ? [""] : []
+ content {
+ seconds = var.options.graceful_shutdown.max_duration_secs
+ nanos = 0
+ }
+ }
+ }
+ }
+ }
+
+ dynamic "service_account" {
+ for_each = var.service_account == null ? [] : [""]
+ content {
+ email = local.service_account.email
+ scopes = local.service_account.scopes
+ }
+ }
+
+ dynamic "shielded_instance_config" {
+ for_each = var.shielded_config != null ? [var.shielded_config] : []
+ iterator = config
+ content {
+ enable_secure_boot = config.value.enable_secure_boot
+ enable_vtpm = config.value.enable_vtpm
+ enable_integrity_monitoring = config.value.enable_integrity_monitoring
+ }
+ }
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
diff --git a/modules/compute-vm/variables.tf b/modules/compute-vm/variables.tf
index 4297a0597..4446e0b31 100644
--- a/modules/compute-vm/variables.tf
+++ b/modules/compute-vm/variables.tf
@@ -126,9 +126,12 @@ variable "confidential_compute" {
}
variable "create_template" {
- description = "Create instance template instead of instances."
- type = bool
- default = false
+ description = "Create instance template instead of instances. Defaults to a global template."
+ type = object({
+ regional = optional(bool, false)
+ })
+ nullable = true
+ default = null
}
variable "description" {
diff --git a/modules/net-lb-app-ext-regional/README.md b/modules/net-lb-app-ext-regional/README.md
index a5e820fdf..f2c199b18 100644
--- a/modules/net-lb-app-ext-regional/README.md
+++ b/modules/net-lb-app-ext-regional/README.md
@@ -316,7 +316,7 @@ module "win-template" {
zone = "${var.region}-a"
name = "win-template"
instance_type = "n2d-standard-2"
- create_template = true
+ create_template = {}
boot_disk = {
initialize_params = {
image = "projects/windows-cloud/global/images/windows-server-2019-dc-v20221214"
@@ -725,7 +725,9 @@ module "ralb-0" {
}
# tftest modules=3 resources=18 fixtures=fixtures/compute-vm-group-bc.tf e2e
```
+
## Deploying changes to load balancer configurations
+
For deploying changes to load balancer configuration please refer to [net-lb-app-ext README.md](../net-lb-app-ext/README.md#deploying-changes-to-load-balancer-configurations)
diff --git a/modules/net-lb-app-ext/README.md b/modules/net-lb-app-ext/README.md
index 8752844cf..d04e5b68c 100644
--- a/modules/net-lb-app-ext/README.md
+++ b/modules/net-lb-app-ext/README.md
@@ -310,7 +310,7 @@ module "win-template" {
zone = "${var.region}-a"
name = "win-template"
instance_type = "n2d-standard-2"
- create_template = true
+ create_template = {}
boot_disk = {
initialize_params = {
image = "projects/windows-cloud/global/images/windows-server-2019-dc-v20221214"
diff --git a/modules/net-lb-int/README.md b/modules/net-lb-int/README.md
index 5b6de2fa3..aa71aff79 100644
--- a/modules/net-lb-int/README.md
+++ b/modules/net-lb-int/README.md
@@ -31,7 +31,7 @@ module "instance_template" {
project_id = var.project_id
zone = "europe-west1-b"
name = "vm-test"
- create_template = true
+ create_template = {}
service_account = {
auto_create = true
}
diff --git a/tests/fixtures/compute-mig.tf b/tests/fixtures/compute-mig.tf
index bf7d6a0a6..1c1279eb6 100644
--- a/tests/fixtures/compute-mig.tf
+++ b/tests/fixtures/compute-mig.tf
@@ -29,7 +29,7 @@ module "_instance-template" {
image = "projects/cos-cloud/global/images/family/cos-stable"
}
}
- create_template = true
+ create_template = {}
}
module "compute-mig" {
diff --git a/tests/modules/compute_vm/examples/template-regional.yaml b/tests/modules/compute_vm/examples/template-regional.yaml
new file mode 100644
index 000000000..5f798c5fd
--- /dev/null
+++ b/tests/modules/compute_vm/examples/template-regional.yaml
@@ -0,0 +1,62 @@
+# Copyright 2023 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.
+
+values:
+ module.cos-test.google_compute_region_instance_template.default[0]:
+ disk:
+ - auto_delete: true
+ boot: true
+ disk_encryption_key: []
+ disk_name: null
+ disk_size_gb: 10
+ disk_type: pd-balanced
+ labels: null
+ resource_policies: null
+ source: null
+ source_image: projects/cos-cloud/global/images/family/cos-stable
+ source_image_encryption_key: []
+ source_snapshot: null
+ source_snapshot_encryption_key: []
+ - auto_delete: true
+ device_name: disk-0
+ disk_encryption_key: []
+ disk_size_gb: 10
+ disk_type: pd-balanced
+ labels: null
+ mode: READ_WRITE
+ resource_policies: null
+ source: null
+ source_image_encryption_key: []
+ source_snapshot: null
+ source_snapshot_encryption_key: []
+ type: PERSISTENT
+ name_prefix: test-
+ network_interface:
+ - access_config: []
+ alias_ip_range: []
+ network: projects/xxx/global/networks/aaa
+ network_ip: null
+ nic_type: null
+ queue_count: null
+ subnetwork: subnet_self_link
+ project: project-id
+ region: europe-west8
+ service_account:
+ - email: fixture-service-account@project-id.iam.gserviceaccount.com
+ scopes:
+ - https://www.googleapis.com/auth/cloud-platform
+ - https://www.googleapis.com/auth/userinfo.email
+
+counts:
+ google_compute_region_instance_template: 1