hashicorp / terraform-provider-google

Terraform Provider for Google Cloud Platform
https://registry.terraform.io/providers/hashicorp/google/latest/docs
Mozilla Public License 2.0
2.29k stars 1.72k forks source link

resource/google_compute_region_instance_group_manager: version.target_size.fixed sees change when none needed. #14826

Open ynikitin-etsy opened 1 year ago

ynikitin-etsy commented 1 year ago

Community Note

Terraform Version

Terraform v1.4.6
on darwin_amd64
+ provider registry.terraform.io/hashicorp/google v4.59.0
+ provider registry.terraform.io/hashicorp/google-beta v4.59.0

Affected Resource(s)

google_compute_region_instance_group_manager

Terraform Configuration Files

terraform {
  required_version = ">= 1.3.9, < 2.0.0"

  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "hidden"

    workspaces {
      name = "hidden"
    }
  }

  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.59.0"
    }
    google-beta = {
      source  = "hashicorp/google-beta"
      version = "~> 4.59.0"
    }
  }
}

resource "google_compute_region_instance_group_manager" "my-manager" {
  name   = "some-group-here"
  region = "us-central1"

  base_instance_name        = "my-vm"
  distribution_policy_zones = ["us-central1-a"]

  version {
    name              = "service-stable"
    instance_template = google_compute_instance_template.stable.self_link
  }

  version {
    name              = "service-canary"
    instance_template = google_compute_instance_template.stable.self_link
    target_size {
      fixed = 0
    }
  }

  update_policy {
    type                  = "PROACTIVE"
    minimal_action        = "REPLACE"
    max_surge_fixed       = 1
    max_unavailable_fixed = 0
    min_ready_sec         = 60
  }

  provider = google-beta

  lifecycle {
    ignore_changes = [
      version[0].name,
      version[1].name
    ]
  }
}

resource "google_compute_instance_template" "stable" {
  name_prefix = "vm-prefix"
  region      = "us-central1"

  description = "some description"

  instance_description = "some description"
  machine_type         = "n1-highcpu-2"
  can_ip_forward       = false

  scheduling {
    automatic_restart   = true
    on_host_maintenance = "MIGRATE"
  }

  disk {
    source_image = "cos-cloud/cos-stable"
    auto_delete  = true
    boot         = true
    disk_size_gb = 30
  }

  lifecycle {
    create_before_destroy = true
  }
}

Debug Output

Panic Output

Expected Behavior

On creation, this works fine. If I update programmatically/or through UI the instance group configuration to make "service-canary" have target_size.fixed = 1 and then back to 0, then terraform will still see a change and try to update target_size.fixed = 0.

~ version {
            name              = "service-canary"
            # (1 unchanged attribute hidden)

          + target_size {
              + fixed = 0
            }
        }

This is not ideal since we will programmatically change these templates to do canary releases. And we do not want TF to see a change after every time we release.

Actual Behavior

Terraform should see no changes.

Steps to Reproduce

  1. terraform init
  2. terraform plan
  3. terraform apply
  4. Change the instance group service-canary version to 1 and then back to 0 through the UI.
  5. terraform plan
  6. TF sees a change.

Important Factoids

Please note that its not possible to add this field to ignore_changes. Because upon any change to the template, the versions will be recreated, and target_size NOT sent. And Terraform will get this error from GCP:

Error 400: Invalid value for field 'resource.versions[0]': 
'{ "instanceTemplate": "https://www.googleapis.com/compute/v1/projects/hidden/...'. Exactly 1 version must leave target size unset, invalid

References

Using gcloud to pull the config where TF should not see changes.

versions:
- instanceTemplate: https://www.googleapis.com/compute/v1/projects/hidden/global/instanceTemplates/stable20230606162304540700000001
  name: service-stable
  targetSize:
    calculated: 4
- instanceTemplate: https://www.googleapis.com/compute/v1/projects/hidden/global/instanceTemplates/stable20230606162304540700000001
  name: service-canary
  targetSize:
    calculated: 0
    fixed: 0

b/312697042

edwardmedia commented 1 year ago

