diff --git a/modules/compute-vm/README.md b/modules/compute-vm/README.md index 0844c5997..b2f627fe5 100644 --- a/modules/compute-vm/README.md +++ b/modules/compute-vm/README.md @@ -937,41 +937,42 @@ module "sole-tenancy" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L266) | Instance name. | string | ✓ | | -| [network_interfaces](variables.tf#L278) | Network interfaces configuration. Use self links for Shared VPC, set addresses to null if not needed. | list(object({…})) | ✓ | | -| [project_id](variables.tf#L363) | Project id. | string | ✓ | | -| [zone](variables.tf#L483) | Compute zone. | string | ✓ | | +| [name](variables.tf#L283) | Instance name. | string | ✓ | | +| [network_interfaces](variables.tf#L295) | Network interfaces configuration. Use self links for Shared VPC, set addresses to null if not needed. | list(object({…})) | ✓ | | +| [project_id](variables.tf#L380) | Project id. | string | ✓ | | +| [zone](variables.tf#L500) | 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. Initialize params are ignored when source is set. | object({…}) | | {…} | | [can_ip_forward](variables.tf#L113) | Enable IP forwarding. | bool | | false | | [confidential_compute](variables.tf#L119) | Enable Confidential Compute for these instances. | bool | | false | -| [create_template](variables.tf#L125) | Create instance template instead of instances. Defaults to a global template. | object({…}) | | null | -| [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#L235) | Instance type. | string | | "f1-micro" | -| [labels](variables.tf#L241) | Instance labels. | map(string) | | {} | -| [metadata](variables.tf#L247) | Instance metadata. | map(string) | | {} | -| [metadata_startup_script](variables.tf#L253) | Instance startup script. Will trigger recreation on change, even after importing. | string | | null | -| [min_cpu_platform](variables.tf#L260) | Minimum CPU platform. | string | | null | -| [network_attached_interfaces](variables.tf#L271) | Network interfaces using network attachments. | list(string) | | [] | -| [network_tag_bindings](variables.tf#L299) | 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#L306) | Instance options. | object({…}) | | {…} | -| [project_number](variables.tf#L368) | Project number. Used in tag bindings to avoid a permadiff. | string | | null | -| [resource_policies](variables.tf#L374) | Resource policies to attach to the instance or template. | list(string) | | null | -| [scratch_disks](variables.tf#L381) | Scratch disks configuration. | object({…}) | | {…} | -| [service_account](variables.tf#L393) | 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#L403) | Shielded VM configuration of the instances. | object({…}) | | null | -| [snapshot_schedules](variables.tf#L413) | Snapshot schedule resource policies that can be attached to disks. | map(object({…})) | | {} | -| [tag_bindings](variables.tf#L456) | 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#L463) | 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#L477) | Instance network tags for firewall rule targets. | list(string) | | [] | +| [context](variables.tf#L125) | Context-specific interpolations. | object({…}) | | {} | +| [create_template](variables.tf#L142) | Create instance template instead of instances. Defaults to a global template. | object({…}) | | null | +| [description](variables.tf#L151) | Description of a Compute Instance. | string | | "Managed by the compute-vm Terraform module." | +| [enable_display](variables.tf#L157) | Enable virtual display on the instances. | bool | | false | +| [encryption](variables.tf#L163) | 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#L173) | GPU information. Based on https://cloud.google.com/compute/docs/gpus. | object({…}) | | null | +| [group](variables.tf#L208) | Define this variable to create an instance group for instances. Disabled for template use. | object({…}) | | null | +| [hostname](variables.tf#L216) | Instance FQDN name. | string | | null | +| [iam](variables.tf#L222) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [instance_schedule](variables.tf#L228) | 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#L252) | Instance type. | string | | "f1-micro" | +| [labels](variables.tf#L258) | Instance labels. | map(string) | | {} | +| [metadata](variables.tf#L264) | Instance metadata. | map(string) | | {} | +| [metadata_startup_script](variables.tf#L270) | Instance startup script. Will trigger recreation on change, even after importing. | string | | null | +| [min_cpu_platform](variables.tf#L277) | Minimum CPU platform. | string | | null | +| [network_attached_interfaces](variables.tf#L288) | Network interfaces using network attachments. | list(string) | | [] | +| [network_tag_bindings](variables.tf#L316) | 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#L323) | Instance options. | object({…}) | | {…} | +| [project_number](variables.tf#L385) | Project number. Used in tag bindings to avoid a permadiff. | string | | null | +| [resource_policies](variables.tf#L391) | Resource policies to attach to the instance or template. | list(string) | | null | +| [scratch_disks](variables.tf#L398) | Scratch disks configuration. | object({…}) | | {…} | +| [service_account](variables.tf#L410) | 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#L420) | Shielded VM configuration of the instances. | object({…}) | | null | +| [snapshot_schedules](variables.tf#L430) | Snapshot schedule resource policies that can be attached to disks. | map(object({…})) | | {} | +| [tag_bindings](variables.tf#L473) | 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#L480) | 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#L494) | 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 2880e47b9..4535cce8f 100644 --- a/modules/compute-vm/main.tf +++ b/modules/compute-vm/main.tf @@ -15,6 +15,7 @@ */ locals { + _region = join("-", slice(split("-", local.zone), 0, 2)) advanced_mf = var.options.advanced_machine_features attached_disks = { for i, disk in var.attached_disks : @@ -30,18 +31,27 @@ locals { for k, v in local.attached_disks : k => v if try(v.options.replica_zone, null) == null } + ctx = { + for k, v in var.context : k => { + for kk, vv in v : "${local.ctx_p}${k}:${kk}" => vv + } + } + ctx_p = "$" + gpu = var.gpu != null on_host_maintenance = ( var.options.spot || var.confidential_compute || local.gpu ? "TERMINATE" : "MIGRATE" ) - region = join("-", slice(split("-", var.zone), 0, 2)) - gpu = var.gpu != null + project_id = lookup(local.ctx.project_ids, var.project_id, var.project_id) + region = lookup(local.ctx.locations, local._region, local._region) service_account = var.service_account == null ? null : { - email = ( - var.service_account.auto_create + email = (var.service_account.auto_create ? google_service_account.service_account[0].email - : var.service_account.email + : try( + local.ctx.iam_principals[var.service_account.email], + var.service_account.email + ) ) scopes = ( var.service_account.scopes != null ? var.service_account.scopes : ( @@ -63,12 +73,13 @@ locals { termination_action = ( var.options.spot || var.options.max_run_duration != null ? coalesce(var.options.termination_action, "STOP") : null ) + zone = lookup(local.ctx.locations, var.zone, var.zone) } resource "google_compute_disk" "boot" { count = !local.template_create && var.boot_disk.use_independent_disk ? 1 : 0 - project = var.project_id - zone = var.zone + project = local.project_id + zone = local.zone # by default, GCP creates boot disks with the same name as instance, the deviation here is kept for backwards # compatibility name = "${var.name}-boot" @@ -82,8 +93,12 @@ resource "google_compute_disk" "boot" { dynamic "disk_encryption_key" { for_each = var.encryption != null ? [""] : [] content { - raw_key = var.encryption.disk_encryption_key_raw - kms_key_self_link = var.encryption.kms_key_self_link + raw_key = var.encryption.disk_encryption_key_raw + kms_key_self_link = lookup( + local.ctx.kms_keys, + var.encryption.kms_key_self_link, + var.encryption.kms_key_self_link + ) } } } @@ -93,8 +108,8 @@ resource "google_compute_disk" "disks" { for k, v in local.attached_disks_zonal : k => v if v.source_type != "attach" } - project = var.project_id - zone = var.zone + project = local.project_id + zone = local.zone name = "${var.name}-${each.key}" type = each.value.options.type size = each.value.size @@ -107,8 +122,12 @@ resource "google_compute_disk" "disks" { dynamic "disk_encryption_key" { for_each = var.encryption != null ? [""] : [] content { - raw_key = var.encryption.disk_encryption_key_raw - kms_key_self_link = var.encryption.kms_key_self_link + raw_key = var.encryption.disk_encryption_key_raw + kms_key_self_link = lookup( + local.ctx.kms_keys, + var.encryption.kms_key_self_link, + var.encryption.kms_key_self_link + ) } } } @@ -119,9 +138,9 @@ resource "google_compute_region_disk" "disks" { for k, v in local.attached_disks_regional : k => v if v.source_type != "attach" } - project = var.project_id + project = local.project_id region = local.region - replica_zones = [var.zone, each.value.options.replica_zone] + replica_zones = [local.zone, each.value.options.replica_zone] name = "${var.name}-${each.key}" type = each.value.options.type size = each.value.size @@ -136,7 +155,12 @@ resource "google_compute_region_disk" "disks" { content { raw_key = var.encryption.disk_encryption_key_raw # TODO: check if self link works here - kms_key_name = var.encryption.kms_key_self_link + kms_key_name = lookup( + local.ctx.kms_keys, + var.encryption.kms_key_self_link, + var.encryption.kms_key_self_link + ) + } } } @@ -144,8 +168,8 @@ resource "google_compute_region_disk" "disks" { resource "google_compute_instance" "default" { provider = google-beta count = local.template_create ? 0 : 1 - project = var.project_id - zone = var.zone + project = local.project_id + zone = local.zone name = var.name hostname = var.hostname description = var.description @@ -228,10 +252,20 @@ resource "google_compute_instance" "default" { : var.boot_disk.source ) disk_encryption_key_raw = ( - var.encryption != null ? var.encryption.disk_encryption_key_raw : null + var.encryption != null ? + try( + local.ctx.kms_keys[var.encryption.disk_encryption_key_raw], + var.encryption.disk_encryption_key_raw + ) + : null ) kms_key_self_link = ( - var.encryption != null ? var.encryption.kms_key_self_link : null + var.encryption != null + ? try( + local.ctx.kms_keys[var.encryption.kms_key_self_link], + var.encryption.kms_key_self_link + ) + : null ) dynamic "initialize_params" { for_each = ( @@ -263,15 +297,27 @@ resource "google_compute_instance" "default" { for_each = var.network_interfaces iterator = config content { - network = config.value.network - subnetwork = config.value.subnetwork - network_ip = try(config.value.addresses.internal, null) + network = lookup( + local.ctx.networks, config.value.network, config.value.network + ) + subnetwork = lookup( + local.ctx.subnets, config.value.subnetwork, config.value.subnetwork + ) + network_ip = try( + local.ctx.addresses[config.value.addresses.internal], + 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 || config.value.network_tier != null ? [""] : [] content { - nat_ip = try(config.value.addresses.external, null) + nat_ip = try( + local.ctx.addresses[config.value.addresses.external], + config.value.addresses.external, + null + ) network_tier = try(config.value.network_tier, null) } } @@ -378,24 +424,26 @@ resource "google_compute_instance" "default" { } resource "google_compute_instance_iam_binding" "default" { - project = var.project_id + project = local.project_id for_each = var.iam - zone = var.zone + zone = local.zone instance_name = var.name - role = each.key - members = each.value - depends_on = [google_compute_instance.default] + role = lookup(local.ctx.custom_roles, each.key, each.key) + members = [ + for m in each.value : lookup(local.ctx.iam_principals, m, m) + ] + depends_on = [google_compute_instance.default] } resource "google_compute_instance_group" "unmanaged" { count = var.group != null && !local.template_create ? 1 : 0 - project = var.project_id + project = local.project_id network = ( length(var.network_interfaces) > 0 ? var.network_interfaces[0].network : "" ) - zone = var.zone + zone = local.zone name = var.name description = var.description instances = [google_compute_instance.default[0].self_link] @@ -411,7 +459,7 @@ resource "google_compute_instance_group" "unmanaged" { resource "google_service_account" "service_account" { count = try(var.service_account.auto_create, null) == true ? 1 : 0 - project = var.project_id + project = local.project_id account_id = "tf-vm-${var.name}" display_name = "Terraform VM ${var.name}." } diff --git a/modules/compute-vm/outputs.tf b/modules/compute-vm/outputs.tf index 79551b84a..7e3b97e88 100644 --- a/modules/compute-vm/outputs.tf +++ b/modules/compute-vm/outputs.tf @@ -57,7 +57,7 @@ output "internal_ips" { output "login_command" { description = "Command to SSH into the machine." - value = "gcloud compute ssh --project ${var.project_id} --zone ${var.zone} ${var.name}" + value = "gcloud compute ssh --project ${local.project_id} --zone ${local.zone} ${var.name}" } output "self_link" { diff --git a/modules/compute-vm/resource-policies.tf b/modules/compute-vm/resource-policies.tf index fbabd667f..e717ecd74 100644 --- a/modules/compute-vm/resource-policies.tf +++ b/modules/compute-vm/resource-policies.tf @@ -46,8 +46,8 @@ locals { resource "google_compute_resource_policy" "schedule" { count = var.instance_schedule != null ? 1 : 0 - project = var.project_id - region = substr(var.zone, 0, length(var.zone) - 2) + project = local.project_id + region = substr(local.zone, 0, length(local.zone) - 2) name = var.name description = coalesce( var.instance_schedule.description, "Schedule policy for ${var.name}." @@ -73,8 +73,8 @@ resource "google_compute_resource_policy" "schedule" { resource "google_compute_resource_policy" "snapshot" { for_each = var.snapshot_schedules - project = var.project_id - region = substr(var.zone, 0, length(var.zone) - 2) + project = local.project_id + region = substr(local.zone, 0, length(local.zone) - 2) name = "${var.name}-${each.key}" description = coalesce( each.value.description, "Schedule policy ${each.key} for ${var.name}." @@ -132,8 +132,8 @@ resource "google_compute_resource_policy" "snapshot" { resource "google_compute_disk_resource_policy_attachment" "boot" { for_each = var.boot_disk.snapshot_schedule != null ? toset(var.boot_disk.snapshot_schedule) : [] - project = var.project_id - zone = var.zone + project = local.project_id + zone = local.zone name = try( google_compute_resource_policy.snapshot[each.value].name, each.value @@ -153,8 +153,8 @@ resource "google_compute_disk_resource_policy_attachment" "attached" { "${attachment.disk_key}-${attachment.snapshot_schedule}" => attachment } - project = var.project_id - zone = var.zone + project = local.project_id + zone = local.zone name = try( google_compute_resource_policy.snapshot[each.value.snapshot_schedule].name, each.value.snapshot_schedule @@ -176,7 +176,7 @@ resource "google_compute_region_disk_resource_policy_attachment" "attached" { "${attachment.disk_key}-${attachment.snapshot_schedule}" => attachment } - project = var.project_id + project = local.project_id name = try( google_compute_resource_policy.snapshot[each.value.snapshot_schedule].name, each.value.snapshot_schedule diff --git a/modules/compute-vm/tags.tf b/modules/compute-vm/tags.tf index e06c9f8c0..d5a6a4fb7 100644 --- a/modules/compute-vm/tags.tf +++ b/modules/compute-vm/tags.tf @@ -46,7 +46,7 @@ locals { ]) tag_parent_base = format( "//compute.googleapis.com/projects/%s", - coalesce(var.project_number, var.project_id) + coalesce(var.project_number, local.project_id) ) } @@ -55,19 +55,19 @@ locals { resource "google_tags_location_tag_binding" "network" { 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}" + "${local.tag_parent_base}/zones/${local.zone}/instances/${google_compute_instance.default[0].instance_id}" ) - tag_value = each.value - location = var.zone + tag_value = lookup(local.ctx.tag_values, each.value, each.value) + location = local.zone } resource "google_tags_location_tag_binding" "instance" { for_each = local.template_create ? {} : var.tag_bindings parent = ( - "${local.tag_parent_base}/zones/${var.zone}/instances/${google_compute_instance.default[0].instance_id}" + "${local.tag_parent_base}/zones/${local.zone}/instances/${google_compute_instance.default[0].instance_id}" ) - tag_value = each.value - location = var.zone + tag_value = lookup(local.ctx.tag_values, each.value, each.value) + location = local.zone } resource "google_tags_location_tag_binding" "boot_disks" { @@ -75,10 +75,10 @@ resource "google_tags_location_tag_binding" "boot_disks" { 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}" + "${local.tag_parent_base}/zones/${local.zone}/disks/${each.value.disk_id}" ) - tag_value = each.value.tag_value - location = var.zone + tag_value = lookup(local.ctx.tag_values, each.value.tag_value, each.value.tag_value) + location = local.zone } resource "google_tags_location_tag_binding" "disks" { @@ -86,10 +86,10 @@ resource "google_tags_location_tag_binding" "disks" { 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}" + "${local.tag_parent_base}/zones/${local.zone}/disks/${each.value.disk_id}" ) - tag_value = each.value.tag_value - location = var.zone + tag_value = lookup(local.ctx.tag_values, each.value.tag_value, each.value.tag_value) + location = local.zone } resource "google_tags_location_tag_binding" "disks_regional" { @@ -99,7 +99,7 @@ resource "google_tags_location_tag_binding" "disks_regional" { parent = ( "${local.tag_parent_base}/regions/${local.region}/disks/${each.value.disk_id}" ) - tag_value = each.value.tag_value + tag_value = lookup(local.ctx.tag_values, each.value.tag_value, each.value.tag_value) location = local.region } @@ -108,8 +108,8 @@ resource "google_tags_location_tag_binding" "disks_regional" { # resource "google_tags_location_tag_binding" "template" { # for_each = local.template_create ? var.tag_bindings : {} # parent = ( -# "${local.tag_parent_base}/regions/${local.region}/instanceTemplates/${google_compute_instance.default[0].instance_id}" +# "${local.tag_parent_base}/regions/${local.region}/instanceTemplates/${google_compute_instance_template.default[0].instance_id}" # ) -# tag_value = each.value +# tag_value = lookup(local.ctx.tag_values, each.value, each.value) # location = local.region # } diff --git a/modules/compute-vm/template.tf b/modules/compute-vm/template.tf index 4e06f9be5..4001d3e65 100644 --- a/modules/compute-vm/template.tf +++ b/modules/compute-vm/template.tf @@ -22,7 +22,7 @@ locals { resource "google_compute_instance_template" "default" { provider = google-beta count = local.template_create && !local.template_regional ? 1 : 0 - project = var.project_id + project = local.project_id region = local.region name_prefix = "${var.name}-" description = var.description @@ -67,7 +67,11 @@ resource "google_compute_instance_template" "default" { dynamic "disk_encryption_key" { for_each = var.encryption != null ? [""] : [] content { - kms_key_self_link = var.encryption.kms_key_self_link + kms_key_self_link = lookup( + local.ctx.kms_keys, + var.encryption.kms_key_self_link, + var.encryption.kms_key_self_link + ) } } } @@ -117,7 +121,11 @@ resource "google_compute_instance_template" "default" { dynamic "disk_encryption_key" { for_each = var.encryption != null ? [""] : [] content { - kms_key_self_link = var.encryption.kms_key_self_link + kms_key_self_link = lookup( + local.ctx.kms_keys, + var.encryption.kms_key_self_link, + var.encryption.kms_key_self_link + ) } } } @@ -127,15 +135,27 @@ resource "google_compute_instance_template" "default" { for_each = var.network_interfaces iterator = config content { - network = config.value.network - subnetwork = config.value.subnetwork - network_ip = try(config.value.addresses.internal, null) + network = lookup( + local.ctx.networks, config.value.network, config.value.network + ) + subnetwork = lookup( + local.ctx.subnets, config.value.subnetwork, config.value.subnetwork + ) + network_ip = try( + local.ctx.addresses[config.value.addresses.internal], + 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) + nat_ip = try( + local.ctx.addresses[config.value.addresses.external], + config.value.addresses.external, + null + ) } } dynamic "alias_ip_range" { @@ -221,7 +241,7 @@ resource "google_compute_instance_template" "default" { resource "google_compute_region_instance_template" "default" { provider = google-beta count = local.template_create && local.template_regional ? 1 : 0 - project = var.project_id + project = local.project_id region = local.region name_prefix = "${var.name}-" description = var.description @@ -266,7 +286,10 @@ resource "google_compute_region_instance_template" "default" { dynamic "disk_encryption_key" { for_each = var.encryption != null ? [""] : [] content { - kms_key_self_link = var.encryption.kms_key_self_link + kms_key_self_link = try( + local.ctx.kms_keys[var.encryption.kms_key_self_link], + var.encryption.kms_key_self_link + ) } } } @@ -316,7 +339,10 @@ resource "google_compute_region_instance_template" "default" { dynamic "disk_encryption_key" { for_each = var.encryption != null ? [""] : [] content { - kms_key_self_link = var.encryption.kms_key_self_link + kms_key_self_link = try( + local.ctx.kms_keys[var.encryption.kms_key_self_link], + var.encryption.kms_key_self_link + ) } } } @@ -326,15 +352,27 @@ resource "google_compute_region_instance_template" "default" { for_each = var.network_interfaces iterator = config content { - network = config.value.network - subnetwork = config.value.subnetwork - network_ip = try(config.value.addresses.internal, null) + network = lookup( + local.ctx.networks, config.value.network, config.value.network + ) + subnetwork = lookup( + local.ctx.subnets, config.value.subnetwork, config.value.subnetwork + ) + network_ip = try( + local.ctx.addresses[config.value.addresses.internal], + 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) + nat_ip = try( + local.ctx.addresses[config.value.addresses.external], + config.value.addresses.external, + null + ) } } dynamic "alias_ip_range" { diff --git a/modules/compute-vm/variables.tf b/modules/compute-vm/variables.tf index 2d502af73..6e6dfa9c9 100644 --- a/modules/compute-vm/variables.tf +++ b/modules/compute-vm/variables.tf @@ -122,6 +122,23 @@ variable "confidential_compute" { default = false } +variable "context" { + description = "Context-specific interpolations." + type = object({ + addresses = optional(map(string), {}) + custom_roles = optional(map(string), {}) + kms_keys = optional(map(string), {}) + iam_principals = optional(map(string), {}) + locations = optional(map(string), {}) + networks = optional(map(string), {}) + project_ids = optional(map(string), {}) + subnets = optional(map(string), {}) + tag_values = optional(map(string), {}) + }) + default = {} + nullable = false +} + variable "create_template" { description = "Create instance template instead of instances. Defaults to a global template." type = object({ diff --git a/tests/modules/compute_vm/context-template-regional.tfvars b/tests/modules/compute_vm/context-template-regional.tfvars new file mode 100644 index 000000000..fc11cce4f --- /dev/null +++ b/tests/modules/compute_vm/context-template-regional.tfvars @@ -0,0 +1,62 @@ +attached_disks = [{ + name = "data-0" + size = 10 + } +] +context = { + addresses = { + ext-test-0 = "35.10.10.10" + int-test-0 = "10.0.0.10" + } + custom_roles = { + myrole_one = "organizations/366118655033/roles/myRoleOne" + } + iam_principals = { + mygroup = "group:test-group@example.com" + } + kms_keys = { + test = "projects/foo-prod-sec-core/locations/global/keyRings/prod-global-default/cryptoKeys/compute" + } + locations = { + ew8a = "europe-west8-a" + } + networks = { + test = "projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0" + } + project_ids = { + test = "foo-test-0" + } + subnets = { + test = "projects/foo-dev-net-spoke-0/regions/europe-west1/subnetworks/gce" + } + tag_values = { + "test/one" = "tagValues/1234567890" + } +} +create_template = { + regional = true +} +encryption = { + encrypt_boot = true + kms_key_self_link = "$kms_keys:test" +} +iam = { + "$custom_roles:myrole_one" = [ + "$iam_principals:mygroup" + ] +} +name = "test" +network_interfaces = [{ + network = "$networks:test" + subnetwork = "$subnets:test" + nat = true + addresses = { + external = "$addresses:ext-test-0" + internal = "$addresses:int-test-0" + } +}] +project_id = "$project_ids:test" +tag_bindings = { + foo = "$tag_values:test/one" +} +zone = "$locations:ew8a" diff --git a/tests/modules/compute_vm/context-template-regional.yaml b/tests/modules/compute_vm/context-template-regional.yaml new file mode 100644 index 000000000..073ca8c5c --- /dev/null +++ b/tests/modules/compute_vm/context-template-regional.yaml @@ -0,0 +1,122 @@ +# 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. + +values: + google_compute_instance_iam_binding.default["$custom_roles:myrole_one"]: + condition: [] + instance_name: test + members: + - group:test-group@example.com + project: foo-test-0 + role: organizations/366118655033/roles/myRoleOne + zone: europe-west8-a + google_compute_region_instance_template.default[0]: + advanced_machine_features: [] + can_ip_forward: false + description: Managed by the compute-vm Terraform module. + disk: + - auto_delete: true + boot: true + disk_encryption_key: + - kms_key_self_link: projects/foo-prod-sec-core/locations/global/keyRings/prod-global-default/cryptoKeys/compute + kms_key_service_account: null + disk_name: null + disk_size_gb: 10 + disk_type: pd-balanced + guest_os_features: null + labels: null + resource_manager_tags: null + resource_policies: null + source: null + source_image: projects/debian-cloud/global/images/family/debian-11 + source_image_encryption_key: [] + source_snapshot: null + source_snapshot_encryption_key: [] + - auto_delete: true + device_name: data-0 + disk_encryption_key: + - kms_key_self_link: projects/foo-prod-sec-core/locations/global/keyRings/prod-global-default/cryptoKeys/compute + kms_key_service_account: null + disk_name: data-0 + disk_size_gb: 10 + disk_type: pd-balanced + guest_os_features: null + labels: null + mode: READ_WRITE + resource_manager_tags: null + resource_policies: null + source: null + source_image_encryption_key: [] + source_snapshot: null + source_snapshot_encryption_key: [] + type: PERSISTENT + effective_labels: + goog-terraform-provisioned: 'true' + enable_display: null + guest_accelerator: [] + instance_description: null + key_revocation_action_type: null + labels: null + machine_type: f1-micro + metadata: null + metadata_startup_script: null + min_cpu_platform: null + name_prefix: test- + network_interface: + - access_config: + - nat_ip: 35.10.10.10 + alias_ip_range: [] + network: projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0 + network_ip: 10.0.0.10 + nic_type: null + queue_count: null + subnetwork: projects/foo-dev-net-spoke-0/regions/europe-west1/subnetworks/gce + network_performance_config: [] + partner_metadata: null + project: foo-test-0 + region: europe-west8 + reservation_affinity: [] + resource_manager_tags: null + resource_policies: null + scheduling: + - automatic_restart: true + availability_domain: null + graceful_shutdown: [] + host_error_timeout_seconds: null + instance_termination_action: null + local_ssd_recovery_timeout: [] + maintenance_interval: null + max_run_duration: [] + min_node_cpus: null + node_affinities: [] + on_host_maintenance: MIGRATE + on_instance_stop_action: [] + preemptible: false + provisioning_model: STANDARD + termination_time: null + service_account: + - scopes: + - https://www.googleapis.com/auth/devstorage.read_only + - https://www.googleapis.com/auth/logging.write + - https://www.googleapis.com/auth/monitoring.write + shielded_instance_config: [] + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null +counts: + google_compute_instance_iam_binding: 1 + google_compute_region_instance_template: 1 + modules: 0 + resources: 2 diff --git a/tests/modules/compute_vm/context-template.tfvars b/tests/modules/compute_vm/context-template.tfvars new file mode 100644 index 000000000..3ee07484a --- /dev/null +++ b/tests/modules/compute_vm/context-template.tfvars @@ -0,0 +1,60 @@ +attached_disks = [{ + name = "data-0" + size = 10 + } +] +context = { + addresses = { + ext-test-0 = "35.10.10.10" + int-test-0 = "10.0.0.10" + } + custom_roles = { + myrole_one = "organizations/366118655033/roles/myRoleOne" + } + iam_principals = { + mygroup = "group:test-group@example.com" + } + kms_keys = { + test = "projects/foo-prod-sec-core/locations/global/keyRings/prod-global-default/cryptoKeys/compute" + } + locations = { + ew8a = "europe-west8-a" + } + networks = { + test = "projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0" + } + project_ids = { + test = "foo-test-0" + } + subnets = { + test = "projects/foo-dev-net-spoke-0/regions/europe-west1/subnetworks/gce" + } + tag_values = { + "test/one" = "tagValues/1234567890" + } +} +create_template = {} +encryption = { + encrypt_boot = true + kms_key_self_link = "$kms_keys:test" +} +iam = { + "$custom_roles:myrole_one" = [ + "$iam_principals:mygroup" + ] +} +name = "test" +network_interfaces = [{ + network = "$networks:test" + subnetwork = "$subnets:test" + nat = true + addresses = { + external = "$addresses:ext-test-0" + internal = "$addresses:int-test-0" + } +}] +project_id = "$project_ids:test" +tag_bindings = { + foo = "$tag_values:test/one" +} +zone = "$locations:ew8a" diff --git a/tests/modules/compute_vm/context-template.yaml b/tests/modules/compute_vm/context-template.yaml new file mode 100644 index 000000000..edd9a7003 --- /dev/null +++ b/tests/modules/compute_vm/context-template.yaml @@ -0,0 +1,123 @@ +# 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. + +values: + google_compute_instance_iam_binding.default["$custom_roles:myrole_one"]: + condition: [] + instance_name: test + members: + - group:test-group@example.com + project: foo-test-0 + role: organizations/366118655033/roles/myRoleOne + zone: europe-west8-a + google_compute_instance_template.default[0]: + advanced_machine_features: [] + can_ip_forward: false + description: Managed by the compute-vm Terraform module. + disk: + - auto_delete: true + boot: true + disk_encryption_key: + - kms_key_self_link: projects/foo-prod-sec-core/locations/global/keyRings/prod-global-default/cryptoKeys/compute + kms_key_service_account: null + disk_name: null + disk_size_gb: 10 + disk_type: pd-balanced + guest_os_features: null + labels: null + resource_manager_tags: null + resource_policies: null + source: null + source_image: projects/debian-cloud/global/images/family/debian-11 + source_image_encryption_key: [] + source_snapshot: null + source_snapshot_encryption_key: [] + - auto_delete: true + device_name: data-0 + disk_encryption_key: + - kms_key_self_link: projects/foo-prod-sec-core/locations/global/keyRings/prod-global-default/cryptoKeys/compute + kms_key_service_account: null + disk_name: data-0 + disk_size_gb: 10 + disk_type: pd-balanced + guest_os_features: null + labels: null + mode: READ_WRITE + resource_manager_tags: null + resource_policies: null + source: null + source_image_encryption_key: [] + source_snapshot: null + source_snapshot_encryption_key: [] + type: PERSISTENT + effective_labels: + goog-terraform-provisioned: 'true' + enable_display: null + guest_accelerator: [] + instance_description: null + key_revocation_action_type: null + labels: null + machine_type: f1-micro + metadata: null + metadata_startup_script: null + min_cpu_platform: null + name_prefix: test- + network_interface: + - access_config: + - nat_ip: 35.10.10.10 + alias_ip_range: [] + ipv6_access_config: [] + network: projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0 + network_ip: 10.0.0.10 + nic_type: null + queue_count: null + subnetwork: projects/foo-dev-net-spoke-0/regions/europe-west1/subnetworks/gce + network_performance_config: [] + partner_metadata: null + project: foo-test-0 + region: europe-west8 + reservation_affinity: [] + resource_manager_tags: null + resource_policies: null + scheduling: + - automatic_restart: true + availability_domain: null + graceful_shutdown: [] + host_error_timeout_seconds: null + instance_termination_action: null + local_ssd_recovery_timeout: [] + maintenance_interval: null + max_run_duration: [] + min_node_cpus: null + node_affinities: [] + on_host_maintenance: MIGRATE + on_instance_stop_action: [] + preemptible: false + provisioning_model: STANDARD + termination_time: null + service_account: + - scopes: + - https://www.googleapis.com/auth/devstorage.read_only + - https://www.googleapis.com/auth/logging.write + - https://www.googleapis.com/auth/monitoring.write + shielded_instance_config: [] + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null +counts: + google_compute_instance_iam_binding: 1 + google_compute_instance_template: 1 + modules: 0 + resources: 2 diff --git a/tests/modules/compute_vm/context-vm.tfvars b/tests/modules/compute_vm/context-vm.tfvars new file mode 100644 index 000000000..91295c4d8 --- /dev/null +++ b/tests/modules/compute_vm/context-vm.tfvars @@ -0,0 +1,59 @@ +attached_disks = [{ + name = "data-0" + size = 10 + } +] +context = { + addresses = { + ext-test-0 = "35.10.10.10" + int-test-0 = "10.0.0.10" + } + custom_roles = { + myrole_one = "organizations/366118655033/roles/myRoleOne" + } + iam_principals = { + mygroup = "group:test-group@example.com" + } + kms_keys = { + test = "projects/foo-prod-sec-core/locations/global/keyRings/prod-global-default/cryptoKeys/compute" + } + locations = { + ew8a = "europe-west8-a" + } + networks = { + test = "projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0" + } + project_ids = { + test = "foo-test-0" + } + subnets = { + test = "projects/foo-dev-net-spoke-0/regions/europe-west1/subnetworks/gce" + } + tag_values = { + "test/one" = "tagValues/1234567890" + } +} +encryption = { + encrypt_boot = true + kms_key_self_link = "$kms_keys:test" +} +iam = { + "$custom_roles:myrole_one" = [ + "$iam_principals:mygroup" + ] +} +name = "test" +network_interfaces = [{ + network = "$networks:test" + subnetwork = "$subnets:test" + nat = true + addresses = { + external = "$addresses:ext-test-0" + internal = "$addresses:int-test-0" + } +}] +project_id = "$project_ids:test" +tag_bindings = { + foo = "$tag_values:test/one" +} +zone = "$locations:ew8a" diff --git a/tests/modules/compute_vm/context-vm.yaml b/tests/modules/compute_vm/context-vm.yaml new file mode 100644 index 000000000..252e886cf --- /dev/null +++ b/tests/modules/compute_vm/context-vm.yaml @@ -0,0 +1,164 @@ +# 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. + +values: + google_compute_disk.disks["data-0"]: + architecture: null + async_primary_disk: [] + create_snapshot_before_destroy: false + create_snapshot_before_destroy_prefix: null + description: null + disk_encryption_key: + - kms_key_self_link: projects/foo-prod-sec-core/locations/global/keyRings/prod-global-default/cryptoKeys/compute + kms_key_service_account: null + raw_key: null + rsa_encrypted_key: null + effective_labels: + disk_name: data-0 + disk_type: pd-balanced + goog-terraform-provisioned: 'true' + image: null + labels: + disk_name: data-0 + disk_type: pd-balanced + name: test-data-0 + params: [] + project: foo-test-0 + size: 10 + snapshot: null + source_disk: null + source_image_encryption_key: [] + source_instant_snapshot: null + source_snapshot_encryption_key: [] + source_storage_object: null + storage_pool: null + terraform_labels: + disk_name: data-0 + disk_type: pd-balanced + goog-terraform-provisioned: 'true' + timeouts: null + type: pd-balanced + zone: europe-west8-a + google_compute_instance.default[0]: + advanced_machine_features: [] + allow_stopping_for_update: true + attached_disk: + - device_name: data-0 + disk_encryption_key_raw: null + disk_encryption_key_rsa: null + disk_encryption_service_account: null + force_attach: null + mode: READ_WRITE + source: test-data-0 + boot_disk: + - auto_delete: true + disk_encryption_key_raw: null + disk_encryption_key_rsa: null + disk_encryption_service_account: null + force_attach: null + initialize_params: + - enable_confidential_compute: null + image: projects/debian-cloud/global/images/family/debian-11 + resource_manager_tags: null + size: 10 + source_image_encryption_key: [] + source_snapshot_encryption_key: [] + storage_pool: null + type: pd-balanced + interface: null + kms_key_self_link: projects/foo-prod-sec-core/locations/global/keyRings/prod-global-default/cryptoKeys/compute + mode: READ_WRITE + can_ip_forward: false + deletion_protection: false + description: Managed by the compute-vm Terraform module. + desired_status: null + effective_labels: + goog-terraform-provisioned: 'true' + enable_display: false + hostname: null + instance_encryption_key: [] + key_revocation_action_type: null + labels: null + machine_type: f1-micro + metadata: null + metadata_startup_script: null + name: test + network_interface: + - access_config: + - nat_ip: 35.10.10.10 + public_ptr_domain_name: null + alias_ip_range: [] + ipv6_access_config: [] + network: projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0 + network_ip: 10.0.0.10 + nic_type: null + queue_count: null + security_policy: null + subnetwork: projects/foo-dev-net-spoke-0/regions/europe-west1/subnetworks/gce + network_performance_config: [] + params: [] + partner_metadata: null + project: foo-test-0 + resource_policies: null + scheduling: + - automatic_restart: true + availability_domain: null + graceful_shutdown: [] + host_error_timeout_seconds: null + instance_termination_action: null + local_ssd_recovery_timeout: [] + maintenance_interval: null + max_run_duration: [] + min_node_cpus: null + node_affinities: [] + on_host_maintenance: MIGRATE + on_instance_stop_action: [] + preemptible: false + provisioning_model: STANDARD + termination_time: null + scratch_disk: [] + service_account: + - scopes: + - https://www.googleapis.com/auth/devstorage.read_only + - https://www.googleapis.com/auth/logging.write + - https://www.googleapis.com/auth/monitoring.write + shielded_instance_config: [] + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + zone: europe-west8-a + google_compute_instance_iam_binding.default["$custom_roles:myrole_one"]: + condition: [] + instance_name: test + members: + - group:test-group@example.com + project: foo-test-0 + role: organizations/366118655033/roles/myRoleOne + zone: europe-west8-a + google_tags_location_tag_binding.disks["data-0/foo"]: + location: europe-west8-a + tag_value: tagValues/1234567890 + timeouts: null + google_tags_location_tag_binding.instance["foo"]: + location: europe-west8-a + tag_value: tagValues/1234567890 + timeouts: null +counts: + google_compute_disk: 1 + google_compute_instance: 1 + google_compute_instance_iam_binding: 1 + google_tags_location_tag_binding: 2 + modules: 0 + resources: 5 diff --git a/tests/modules/compute_vm/tftest.yaml b/tests/modules/compute_vm/tftest.yaml new file mode 100644 index 000000000..61f5f1dd4 --- /dev/null +++ b/tests/modules/compute_vm/tftest.yaml @@ -0,0 +1,19 @@ +# 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. + +module: modules/compute-vm +tests: + context-template: + context-template-regional: + context-vm: