diff --git a/templates/terraform/constants/backend_service.go.erb b/templates/terraform/constants/backend_service.go.erb index bc3c5b2a1792..848291ad31b1 100644 --- a/templates/terraform/constants/backend_service.go.erb +++ b/templates/terraform/constants/backend_service.go.erb @@ -12,6 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -%> +// Whether the backend is a global or regional NEG +func isNegBackend(backend map[string]interface{}) bool { + backendGroup, ok := backend["group"] + if !ok { + return false + } + + match, err := regexp.MatchString("(?:global|regions/[^/]+)/networkEndpointGroups", backendGroup.(string)) + if err != nil { + // should not happen as long as the regexp pattern compiled correctly + return false + } + return match +} + func resourceGoogleComputeBackendServiceBackendHash(v interface{}) int { if v == nil { return 0 @@ -111,6 +126,16 @@ func resourceGoogleComputeBackendServiceBackendHash(v interface{}) int { // the hash function doesn't return something else. buf.WriteString(fmt.Sprintf("%f-", v.(float64))) } + if v, ok := m["max_utilization"]; ok && !isNegBackend(m) { + if v == nil { + v = 0.0 + } + + // floats can't be added to the hash with %v as the other values are because + // %v and %f are not equivalent strings so this must remain as a float so that + // the hash function doesn't return something else. + buf.WriteString(fmt.Sprintf("%f-", v.(float64))) + } // This is in region backend service, but not in backend service. Should be a no-op // if it's not present. diff --git a/templates/terraform/encoders/backend_service.go.erb b/templates/terraform/encoders/backend_service.go.erb index a9e3ec9f2181..68f383c27f95 100644 --- a/templates/terraform/encoders/backend_service.go.erb +++ b/templates/terraform/encoders/backend_service.go.erb @@ -39,17 +39,9 @@ if !ok { backends := backendsRaw.([]interface{}) for _, backendRaw := range backends { backend := backendRaw.(map[string]interface{}) - backendGroup, ok := backend["group"] - if !ok { - continue - } - match, err := regexp.MatchString("(?:global|regions/[^/]+)/networkEndpointGroups", backendGroup.(string)) - if err != nil { - return nil, err - } - if match { - // Remove `max_utilization` from any backend that belongs to a serverless NEG. This field + if isNegBackend(backend) { + // Remove `max_utilization` from any backend that belongs to an NEG. This field // has a default value and causes API validation errors backend["maxUtilization"] = nil } diff --git a/third_party/terraform/tests/resource_compute_backend_service_test.go.erb b/third_party/terraform/tests/resource_compute_backend_service_test.go.erb index 0873439b77e4..572f0af5ad14 100644 --- a/third_party/terraform/tests/resource_compute_backend_service_test.go.erb +++ b/third_party/terraform/tests/resource_compute_backend_service_test.go.erb @@ -78,6 +78,44 @@ func TestAccComputeBackendService_withBackend(t *testing.T) { }) } +func TestAccComputeBackendService_withBackendAndMaxUtilization(t *testing.T) { + serviceName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + igName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + itName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + checkName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroyProducer(t), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withBackend( + serviceName, igName, itName, checkName, 10), + }, + resource.TestStep{ + ResourceName: "google_compute_backend_service.lipsum", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + Config: testAccComputeBackendService_withBackendAndMaxUtilization( + serviceName, igName, itName, checkName, 10), + PlanOnly: true, + ExpectNonEmptyPlan: true, + }, + resource.TestStep{ + Config: testAccComputeBackendService_withBackendAndMaxUtilization( + serviceName, igName, itName, checkName, 10), + }, + resource.TestStep{ + ResourceName: "google_compute_backend_service.lipsum", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccComputeBackendService_withBackendAndIAP(t *testing.T) { serviceName := fmt.Sprintf("tf-test-%s", randString(t, 10)) igName := fmt.Sprintf("tf-test-%s", randString(t, 10)) @@ -941,6 +979,64 @@ resource "google_compute_http_health_check" "default" { `, serviceName, timeout, igName, itName, checkName) } +func testAccComputeBackendService_withBackendAndMaxUtilization( + serviceName, igName, itName, checkName string, timeout int64) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_backend_service" "lipsum" { + name = "%s" + description = "Hello World 1234" + port_name = "http" + protocol = "HTTP" + timeout_sec = %v + + backend { + group = google_compute_instance_group_manager.foobar.instance_group + max_utilization = 1.0 + } + + health_checks = [google_compute_http_health_check.default.self_link] +} + +resource "google_compute_instance_group_manager" "foobar" { + name = "%s" + version { + instance_template = google_compute_instance_template.foobar.self_link + name = "primary" + } + base_instance_name = "foobar" + zone = "us-central1-f" + target_size = 1 +} + +resource "google_compute_instance_template" "foobar" { + name = "%s" + machine_type = "e2-medium" + + network_interface { + network = "default" + } + + disk { + source_image = data.google_compute_image.my_image.self_link + auto_delete = true + boot = true + } +} + +resource "google_compute_http_health_check" "default" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, timeout, igName, itName, checkName) +} + func testAccComputeBackendService_withBackendAndIAP( serviceName, igName, itName, checkName string, timeout int64) string { return fmt.Sprintf(`