diff --git a/modules/compute-vm/README.md b/modules/compute-vm/README.md index 450ccad73..aea113b74 100644 --- a/modules/compute-vm/README.md +++ b/modules/compute-vm/README.md @@ -34,7 +34,8 @@ In both modes, an optional service account can be created and assigned to either - [Instance group](#instance-group) - [Instance Schedule](#instance-schedule) - [Snapshot Schedules](#snapshot-schedules) - - [Resource Manager Tags](#resource-manager-tags) + - [Resource Manager Tags (non-firewall)](#resource-manager-tags-non-firewall) + - [Resource Manager Tags (firewall)](#resource-manager-tags-firewall) - [Variables](#variables) - [Outputs](#outputs) @@ -678,15 +679,21 @@ module "instance" { # tftest modules=1 resources=5 inventory=snapshot-schedule-create.yaml ``` -### Resource Manager Tags +### Resource Manager Tags (non-firewall) -Resource manager tags (or "secure tags") bindings are supported with the following limitations: +Resource manager tags bindings for use in IAM or org policy conditions are supported via the `tag_bindings` variable with the following limitations: -- a single `tag_bindings` variable is used for both the instance and the boot disk - tag bindings are not created for attached disks - tag bindings will not be created for the boot disk if the `use_independent_disk` flag is true - tag bindings are ignored for instance templates +The current provider implementation is sub-optimal and forces + +- recreation of the instance on tag changes +- specifying both the key and value where only the value is actually needed + +This is an example of setting tag bindings: + ```hcl module "simple-vm-example" { source = "./fabric/modules/compute-vm" @@ -703,6 +710,33 @@ module "simple-vm-example" { } # tftest modules=1 resources=1 inventory=tag-bindings.yaml ``` + +### Resource Manager Tags (firewall) + +Network-scoped resource manager tags (or "secure tags") bindings for use in firewall rules are supported with similar limitations as in the section above, via a separate `tag_bindings_firewall` variable that only applies bindings to the instance and not the boot disk. + +This is an example of setting both types of tag bindings: + +```hcl +module "simple-vm-example" { + source = "./fabric/modules/compute-vm" + project_id = var.project_id + zone = "europe-west1-b" + name = "test" + network_interfaces = [{ + network = var.vpc.self_link + subnetwork = var.subnet.self_link + }] + tag_bindings = { + "tagKeys/1234567890" = "tagValues/7890123456" + } + # tags here need to be scoped to a VPC + tag_bindings_firewall = { + "tagKeys/5678901234" = "tagValues/3456789012" + } +} +# tftest modules=1 resources=1 inventory=tag-bindings.yaml +``` ## Variables @@ -711,7 +745,7 @@ module "simple-vm-example" { | [name](variables.tf#L235) | Instance name. | string | ✓ | | | [network_interfaces](variables.tf#L240) | Network interfaces configuration. Use self links for Shared VPC, set addresses to null if not needed. | list(object({…})) | ✓ | | | [project_id](variables.tf#L278) | Project id. | string | ✓ | | -| [zone](variables.tf#L370) | Compute zone. | string | ✓ | | +| [zone](variables.tf#L376) | 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#L83) | Boot disk properties. | object({…}) | | {…} | @@ -734,8 +768,9 @@ module "simple-vm-example" { | [service_account](variables.tf#L295) | 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#L305) | Shielded VM configuration of the instances. | object({…}) | | null | | [snapshot_schedules](variables.tf#L315) | Snapshot schedule resource policies that can be attached to disks. | map(object({…})) | | {} | -| [tag_bindings](variables.tf#L358) | Tag bindings for this instance, in tag key => tag value format. | map(string) | | null | -| [tags](variables.tf#L364) | Instance network tags for firewall rule targets. | list(string) | | [] | +| [tag_bindings](variables.tf#L358) | Resource manager tag bindings for this instance, in tag key => tag value format. | map(string) | | null | +| [tag_bindings_firewall](variables.tf#L364) | Firewall (network scoped) tag bindings for this instance, in tag key => tag value format. | map(string) | | null | +| [tags](variables.tf#L370) | Instance network tags for firewall rule targets. | list(string) | | [] | ## Outputs @@ -754,4 +789,3 @@ module "simple-vm-example" { | [template](outputs.tf#L82) | Template resource. | | | [template_name](outputs.tf#L87) | Template name. | | - diff --git a/modules/compute-vm/main.tf b/modules/compute-vm/main.tf index e2d4dfcc3..c39db31b3 100644 --- a/modules/compute-vm/main.tf +++ b/modules/compute-vm/main.tf @@ -58,7 +58,17 @@ locals { ) ) } - termination_action = var.options.spot ? coalesce(var.options.termination_action, "STOP") : null + tags_combined = ( + var.tag_bindings == null && var.tag_bindings_firewall == null + ? null + : merge( + coalesce(var.tag_bindings, {}), + coalesce(var.tag_bindings_firewall, {}) + ) + ) + termination_action = ( + var.options.spot ? coalesce(var.options.termination_action, "STOP") : null + ) } resource "google_compute_disk" "boot" { @@ -294,9 +304,9 @@ resource "google_compute_instance" "default" { } dynamic "params" { - for_each = var.tag_bindings == null ? [] : [""] + for_each = local.tags_combined == null ? [] : [""] content { - resource_manager_tags = var.tag_bindings + resource_manager_tags = local.tags_combined } } diff --git a/modules/compute-vm/tags.tf b/modules/compute-vm/tags.tf index cce3b99cc..fccc7e4d2 100644 --- a/modules/compute-vm/tags.tf +++ b/modules/compute-vm/tags.tf @@ -16,15 +16,15 @@ # tfdoc:file:description Tag bindings. -# TODO: re-implement once -# - the provider accepts a project id in the parent without a permadiff -# - the disk resource exposes an id that can be used to build the parent - -# locals { -# tag_parent_base = ( -# "//compute.googleapis.com/projects/${var.project_id}/zones/${var.zone}" -# ) -# } +# TODO: re-implement once the following have been addressed in the provider +# - permadiff in google_tags_location_tag_binding which returns a project +# number in the tag id even when a project id is set +# - no numeric id exposed from the google_compute_disk resource making it +# impossible to derive the tag binding parent +# - google_compute_instance.params.resource_manager_tags and +# google_compute_instance.boot_disk.initialize_params.resource_manager_tags +# attributes need a map of tag key => tag value, while only the tag value +# is really needed by the API # resource "google_tags_location_tag_binding" "instance" { # for_each = var.create_template ? {} : coalesce(var.tag_bindings, {}) diff --git a/modules/compute-vm/variables.tf b/modules/compute-vm/variables.tf index d82ff1d4b..e57dacce9 100644 --- a/modules/compute-vm/variables.tf +++ b/modules/compute-vm/variables.tf @@ -356,7 +356,13 @@ variable "snapshot_schedules" { } variable "tag_bindings" { - description = "Tag bindings for this instance, in tag key => tag value format." + description = "Resource manager tag bindings for this instance, in tag key => tag value format." + type = map(string) + default = null +} + +variable "tag_bindings_firewall" { + description = "Firewall (network scoped) tag bindings for this instance, in tag key => tag value format." type = map(string) default = null }