How to update existing state created by module block?

Terraform Version

Terraform v1.3.0
on darwin_arm64
+ provider registry.terraform.io/hashicorp/google v4.36.0

Terraform Configuration Files

/module/instances/main.tf

resource "google_compute_instance" "vm_instance" {
  dynamic "attached_disk" {
    for_each =  var.attached_disk
    content {
      device_name = lookup(attached_disk.value, "device_name", null)
      mode        = lookup(attached_disk.value, "mode", null)
      source      = lookup(attached_disk.value, "source", null)
    }
  }

  boot_disk {
    auto_delete = "true"
    device_name = var.name
    mode        = "READ_WRITE"
    initialize_params {
      image = var.image
    }
  }

  can_ip_forward = "false"

  confidential_instance_config {
    enable_confidential_compute = "false"
  }

  deletion_protection = "true"
  enable_display      = "false"

  machine_type = var.machine_type

  metadata = {
    ssh-keys = "${var.owner}:${var.ssh_key}"
  }

  name = var.name

  network_interface {
    access_config {
      nat_ip     = var.nat_ip
      network_tier = "PREMIUM"
    }

    network            = var.network
    network_ip       = var.network_ip
    queue_count        = "0"
    stack_type         = "IPV4_ONLY"
    subnetwork         = var.subnetwork
    subnetwork_project = var.project
  }

  project = var.project

  reservation_affinity {
    type = "ANY_RESERVATION"
  }

  scheduling {
    automatic_restart   = "true"
    min_node_cpus       = "0"
    on_host_maintenance = "MIGRATE"
    preemptible         = "false"
    provisioning_model  = "STANDARD"
  }

  service_account {
    email  = var.service_account
    scopes = ["https://www.googleapis.com/auth/cloud-platform"]
  }

  shielded_instance_config {
    enable_integrity_monitoring = "true"
    enable_secure_boot          = "false"
    enable_vtpm                 = "true"
  }

  zone = var.zone
}

/instances/compute_instance.tf

module "instance" {
  source = "../module/instances"
  
  name = "vm1"
  # Fill variables
}

Expected Behavior

No changes. Your infrastructure matches the configuration.

Actual Behavior

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

    module.gerrit-instance.google_compute_instance.vm_instance must be replaced
-/+ resource "google_compute_instance" "vm_instance" {
      #...
      ~ id                   =  "~~" -> (known after apply)
      ~ instance_id          =  "~~" -> (known after apply)
      # ...
        name                 = "vm1"
      # ...
      ~ scheduling {
            # (5 unchanged attributes hidden)
        }

        # (3 unchanged blocks hidden)
    }

Steps to Reproduce

terraform init
terraform apply
terraform plan

Additional Context

  1. Create “vm1” instance on gcp
  2. To check apply, run $ terraform plan
  3. Planned action is Remove “vm1” and create replacement “vm1”

Should I delete and create a replacement instead of updating every time I modify an argument?

Somewhere in the bits you’ve not shown us, there should be an explanation from Terraform why it needs to do a replace.

Below log is all.

I didn’t change terraform at all, but I don’t know why the id of google_compute_instance resource changes.

The ones marked with ${} are all the ones I changed. Tell me if you need.

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # module.gerrit-instance.google_compute_instance.vm_instance must be replaced
-/+ resource "google_compute_instance" "vm_instance" {
      ~ cpu_platform         = "Intel Broadwell" -> (known after apply)
      ~ current_status       = "RUNNING" -> (known after apply)
      ~ guest_accelerator    = [] -> (known after apply)
      ~ id                   = "projects/${project}/zones/us-central1-f/instances/vm1" -> (known after apply)
      ~ instance_id          = "${instance_id}" -> (known after apply)
      ~ label_fingerprint    = "${label_fingerprint}=" -> (known after apply)
      ~ metadata_fingerprint = "${metadata_fingerprint}" -> (known after apply)
      + min_cpu_platform     = (known after apply)
        name                 = "vm1"
      - resource_policies    = [] -> null
      ~ self_link            = "https://www.googleapis.com/compute/v1/projects/${project}/zones/us-central1-f/instances/vm1" -> (known after apply)
      - tags                 = [
          - "http-server",
        ] -> null
      ~ tags_fingerprint     = "${tags_fingerprint}" -> (known after apply)
        # (8 unchanged attributes hidden)

      ~ boot_disk {
          + disk_encryption_key_sha256 = (known after apply)
          + kms_key_self_link          = (known after apply)
          ~ source                     = "https://www.googleapis.com/compute/v1/projects/${project}/zones/us-central1-f/disks/vm1" -> (known after apply)
            # (3 unchanged attributes hidden)

          ~ initialize_params {
              ~ image  = "https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-2204-jammy-v20220924" -> "ubuntu-2204-jammy-v20220924"
              ~ labels = {} -> (known after apply)
              ~ size   = 10 -> (known after apply)
              ~ type   = "pd-standard" -> (known after apply)
            }
        }

      + confidential_instance_config { # forces replacement
          + enable_confidential_compute = false
        }

      ~ network_interface {
          + ipv6_access_type   = (known after apply)
          ~ name               = "${name}" -> (known after apply)
          ~ network_ip         = "${network_ip}" -> (known after apply)
            # (5 unchanged attributes hidden)

          ~ access_config {
              ~ nat_ip       = "${nat_ip}" -> (known after apply)
                # (1 unchanged attribute hidden)
            }
        }

      ~ scheduling {
            # (5 unchanged attributes hidden)
        }

        # (3 unchanged blocks hidden)
    }

Plan: 1 to add, 0 to change, 1 to destroy

See the “forces replacement” comment? That’s telling you why Terraform believes it needs to replace this resource.

I don’t work with GCE so I can’t easily test, but here’s my guess as to what is going on:

In your configuration you’re setting "false" as a string, and this is apparently being accepted and silently converted somewhere … but stored in some way that gives rise to it being considered a change when terraform-provider-google reads back the instance state on the next run.

Changing it to an actual boolean false in your configuration might fix this.

Assuming I’m right, this minor mistake producing such confusing behaviour is probably worth a bug report - it’s pretty user-unfriendly - but consistent with other problems caused by mistakes coding Terraform providers that I’ve seen.

1 Like

As you said, it was resolved by clearing the confirmation_instance_config block in the module.

No changes. Your infrastructure matches the configuration.

Thank you so much. I’ll register this problem separately.

This seems like something that might happen if the provider schema doesn’t match the underlying API: if the remote API only returns its “confidential instance config” when that nested flag is true then the provider will need some extra logic to avoid telling Terraform Core that the block has been removed when it refreshes the object from the remote API.

I would suggest reporting this in an issue in this provider’s own repository.

2 posts were split to a new topic: Change the state of an existing module