@ynikitin-etsy After you update the resource via other means, I am not sure you can achieve what you want in the resource itself. But there are two options you may consider 1) use lifecycle.ignore_changes 2) after step 3, you can run terraform import to sync your state. Does this make sense?

ynikitin-etsy commented 1 year ago

@edwardmedia Thanks for the reply. That makes sense conceptually. I have a small blurb in my issue regarding ignore_changes.

When we do ignore_changes, if the template changes, something like this happens:

{
    id: "projects/hidden/regions/us-central1/instanceGroupManagers/my-group"
    name: "my-group"
    # (15 unchanged attribute hidden)

    ~ version {
        ~ instance_template : "https://www.googleapis.com/shortened/hidden/global/instanceTemplates/stable-20230601" => Known after apply
        name: "service-stable"
    }
    ~ version {
        ~ instance_template : "https://www.googleapis.com/shortened/hidden/global/instanceTemplates/stable-20230601" => Known after apply
        name :"service-canary"
    }
}

This errors with Exactly 1 version must leave target size unset, invalid.

Terraform will not send version.target_size.fixed and thus the error above.

Since this is somewhat of a typical way to release with canaries, I do think, this functionality should have less friction. Importing state everytime I think introduces a lot of friction into this, especially if releases are common.

I think it would be possible for Terraform to be aware of the current value for the target_size and not keep resetting it to 0.

edwardmedia commented 1 year ago

I see where the problem is. Below is a pair of request and response. The api looks fine. The problem is the provider fails to update the state accordingly.

---[ REQUEST ]---------------------------------------
PATCH /compute/v1/projects/myproject/regions/us-central1/instanceGroupManagers/issue14826?alt=json&prettyPrint=false HTTP/1.1
Host: compute.googleapis.com
{
 "fingerprint": "S451Z6lxa_s=",
 "versions": [
  {
   "instanceTemplate": "https://www.googleapis.com/compute/v1/projects/myproject/global/instanceTemplates/issue1482620230606213223615300000001",
   "name": "service-stable",
   "targetSize": {}
  },
  {
   "instanceTemplate": "https://www.googleapis.com/compute/v1/projects/myproject/global/instanceTemplates/issue14826-stable20230606214007572900000001",
   "name": "service-canary",
   "targetSize": {
    "fixed": 0
   }
  }
 ]
}

---[ RESPONSE ]--------------------------------------
 HTTP/2.0 200 OK
 {
  "kind": "compute#instanceGroupManager",
   ...
  "versions": [
   {
    "name": "service-stable",
    "instanceTemplate": "https://www.googleapis.com/compute/v1/projects/myproject/global/instanceTemplates/issue1482620230606213223615300000001",
    "targetSize": {
     "calculated": 3
    }
   },
   {
    "name": "service-canary",
    "instanceTemplate": "https://www.googleapis.com/compute/v1/projects/myproject/global/instanceTemplates/issue14826-stable20230606214007572900000001",
    "targetSize": {
     "fixed": 0,
     "calculated": 0
    }
   }
  ],
  ...
}
ynikitin-etsy commented 1 year ago

@edwardmedia thank you for confirming. I wasn't sure if my initial ignore_changes screwed up my state, but no matter what I do my state is always something like this:

"version": [
              {
                "instance_template": "https://www.googleapis.com/compute/v1/projects/hidden/global/instanceTemplates/canary20230606",
                "name": "service-stable",
                "target_size": []
              },
              {
                "instance_template": "https://www.googleapis.com/compute/v1/projects/hidden/global/instanceTemplates/stable20230606",
                "name": "service-canary",
                "target_size": []
              }
            ]

It does seem like the provider is not saving the state for target_size correctly.

roaks3 commented 1 year ago

This seems related to Terraform's treatment of 0 as a zero-value (without special handling, it functions the same as if the field was omitted from the configuration).

pablin-10 commented 2 months ago

I confirm this still happens with the following configuration:

Terraform v1.8.5
on linux_amd64
+ provider registry.terraform.io/digitalocean/digitalocean v2.39.2
+ provider registry.terraform.io/hashicorp/google v5.24.0
+ provider registry.terraform.io/hashicorp/google-beta v5.25.0
+ provider registry.terraform.io/hashicorp/random v3.6.2