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.36k stars 1.75k forks source link

`google_service_networking_connection` always detects changes if `reserved_peering_ranges` has multiple entries and is not a static list #20398

Open mrozentsvayg opened 2 days ago

mrozentsvayg commented 2 days ago

Community Note

Terraform Version & Provider Version(s)

OpenTofu v1.8.5 on darwin_arm64

Affected Resource(s)

google_service_networking_connection

Terraform Configuration

private_service_access:
  private_ip_allocation:
    default-ip-range: 172.22.128.0/20
    record1: 10.100.0.0/22
    record2: 10.100.4.0/26
    record4: 10.100.4.64/28
    record5: 10.100.4.128/26
    record6: 10.100.12.0/24
    record3: 10.101.0.0/22
    record8: 10.101.4.0/26
    record7: 10.101.4.64/28
    record9: 10.102.0.0/24
    record11: 10.102.4.0/26
    record10: 10.102.4.64/28
terraform {
  required_providers {
    google-beta = {
      source  = "opentofu/google-beta"
      version = ">= 6.10.0"
    }
  }
}

provider "google-beta" {
  project = var.project
}

variable "config" {
  type    = string
  default = "../../conf/conf.yaml"
}

locals {
  config  = yamldecode(file(var.config))
}

resource "google_service_networking_connection" "private_service_connection" {
  provider = google-beta

  network = "default"
  service = "services/servicenetworking.googleapis.com"
  reserved_peering_ranges = [ for k, v in local.config.private_service_access.private_ip_allocation : k ]
}

Debug Output

OpenTofu will perform the following actions:

  # google_service_networking_connection.private_service_connection will be updated in-place
  ~ resource "google_service_networking_connection" "private_service_connection" {
        id                      = "default:services/servicenetworking.googleapis.com"
      ~ reserved_peering_ranges = [
            "default-ip-range",
          - "record1",
          - "record2",
          - "record3",
          - "record4",
          - "record5",
          - "record6",
          - "record7",
          - "record8",
            "record9",
            # (1 unchanged element hidden)
            "record11",
          + "record3",
          + "record7",
          + "record8",
          + "record1",
          + "record6",
          + "record5",
          + "record4",
          + "record2",
        ]
      + update_on_creation_fail = true
        # (3 unchanged attributes hidden)
    }

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

Expected Behavior

No changes to be detected, the order of ranges names in the list should not matter.

Actual Behavior

Multiple unnecessary changes for the private connections to services to be performed likely causing impact. Could also be over and over again (didn't test), in case if gcp maintains it's own order of ranges, and the order of the map keys as implemented may always differ.

It's only possible to maintain the order if reserved_peering_ranges is implemented as static list. this:

private_service_access:
  reserved_peering_ranges:
  - default-ip-range
  - record1
  - record2
  - record3
  - record4
  - record5
  - record6
  - record7
  - record8
  - record9
  - record10
  - record11

 ...

 resource "google_service_networking_connection" "private_service_connection" {
  provider = google-beta

  network = "default"
  service = "services/servicenetworking.googleapis.com"
  # reserved_peering_ranges = [ for k, v in local.config.private_service_access.private_ip_allocation : k ]
  reserved_peering_ranges = local.config.private_service_access.reserved_peering_ranges
}

wouldn't trigger changes, but the implementation is suboptimal and requires double configuration to work it around. it would also require something like lifecycle precondition to make sure that the ip allocations are listed for the private connections:

    precondition {
      condition     = contains([for range in keys(var.private_ip_allocation) : contains(var.reserved_peering_ranges, range)], false) == false
      error_message = "the private_ip_allocation is not in reserved_peering_ranges"
    }

Steps to reproduce

  1. tofu plan

Important Factoids

No response

References

No response