From f370e022664d6c48e12d5ae8b0e1f0b4116ae92b Mon Sep 17 00:00:00 2001 From: The Magician Date: Tue, 2 Apr 2019 14:32:15 -0700 Subject: [PATCH] Generate BackendService in Terraform (#569) /cc @rileykarson --- google-beta/provider.go | 1 - google-beta/provider_compute_gen.go | 1 + .../resource_compute_backend_service.go | 1281 +++++++++++++---- ..._compute_backend_service_generated_test.go | 90 ++ ...esource_compute_backend_service_migrate.go | 91 +- .../resource_compute_backend_service_test.go | 20 +- ...resource_compute_region_backend_service.go | 61 + .../r/compute_backend_service.html.markdown | 399 +++-- 8 files changed, 1483 insertions(+), 461 deletions(-) create mode 100644 google-beta/resource_compute_backend_service_generated_test.go diff --git a/google-beta/provider.go b/google-beta/provider.go index 5547e218136..4e0c6f7aca1 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -171,7 +171,6 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_cloudiot_registry": resourceCloudIoTRegistry(), "google_composer_environment": resourceComposerEnvironment(), "google_compute_attached_disk": resourceComputeAttachedDisk(), - "google_compute_backend_service": resourceComputeBackendService(), "google_compute_global_forwarding_rule": resourceComputeGlobalForwardingRule(), "google_compute_instance": resourceComputeInstance(), "google_compute_instance_from_template": resourceComputeInstanceFromTemplate(), diff --git a/google-beta/provider_compute_gen.go b/google-beta/provider_compute_gen.go index 4e85673457b..fa141f2f406 100644 --- a/google-beta/provider_compute_gen.go +++ b/google-beta/provider_compute_gen.go @@ -21,6 +21,7 @@ var GeneratedComputeResourcesMap = map[string]*schema.Resource{ "google_compute_autoscaler": resourceComputeAutoscaler(), "google_compute_backend_bucket": resourceComputeBackendBucket(), "google_compute_backend_bucket_signed_url_key": resourceComputeBackendBucketSignedUrlKey(), + "google_compute_backend_service": resourceComputeBackendService(), "google_compute_disk": resourceComputeDisk(), "google_compute_firewall": resourceComputeFirewall(), "google_compute_forwarding_rule": resourceComputeForwardingRule(), diff --git a/google-beta/resource_compute_backend_service.go b/google-beta/resource_compute_backend_service.go index 60ecc04ef17..0e8d8ada269 100644 --- a/google-beta/resource_compute_backend_service.go +++ b/google-beta/resource_compute_backend_service.go @@ -1,13 +1,30 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + package google import ( - "errors" "fmt" "log" + "reflect" + "strconv" + "time" "github.com/hashicorp/errwrap" "github.com/hashicorp/terraform/helper/schema" - computeBeta "google.golang.org/api/compute/v0.beta" + "github.com/hashicorp/terraform/helper/validation" + "google.golang.org/api/compute/v1" ) func resourceComputeBackendService() *schema.Resource { @@ -16,44 +33,49 @@ func resourceComputeBackendService() *schema.Resource { Read: resourceComputeBackendServiceRead, Update: resourceComputeBackendServiceUpdate, Delete: resourceComputeBackendServiceDelete, + Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + State: resourceComputeBackendServiceImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(240 * time.Second), + Update: schema.DefaultTimeout(240 * time.Second), + Delete: schema.DefaultTimeout(240 * time.Second), }, + SchemaVersion: 1, Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validateGCPName, - }, - "health_checks": { Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: selfLinkRelativePathHash, Required: true, MinItems: 1, MaxItems: 1, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: selfLinkRelativePathHash, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, }, - "affinity_cookie_ttl_sec": { Type: schema.TypeInt, Optional: true, }, - "backend": { Type: schema.TypeSet, Optional: true, - Set: resourceGoogleComputeBackendServiceBackendHash, Elem: computeBackendServiceBackendSchema(), + Set: resourceGoogleComputeBackendServiceBackendHash, }, - "cdn_policy": { Type: schema.TypeList, - Optional: true, Computed: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -76,16 +98,20 @@ func resourceComputeBackendService() *schema.Resource { Optional: true, }, "query_string_blacklist": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - ConflictsWith: []string{"cdn_policy.0.cache_key_policy.query_string_whitelist"}, + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, }, "query_string_whitelist": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - ConflictsWith: []string{"cdn_policy.0.cache_key_policy.query_string_blacklist"}, + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, }, }, }, @@ -93,7 +119,6 @@ func resourceComputeBackendService() *schema.Resource { }, }, }, - "connection_draining_timeout_sec": { Type: schema.TypeInt, Optional: true, @@ -103,26 +128,19 @@ func resourceComputeBackendService() *schema.Resource { "custom_request_headers": { Type: schema.TypeSet, Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, }, - "description": { Type: schema.TypeString, Optional: true, }, - "enable_cdn": { Type: schema.TypeBool, Optional: true, - Default: false, }, - - "fingerprint": { - Type: schema.TypeString, - Computed: true, - }, - "iap": { Type: schema.TypeList, Optional: true, @@ -146,51 +164,47 @@ func resourceComputeBackendService() *schema.Resource { }, }, }, - "port_name": { Type: schema.TypeString, - Optional: true, Computed: true, - }, - - "protocol": { - Type: schema.TypeString, Optional: true, - Computed: true, }, - - "region": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Removed: "region has been removed as it was never used. For internal load balancing, use google_compute_region_backend_service", + "protocol": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"HTTP", "HTTPS", "TCP", "SSL", ""}, false), }, - "security_policy": { Type: schema.TypeString, Optional: true, DiffSuppressFunc: compareSelfLinkOrResourceName, }, - "session_affinity": { - Type: schema.TypeString, - Optional: true, - Computed: true, + Type: schema.TypeString, + Computed: true, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"NONE", "CLIENT_IP", "GENERATED_COOKIE", "CLIENT_IP_PROTO", "CLIENT_IP_PORT_PROTO", ""}, false), }, - "timeout_sec": { Type: schema.TypeInt, + Computed: true, Optional: true, + }, + "creation_timestamp": { + Type: schema.TypeString, + Computed: true, + }, + "fingerprint": { + Type: schema.TypeString, Computed: true, }, - "project": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "self_link": { Type: schema.TypeString, Computed: true, @@ -202,39 +216,40 @@ func resourceComputeBackendService() *schema.Resource { func computeBackendServiceBackendSchema() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ - "group": { - Type: schema.TypeString, - Optional: true, - DiffSuppressFunc: compareSelfLinkRelativePaths, - }, "balancing_mode": { - Type: schema.TypeString, - Optional: true, - Default: "UTILIZATION", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"UTILIZATION", "RATE", "CONNECTION", ""}, false), + Default: "UTILIZATION", }, "capacity_scaler": { Type: schema.TypeFloat, Optional: true, - Default: 1, + Default: 1.0, }, "description": { Type: schema.TypeString, Optional: true, }, - "max_rate": { + "group": { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: compareSelfLinkRelativePaths, + }, + "max_connections": { Type: schema.TypeInt, Optional: true, }, - "max_rate_per_instance": { - Type: schema.TypeFloat, + "max_connections_per_instance": { + Type: schema.TypeInt, Optional: true, }, - "max_connections": { + "max_rate": { Type: schema.TypeInt, Optional: true, }, - "max_connections_per_instance": { - Type: schema.TypeInt, + "max_rate_per_instance": { + Type: schema.TypeFloat, Optional: true, }, "max_utilization": { @@ -249,49 +264,163 @@ func computeBackendServiceBackendSchema() *schema.Resource { func resourceComputeBackendServiceCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - service, err := expandBackendService(d) + obj := make(map[string]interface{}) + affinityCookieTtlSecProp, err := expandComputeBackendServiceAffinityCookieTtlSec(d.Get("affinity_cookie_ttl_sec"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("affinity_cookie_ttl_sec"); !isEmptyValue(reflect.ValueOf(affinityCookieTtlSecProp)) && (ok || !reflect.DeepEqual(v, affinityCookieTtlSecProp)) { + obj["affinityCookieTtlSec"] = affinityCookieTtlSecProp + } + backendsProp, err := expandComputeBackendServiceBackend(d.Get("backend"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("backend"); !isEmptyValue(reflect.ValueOf(backendsProp)) && (ok || !reflect.DeepEqual(v, backendsProp)) { + obj["backends"] = backendsProp + } + cdnPolicyProp, err := expandComputeBackendServiceCdnPolicy(d.Get("cdn_policy"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("cdn_policy"); !isEmptyValue(reflect.ValueOf(cdnPolicyProp)) && (ok || !reflect.DeepEqual(v, cdnPolicyProp)) { + obj["cdnPolicy"] = cdnPolicyProp + } + connectionDrainingProp, err := expandComputeBackendServiceConnectionDraining(d, config) + if err != nil { + return err + } else if !isEmptyValue(reflect.ValueOf(connectionDrainingProp)) { + obj["connectionDraining"] = connectionDrainingProp + } + customRequestHeadersProp, err := expandComputeBackendServiceCustomRequestHeaders(d.Get("custom_request_headers"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("custom_request_headers"); !isEmptyValue(reflect.ValueOf(customRequestHeadersProp)) && (ok || !reflect.DeepEqual(v, customRequestHeadersProp)) { + obj["customRequestHeaders"] = customRequestHeadersProp + } + fingerprintProp, err := expandComputeBackendServiceFingerprint(d.Get("fingerprint"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("fingerprint"); !isEmptyValue(reflect.ValueOf(fingerprintProp)) && (ok || !reflect.DeepEqual(v, fingerprintProp)) { + obj["fingerprint"] = fingerprintProp + } + descriptionProp, err := expandComputeBackendServiceDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + enableCDNProp, err := expandComputeBackendServiceEnableCDN(d.Get("enable_cdn"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("enable_cdn"); !isEmptyValue(reflect.ValueOf(enableCDNProp)) && (ok || !reflect.DeepEqual(v, enableCDNProp)) { + obj["enableCDN"] = enableCDNProp + } + healthChecksProp, err := expandComputeBackendServiceHealthChecks(d.Get("health_checks"), d, config) if err != nil { return err + } else if v, ok := d.GetOkExists("health_checks"); !isEmptyValue(reflect.ValueOf(healthChecksProp)) && (ok || !reflect.DeepEqual(v, healthChecksProp)) { + obj["healthChecks"] = healthChecksProp + } + iapProp, err := expandComputeBackendServiceIap(d.Get("iap"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("iap"); ok || !reflect.DeepEqual(v, iapProp) { + obj["iap"] = iapProp + } + nameProp, err := expandComputeBackendServiceName(d.Get("name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + portNameProp, err := expandComputeBackendServicePortName(d.Get("port_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("port_name"); !isEmptyValue(reflect.ValueOf(portNameProp)) && (ok || !reflect.DeepEqual(v, portNameProp)) { + obj["portName"] = portNameProp + } + protocolProp, err := expandComputeBackendServiceProtocol(d.Get("protocol"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("protocol"); !isEmptyValue(reflect.ValueOf(protocolProp)) && (ok || !reflect.DeepEqual(v, protocolProp)) { + obj["protocol"] = protocolProp + } + securityPolicyProp, err := expandComputeBackendServiceSecurityPolicy(d.Get("security_policy"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("security_policy"); !isEmptyValue(reflect.ValueOf(securityPolicyProp)) && (ok || !reflect.DeepEqual(v, securityPolicyProp)) { + obj["securityPolicy"] = securityPolicyProp + } + sessionAffinityProp, err := expandComputeBackendServiceSessionAffinity(d.Get("session_affinity"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("session_affinity"); !isEmptyValue(reflect.ValueOf(sessionAffinityProp)) && (ok || !reflect.DeepEqual(v, sessionAffinityProp)) { + obj["sessionAffinity"] = sessionAffinityProp + } + timeoutSecProp, err := expandComputeBackendServiceTimeoutSec(d.Get("timeout_sec"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("timeout_sec"); !isEmptyValue(reflect.ValueOf(timeoutSecProp)) && (ok || !reflect.DeepEqual(v, timeoutSecProp)) { + obj["timeoutSec"] = timeoutSecProp } - project, err := getProject(d, config) + obj, err = resourceComputeBackendServiceEncoder(d, meta, obj) if err != nil { return err } - log.Printf("[DEBUG] Creating new Backend Service: %#v", service) - op, err := config.clientComputeBeta.BackendServices.Insert( - project, service).Do() + url, err := replaceVars(d, config, "https://www.googleapis.com/compute/beta/projects/{{project}}/global/backendServices") if err != nil { - return fmt.Errorf("Error creating backend service: %s", err) + return err } - log.Printf("[DEBUG] Waiting for new backend service, operation: %#v", op) + log.Printf("[DEBUG] Creating new BackendService: %#v", obj) + res, err := sendRequestWithTimeout(config, "POST", url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating BackendService: %s", err) + } // Store the ID now - d.SetId(service.Name) + id, err := replaceVars(d, config, "{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + project, err := getProject(d, config) + if err != nil { + return err + } + op := &compute.Operation{} + err = Convert(res, op) + if err != nil { + return err + } + + waitErr := computeOperationWaitTime( + config.clientCompute, op, project, "Creating BackendService", + int(d.Timeout(schema.TimeoutCreate).Minutes())) - // Wait for the operation to complete - waitErr := computeSharedOperationWait(config.clientCompute, op, project, "Creating Backend Service") if waitErr != nil { // The resource didn't actually create d.SetId("") - return waitErr + return fmt.Errorf("Error waiting to create BackendService: %s", waitErr) } + log.Printf("[DEBUG] Finished creating BackendService %q: %#v", d.Id(), res) + + // security_policy isn't set by Create / Update if v, ok := d.GetOk("security_policy"); ok { pol, err := ParseSecurityPolicyFieldValue(v.(string), d, config) if err != nil { return errwrap.Wrapf("Error parsing Backend Service security policy: {{err}}", err) } - op, err := config.clientComputeBeta.BackendServices.SetSecurityPolicy( - project, service.Name, &computeBeta.SecurityPolicyReference{ + op, err := config.clientCompute.BackendServices.SetSecurityPolicy( + project, obj["name"].(string), &compute.SecurityPolicyReference{ SecurityPolicy: pol.RelativeLink(), }).Do() if err != nil { return errwrap.Wrapf("Error setting Backend Service security policy: {{err}}", err) } - waitErr := computeSharedOperationWait(config.clientCompute, op, project, "Adding Backend Service Security Policy") + waitErr := computeSharedOperationWait(config.clientCompute, op, project, "Setting Backend Service Security Policy") if waitErr != nil { return waitErr } @@ -303,348 +432,904 @@ func resourceComputeBackendServiceCreate(d *schema.ResourceData, meta interface{ func resourceComputeBackendServiceRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - project, err := getProject(d, config) + url, err := replaceVars(d, config, "https://www.googleapis.com/compute/beta/projects/{{project}}/global/backendServices/{{name}}") if err != nil { return err } - service, err := config.clientComputeBeta.BackendServices.Get(project, d.Id()).Do() + res, err := sendRequest(config, "GET", url, nil) if err != nil { - return handleNotFoundError(err, d, fmt.Sprintf("Backend Service %q", d.Get("name").(string))) + return handleNotFoundError(err, d, fmt.Sprintf("ComputeBackendService %q", d.Id())) } - d.Set("name", service.Name) - d.Set("description", service.Description) - d.Set("enable_cdn", service.EnableCDN) - d.Set("port_name", service.PortName) - d.Set("protocol", service.Protocol) - d.Set("session_affinity", service.SessionAffinity) - d.Set("affinity_cookie_ttl_sec", service.AffinityCookieTtlSec) - d.Set("timeout_sec", service.TimeoutSec) - d.Set("fingerprint", service.Fingerprint) - d.Set("self_link", ConvertSelfLinkToV1(service.SelfLink)) - d.Set("backend", flattenBackends(service.Backends)) - d.Set("connection_draining_timeout_sec", service.ConnectionDraining.DrainingTimeoutSec) - d.Set("iap", flattenIap(d, service.Iap)) - d.Set("project", project) - guardedHealthChecks := make([]string, len(service.HealthChecks)) - for i, v := range service.HealthChecks { - guardedHealthChecks[i] = ConvertSelfLinkToV1(v) + res, err = resourceComputeBackendServiceDecoder(d, meta, res) + if err != nil { + return err } - d.Set("health_checks", guardedHealthChecks) - if err := d.Set("cdn_policy", flattenCdnPolicy(service.CdnPolicy)); err != nil { + project, err := getProject(d, config) + if err != nil { return err } - d.Set("security_policy", service.SecurityPolicy) + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + + if err := d.Set("affinity_cookie_ttl_sec", flattenComputeBackendServiceAffinityCookieTtlSec(res["affinityCookieTtlSec"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("backend", flattenComputeBackendServiceBackend(res["backends"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("cdn_policy", flattenComputeBackendServiceCdnPolicy(res["cdnPolicy"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if v, ok := res["connectionDraining"].(map[string]interface{}); res["connectionDraining"] != nil && ok { + if err := d.Set("connection_draining_timeout_sec", flattenComputeBackendServiceConnectionDrainingConnection_draining_timeout_sec(v["drainingTimeoutSec"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + } else { + d.Set("connection_draining_timeout_sec", nil) + } + if err := d.Set("creation_timestamp", flattenComputeBackendServiceCreationTimestamp(res["creationTimestamp"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("custom_request_headers", flattenComputeBackendServiceCustomRequestHeaders(res["customRequestHeaders"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("fingerprint", flattenComputeBackendServiceFingerprint(res["fingerprint"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("description", flattenComputeBackendServiceDescription(res["description"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("enable_cdn", flattenComputeBackendServiceEnableCDN(res["enableCDN"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("health_checks", flattenComputeBackendServiceHealthChecks(res["healthChecks"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("iap", flattenComputeBackendServiceIap(res["iap"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("name", flattenComputeBackendServiceName(res["name"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("port_name", flattenComputeBackendServicePortName(res["portName"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("protocol", flattenComputeBackendServiceProtocol(res["protocol"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("security_policy", flattenComputeBackendServiceSecurityPolicy(res["securityPolicy"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("session_affinity", flattenComputeBackendServiceSessionAffinity(res["sessionAffinity"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("timeout_sec", flattenComputeBackendServiceTimeoutSec(res["timeoutSec"], d)); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } + if err := d.Set("self_link", ConvertSelfLinkToV1(res["selfLink"].(string))); err != nil { + return fmt.Errorf("Error reading BackendService: %s", err) + } - d.Set("custom_request_headers", service.CustomRequestHeaders) return nil } func resourceComputeBackendServiceUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - service, err := expandBackendService(d) + obj := make(map[string]interface{}) + affinityCookieTtlSecProp, err := expandComputeBackendServiceAffinityCookieTtlSec(d.Get("affinity_cookie_ttl_sec"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("affinity_cookie_ttl_sec"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, affinityCookieTtlSecProp)) { + obj["affinityCookieTtlSec"] = affinityCookieTtlSecProp + } + backendsProp, err := expandComputeBackendServiceBackend(d.Get("backend"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("backend"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, backendsProp)) { + obj["backends"] = backendsProp + } + cdnPolicyProp, err := expandComputeBackendServiceCdnPolicy(d.Get("cdn_policy"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("cdn_policy"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, cdnPolicyProp)) { + obj["cdnPolicy"] = cdnPolicyProp + } + connectionDrainingProp, err := expandComputeBackendServiceConnectionDraining(d, config) + if err != nil { + return err + } else if !isEmptyValue(reflect.ValueOf(connectionDrainingProp)) { + obj["connectionDraining"] = connectionDrainingProp + } + customRequestHeadersProp, err := expandComputeBackendServiceCustomRequestHeaders(d.Get("custom_request_headers"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("custom_request_headers"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, customRequestHeadersProp)) { + obj["customRequestHeaders"] = customRequestHeadersProp + } + fingerprintProp, err := expandComputeBackendServiceFingerprint(d.Get("fingerprint"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("fingerprint"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, fingerprintProp)) { + obj["fingerprint"] = fingerprintProp + } + descriptionProp, err := expandComputeBackendServiceDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + enableCDNProp, err := expandComputeBackendServiceEnableCDN(d.Get("enable_cdn"), d, config) if err != nil { return err + } else if v, ok := d.GetOkExists("enable_cdn"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, enableCDNProp)) { + obj["enableCDN"] = enableCDNProp + } + healthChecksProp, err := expandComputeBackendServiceHealthChecks(d.Get("health_checks"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("health_checks"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, healthChecksProp)) { + obj["healthChecks"] = healthChecksProp + } + iapProp, err := expandComputeBackendServiceIap(d.Get("iap"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("iap"); ok || !reflect.DeepEqual(v, iapProp) { + obj["iap"] = iapProp + } + nameProp, err := expandComputeBackendServiceName(d.Get("name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + portNameProp, err := expandComputeBackendServicePortName(d.Get("port_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("port_name"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, portNameProp)) { + obj["portName"] = portNameProp + } + protocolProp, err := expandComputeBackendServiceProtocol(d.Get("protocol"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("protocol"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, protocolProp)) { + obj["protocol"] = protocolProp + } + securityPolicyProp, err := expandComputeBackendServiceSecurityPolicy(d.Get("security_policy"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("security_policy"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, securityPolicyProp)) { + obj["securityPolicy"] = securityPolicyProp + } + sessionAffinityProp, err := expandComputeBackendServiceSessionAffinity(d.Get("session_affinity"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("session_affinity"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, sessionAffinityProp)) { + obj["sessionAffinity"] = sessionAffinityProp + } + timeoutSecProp, err := expandComputeBackendServiceTimeoutSec(d.Get("timeout_sec"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("timeout_sec"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, timeoutSecProp)) { + obj["timeoutSec"] = timeoutSecProp } - service.Fingerprint = d.Get("fingerprint").(string) - project, err := getProject(d, config) + obj, err = resourceComputeBackendServiceEncoder(d, meta, obj) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "https://www.googleapis.com/compute/beta/projects/{{project}}/global/backendServices/{{name}}") if err != nil { return err } - log.Printf("[DEBUG] Updating existing Backend Service %q: %#v", d.Id(), service) - op, err := config.clientComputeBeta.BackendServices.Update( - project, d.Id(), service).Do() + log.Printf("[DEBUG] Updating BackendService %q: %#v", d.Id(), obj) + res, err := sendRequestWithTimeout(config, "PUT", url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating BackendService %q: %s", d.Id(), err) + } + + project, err := getProject(d, config) + if err != nil { + return err + } + op := &compute.Operation{} + err = Convert(res, op) if err != nil { - return fmt.Errorf("Error updating backend service: %s", err) + return err } - err = computeSharedOperationWait(config.clientCompute, op, project, "Updating Backend Service") + err = computeOperationWaitTime( + config.clientCompute, op, project, "Updating BackendService", + int(d.Timeout(schema.TimeoutUpdate).Minutes())) + if err != nil { return err } - if d.HasChange("security_policy") { - pol, err := ParseSecurityPolicyFieldValue(d.Get("security_policy").(string), d, config) + // security_policy isn't set by Create / Update + if v, ok := d.GetOk("security_policy"); ok { + pol, err := ParseSecurityPolicyFieldValue(v.(string), d, config) if err != nil { - return err + return errwrap.Wrapf("Error parsing Backend Service security policy: {{err}}", err) } - op, err := config.clientComputeBeta.BackendServices.SetSecurityPolicy( - project, service.Name, &computeBeta.SecurityPolicyReference{ + op, err := config.clientCompute.BackendServices.SetSecurityPolicy( + project, obj["name"].(string), &compute.SecurityPolicyReference{ SecurityPolicy: pol.RelativeLink(), }).Do() if err != nil { - return err + return errwrap.Wrapf("Error setting Backend Service security policy: {{err}}", err) } - waitErr := computeSharedOperationWait(config.clientCompute, op, project, "Adding Backend Service Security Policy") + waitErr := computeSharedOperationWait(config.clientCompute, op, project, "Setting Backend Service Security Policy") if waitErr != nil { return waitErr } } - return resourceComputeBackendServiceRead(d, meta) } func resourceComputeBackendServiceDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - project, err := getProject(d, config) + url, err := replaceVars(d, config, "https://www.googleapis.com/compute/beta/projects/{{project}}/global/backendServices/{{name}}") if err != nil { return err } - log.Printf("[DEBUG] Deleting backend service %s", d.Id()) - op, err := config.clientCompute.BackendServices.Delete( - project, d.Id()).Do() + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting BackendService %q", d.Id()) + res, err := sendRequestWithTimeout(config, "DELETE", url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "BackendService") + } + + project, err := getProject(d, config) if err != nil { - return fmt.Errorf("Error deleting backend service: %s", err) + return err + } + op := &compute.Operation{} + err = Convert(res, op) + if err != nil { + return err } - err = computeOperationWait(config.clientCompute, op, project, "Deleting Backend Service") + err = computeOperationWaitTime( + config.clientCompute, op, project, "Deleting BackendService", + int(d.Timeout(schema.TimeoutDelete).Minutes())) + if err != nil { return err } - d.SetId("") + log.Printf("[DEBUG] Finished deleting BackendService %q: %#v", d.Id(), res) return nil } -func expandIap(configured []interface{}) *computeBeta.BackendServiceIAP { - if len(configured) == 0 || configured[0] == nil { +func resourceComputeBackendServiceImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{"projects/(?P[^/]+)/global/backendServices/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)"}, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenComputeBackendServiceAffinityCookieTtlSec(v interface{}, d *schema.ResourceData) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +} + +func flattenComputeBackendServiceBackend(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := schema.NewSet(resourceGoogleComputeBackendServiceBackendHash, []interface{}{}) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed.Add(map[string]interface{}{ + "balancing_mode": flattenComputeBackendServiceBackendBalancingMode(original["balancingMode"], d), + "capacity_scaler": flattenComputeBackendServiceBackendCapacityScaler(original["capacityScaler"], d), + "description": flattenComputeBackendServiceBackendDescription(original["description"], d), + "group": flattenComputeBackendServiceBackendGroup(original["group"], d), + "max_connections": flattenComputeBackendServiceBackendMaxConnections(original["maxConnections"], d), + "max_connections_per_instance": flattenComputeBackendServiceBackendMaxConnectionsPerInstance(original["maxConnectionsPerInstance"], d), + "max_rate": flattenComputeBackendServiceBackendMaxRate(original["maxRate"], d), + "max_rate_per_instance": flattenComputeBackendServiceBackendMaxRatePerInstance(original["maxRatePerInstance"], d), + "max_utilization": flattenComputeBackendServiceBackendMaxUtilization(original["maxUtilization"], d), + }) + } + return transformed +} +func flattenComputeBackendServiceBackendBalancingMode(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceBackendCapacityScaler(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceBackendDescription(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceBackendGroup(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return v + } + return ConvertSelfLinkToV1(v.(string)) +} + +func flattenComputeBackendServiceBackendMaxConnections(v interface{}, d *schema.ResourceData) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +} + +func flattenComputeBackendServiceBackendMaxConnectionsPerInstance(v interface{}, d *schema.ResourceData) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +} + +func flattenComputeBackendServiceBackendMaxRate(v interface{}, d *schema.ResourceData) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +} + +func flattenComputeBackendServiceBackendMaxRatePerInstance(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceBackendMaxUtilization(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceCdnPolicy(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["cache_key_policy"] = + flattenComputeBackendServiceCdnPolicyCacheKeyPolicy(original["cacheKeyPolicy"], d) + return []interface{}{transformed} +} +func flattenComputeBackendServiceCdnPolicyCacheKeyPolicy(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { return nil } + transformed := make(map[string]interface{}) + transformed["include_host"] = + flattenComputeBackendServiceCdnPolicyCacheKeyPolicyIncludeHost(original["includeHost"], d) + transformed["include_protocol"] = + flattenComputeBackendServiceCdnPolicyCacheKeyPolicyIncludeProtocol(original["includeProtocol"], d) + transformed["include_query_string"] = + flattenComputeBackendServiceCdnPolicyCacheKeyPolicyIncludeQueryString(original["includeQueryString"], d) + transformed["query_string_blacklist"] = + flattenComputeBackendServiceCdnPolicyCacheKeyPolicyQueryStringBlacklist(original["queryStringBlacklist"], d) + transformed["query_string_whitelist"] = + flattenComputeBackendServiceCdnPolicyCacheKeyPolicyQueryStringWhitelist(original["queryStringWhitelist"], d) + return []interface{}{transformed} +} +func flattenComputeBackendServiceCdnPolicyCacheKeyPolicyIncludeHost(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceCdnPolicyCacheKeyPolicyIncludeProtocol(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceCdnPolicyCacheKeyPolicyIncludeQueryString(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceCdnPolicyCacheKeyPolicyQueryStringBlacklist(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return v + } + return schema.NewSet(schema.HashString, v.([]interface{})) +} + +func flattenComputeBackendServiceCdnPolicyCacheKeyPolicyQueryStringWhitelist(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return v + } + return schema.NewSet(schema.HashString, v.([]interface{})) +} + +func flattenComputeBackendServiceConnectionDrainingConnection_draining_timeout_sec(v interface{}, d *schema.ResourceData) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +} + +func flattenComputeBackendServiceCreationTimestamp(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceCustomRequestHeaders(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return v + } + return schema.NewSet(schema.HashString, v.([]interface{})) +} + +func flattenComputeBackendServiceFingerprint(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceDescription(v interface{}, d *schema.ResourceData) interface{} { + return v +} - data := configured[0].(map[string]interface{}) - return &computeBeta.BackendServiceIAP{ - Enabled: true, - Oauth2ClientId: data["oauth2_client_id"].(string), - Oauth2ClientSecret: data["oauth2_client_secret"].(string), - ForceSendFields: []string{"Enabled", "Oauth2ClientId", "Oauth2ClientSecret"}, +func flattenComputeBackendServiceEnableCDN(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceHealthChecks(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return v } + return convertAndMapStringArr(v.([]interface{}), ConvertSelfLinkToV1) } -func flattenIap(d *schema.ResourceData, iap *computeBeta.BackendServiceIAP) []map[string]interface{} { - result := make([]map[string]interface{}, 0, 1) - if iap == nil || !iap.Enabled { - return result +func flattenComputeBackendServiceIap(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil } + transformed := make(map[string]interface{}) + transformed["oauth2_client_id"] = + flattenComputeBackendServiceIapOauth2ClientId(original["oauth2ClientId"], d) + transformed["oauth2_client_secret"] = + flattenComputeBackendServiceIapOauth2ClientSecret(original["oauth2ClientSecret"], d) + transformed["oauth2_client_secret_sha256"] = + flattenComputeBackendServiceIapOauth2ClientSecretSha256(original["oauth2ClientSecretSha256"], d) + return []interface{}{transformed} +} +func flattenComputeBackendServiceIapOauth2ClientId(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceIapOauth2ClientSecret(v interface{}, d *schema.ResourceData) interface{} { + return d.Get("iap.0.oauth2_client_secret") +} + +func flattenComputeBackendServiceIapOauth2ClientSecretSha256(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServicePortName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceProtocol(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenComputeBackendServiceSecurityPolicy(v interface{}, d *schema.ResourceData) interface{} { + return v +} - return append(result, map[string]interface{}{ - "oauth2_client_id": iap.Oauth2ClientId, - "oauth2_client_secret": d.Get("iap.0.oauth2_client_secret"), - "oauth2_client_secret_sha256": iap.Oauth2ClientSecretSha256, - }) +func flattenComputeBackendServiceSessionAffinity(v interface{}, d *schema.ResourceData) interface{} { + return v } -func expandBackends(configured []interface{}) ([]*computeBeta.Backend, error) { - backends := make([]*computeBeta.Backend, 0, len(configured)) +func flattenComputeBackendServiceTimeoutSec(v interface{}, d *schema.ResourceData) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +} - for _, raw := range configured { - data := raw.(map[string]interface{}) +func expandComputeBackendServiceAffinityCookieTtlSec(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} - g, ok := data["group"] - if !ok { - return nil, errors.New("google_compute_backend_service.backend.group must be set") +func expandComputeBackendServiceBackend(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + v = v.(*schema.Set).List() + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) - b := computeBeta.Backend{ - Group: g.(string), + transformedBalancingMode, err := expandComputeBackendServiceBackendBalancingMode(original["balancing_mode"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedBalancingMode); val.IsValid() && !isEmptyValue(val) { + transformed["balancingMode"] = transformedBalancingMode } - if v, ok := data["balancing_mode"]; ok { - b.BalancingMode = v.(string) + transformedCapacityScaler, err := expandComputeBackendServiceBackendCapacityScaler(original["capacity_scaler"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCapacityScaler); val.IsValid() && !isEmptyValue(val) { + transformed["capacityScaler"] = transformedCapacityScaler } - if v, ok := data["capacity_scaler"]; ok { - b.CapacityScaler = v.(float64) - b.ForceSendFields = append(b.ForceSendFields, "CapacityScaler") + + transformedDescription, err := expandComputeBackendServiceBackendDescription(original["description"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDescription); val.IsValid() && !isEmptyValue(val) { + transformed["description"] = transformedDescription } - if v, ok := data["description"]; ok { - b.Description = v.(string) + + transformedGroup, err := expandComputeBackendServiceBackendGroup(original["group"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedGroup); val.IsValid() && !isEmptyValue(val) { + transformed["group"] = transformedGroup } - if v, ok := data["max_rate"]; ok { - b.MaxRate = int64(v.(int)) - if b.MaxRate == 0 { - b.NullFields = append(b.NullFields, "MaxRate") - } + + transformedMaxConnections, err := expandComputeBackendServiceBackendMaxConnections(original["max_connections"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMaxConnections); val.IsValid() && !isEmptyValue(val) { + transformed["maxConnections"] = transformedMaxConnections } - if v, ok := data["max_rate_per_instance"]; ok { - b.MaxRatePerInstance = v.(float64) - if b.MaxRatePerInstance == 0 { - b.NullFields = append(b.NullFields, "MaxRatePerInstance") - } + + transformedMaxConnectionsPerInstance, err := expandComputeBackendServiceBackendMaxConnectionsPerInstance(original["max_connections_per_instance"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMaxConnectionsPerInstance); val.IsValid() && !isEmptyValue(val) { + transformed["maxConnectionsPerInstance"] = transformedMaxConnectionsPerInstance } - if v, ok := data["max_connections"]; ok { - b.MaxConnections = int64(v.(int)) - if b.MaxConnections == 0 { - b.NullFields = append(b.NullFields, "MaxConnections") - } + + transformedMaxRate, err := expandComputeBackendServiceBackendMaxRate(original["max_rate"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMaxRate); val.IsValid() && !isEmptyValue(val) { + transformed["maxRate"] = transformedMaxRate } - if v, ok := data["max_connections_per_instance"]; ok { - b.MaxConnectionsPerInstance = int64(v.(int)) - if b.MaxConnectionsPerInstance == 0 { - b.NullFields = append(b.NullFields, "MaxConnectionsPerInstance") - } + + transformedMaxRatePerInstance, err := expandComputeBackendServiceBackendMaxRatePerInstance(original["max_rate_per_instance"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMaxRatePerInstance); val.IsValid() && !isEmptyValue(val) { + transformed["maxRatePerInstance"] = transformedMaxRatePerInstance } - if v, ok := data["max_utilization"]; ok { - b.MaxUtilization = v.(float64) - b.ForceSendFields = append(b.ForceSendFields, "MaxUtilization") + + transformedMaxUtilization, err := expandComputeBackendServiceBackendMaxUtilization(original["max_utilization"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMaxUtilization); val.IsValid() && !isEmptyValue(val) { + transformed["maxUtilization"] = transformedMaxUtilization } - backends = append(backends, &b) + req = append(req, transformed) } + return req, nil +} - return backends, nil +func expandComputeBackendServiceBackendBalancingMode(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil } -func flattenBackends(backends []*computeBeta.Backend) []map[string]interface{} { - result := make([]map[string]interface{}, 0, len(backends)) +func expandComputeBackendServiceBackendCapacityScaler(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} - for _, b := range backends { - data := make(map[string]interface{}) +func expandComputeBackendServiceBackendDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} - data["balancing_mode"] = b.BalancingMode - data["capacity_scaler"] = b.CapacityScaler - data["description"] = b.Description - data["group"] = ConvertSelfLinkToV1(b.Group) - data["max_rate"] = b.MaxRate - data["max_rate_per_instance"] = b.MaxRatePerInstance - data["max_connections"] = b.MaxConnections - data["max_connections_per_instance"] = b.MaxConnectionsPerInstance - data["max_utilization"] = b.MaxUtilization - result = append(result, data) +func expandComputeBackendServiceBackendGroup(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + f, err := parseZonalFieldValue("instanceGroups", v.(string), "project", "zone", d, config, true) + if err != nil { + return nil, fmt.Errorf("Invalid value for group: %s", err) } + return f.RelativeLink(), nil +} - return result +func expandComputeBackendServiceBackendMaxConnections(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil } -func expandBackendService(d *schema.ResourceData) (*computeBeta.BackendService, error) { - hc := d.Get("health_checks").(*schema.Set).List() - healthChecks := make([]string, 0, len(hc)) - for _, v := range hc { - healthChecks = append(healthChecks, v.(string)) - } +func expandComputeBackendServiceBackendMaxConnectionsPerInstance(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} - // The IAP service is enabled and disabled by adding or removing - // the IAP configuration block (and providing the client id - // and secret). We are force sending the three required API fields - // to enable/disable IAP at all times here, and relying on Golang's - // type defaults to enable or disable IAP in the existence or absence - // of the block, instead of checking if the block exists, zeroing out - // fields, etc. - service := &computeBeta.BackendService{ - Name: d.Get("name").(string), - HealthChecks: healthChecks, - Iap: &computeBeta.BackendServiceIAP{ - ForceSendFields: []string{"Enabled", "Oauth2ClientId", "Oauth2ClientSecret"}, - }, - CdnPolicy: &computeBeta.BackendServiceCdnPolicy{ - CacheKeyPolicy: &computeBeta.CacheKeyPolicy{ - ForceSendFields: []string{"IncludeProtocol", "IncludeHost", "IncludeQueryString", "QueryStringWhitelist", "QueryStringBlacklist"}, - }, - }, - CustomRequestHeaders: convertStringSet(d.Get("custom_request_headers").(*schema.Set)), +func expandComputeBackendServiceBackendMaxRate(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceBackendMaxRatePerInstance(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceBackendMaxUtilization(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceCdnPolicy(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) - if v, ok := d.GetOk("iap"); ok { - service.Iap = expandIap(v.([]interface{})) + transformedCacheKeyPolicy, err := expandComputeBackendServiceCdnPolicyCacheKeyPolicy(original["cache_key_policy"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCacheKeyPolicy); val.IsValid() && !isEmptyValue(val) { + transformed["cacheKeyPolicy"] = transformedCacheKeyPolicy } - var err error - if v, ok := d.GetOk("backend"); ok { - service.Backends, err = expandBackends(v.(*schema.Set).List()) - if err != nil { - return nil, err - } + return transformed, nil +} + +func expandComputeBackendServiceCdnPolicyCacheKeyPolicy(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) - if v, ok := d.GetOk("description"); ok { - service.Description = v.(string) + transformedIncludeHost, err := expandComputeBackendServiceCdnPolicyCacheKeyPolicyIncludeHost(original["include_host"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedIncludeHost); val.IsValid() && !isEmptyValue(val) { + transformed["includeHost"] = transformedIncludeHost } - if v, ok := d.GetOk("port_name"); ok { - service.PortName = v.(string) + transformedIncludeProtocol, err := expandComputeBackendServiceCdnPolicyCacheKeyPolicyIncludeProtocol(original["include_protocol"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedIncludeProtocol); val.IsValid() && !isEmptyValue(val) { + transformed["includeProtocol"] = transformedIncludeProtocol } - if v, ok := d.GetOk("protocol"); ok { - service.Protocol = v.(string) + transformedIncludeQueryString, err := expandComputeBackendServiceCdnPolicyCacheKeyPolicyIncludeQueryString(original["include_query_string"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedIncludeQueryString); val.IsValid() && !isEmptyValue(val) { + transformed["includeQueryString"] = transformedIncludeQueryString } - if v, ok := d.GetOk("session_affinity"); ok { - service.SessionAffinity = v.(string) + transformedQueryStringBlacklist, err := expandComputeBackendServiceCdnPolicyCacheKeyPolicyQueryStringBlacklist(original["query_string_blacklist"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedQueryStringBlacklist); val.IsValid() && !isEmptyValue(val) { + transformed["queryStringBlacklist"] = transformedQueryStringBlacklist } - if v, ok := d.GetOk("affinity_cookie_ttl_sec"); ok { - service.AffinityCookieTtlSec = int64(v.(int)) + transformedQueryStringWhitelist, err := expandComputeBackendServiceCdnPolicyCacheKeyPolicyQueryStringWhitelist(original["query_string_whitelist"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedQueryStringWhitelist); val.IsValid() && !isEmptyValue(val) { + transformed["queryStringWhitelist"] = transformedQueryStringWhitelist } - if v, ok := d.GetOk("timeout_sec"); ok { - service.TimeoutSec = int64(v.(int)) + return transformed, nil +} + +func expandComputeBackendServiceCdnPolicyCacheKeyPolicyIncludeHost(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceCdnPolicyCacheKeyPolicyIncludeProtocol(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceCdnPolicyCacheKeyPolicyIncludeQueryString(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceCdnPolicyCacheKeyPolicyQueryStringBlacklist(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + v = v.(*schema.Set).List() + return v, nil +} + +func expandComputeBackendServiceCdnPolicyCacheKeyPolicyQueryStringWhitelist(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + v = v.(*schema.Set).List() + return v, nil +} + +func expandComputeBackendServiceConnectionDraining(d TerraformResourceData, config *Config) (interface{}, error) { + transformed := make(map[string]interface{}) + // Note that nesting flattened objects won't work because we don't handle them properly here. + transformedConnection_draining_timeout_sec, err := expandComputeBackendServiceConnectionDrainingConnection_draining_timeout_sec(d.Get("connection_draining_timeout_sec"), d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedConnection_draining_timeout_sec); val.IsValid() && !isEmptyValue(val) { + transformed["drainingTimeoutSec"] = transformedConnection_draining_timeout_sec } - if v, ok := d.GetOk("enable_cdn"); ok { - service.EnableCDN = v.(bool) + return transformed, nil +} + +func expandComputeBackendServiceConnectionDrainingConnection_draining_timeout_sec(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceCustomRequestHeaders(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + v = v.(*schema.Set).List() + return v, nil +} + +func expandComputeBackendServiceFingerprint(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceEnableCDN(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceHealthChecks(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + v = v.(*schema.Set).List() + return v, nil +} + +func expandComputeBackendServiceIap(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) - connectionDrainingTimeoutSec := d.Get("connection_draining_timeout_sec") - connectionDraining := &computeBeta.ConnectionDraining{ - DrainingTimeoutSec: int64(connectionDrainingTimeoutSec.(int)), + transformedOauth2ClientId, err := expandComputeBackendServiceIapOauth2ClientId(original["oauth2_client_id"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedOauth2ClientId); val.IsValid() && !isEmptyValue(val) { + transformed["oauth2ClientId"] = transformedOauth2ClientId } - service.ConnectionDraining = connectionDraining + transformedOauth2ClientSecret, err := expandComputeBackendServiceIapOauth2ClientSecret(original["oauth2_client_secret"], d, config) + if err != nil { + return nil, err + } else { + transformed["oauth2ClientSecret"] = transformedOauth2ClientSecret + } - if v, ok := d.GetOk("cdn_policy"); ok { - c := expandCdnPolicy(v.([]interface{})) - if c != nil { - service.CdnPolicy = c - } + transformedOauth2ClientSecretSha256, err := expandComputeBackendServiceIapOauth2ClientSecretSha256(original["oauth2_client_secret_sha256"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedOauth2ClientSecretSha256); val.IsValid() && !isEmptyValue(val) { + transformed["oauth2ClientSecretSha256"] = transformedOauth2ClientSecretSha256 } - return service, nil + return transformed, nil } -func expandCdnPolicy(configured []interface{}) *computeBeta.BackendServiceCdnPolicy { - if len(configured) == 0 || configured[0] == nil { - return nil - } +func expandComputeBackendServiceIapOauth2ClientId(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} - data := configured[0].(map[string]interface{}) - ckp := data["cache_key_policy"].([]interface{}) - if len(ckp) == 0 { - return nil - } - ckpData := ckp[0].(map[string]interface{}) +func expandComputeBackendServiceIapOauth2ClientSecret(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} - return &computeBeta.BackendServiceCdnPolicy{ - CacheKeyPolicy: &computeBeta.CacheKeyPolicy{ - IncludeHost: ckpData["include_host"].(bool), - IncludeProtocol: ckpData["include_protocol"].(bool), - IncludeQueryString: ckpData["include_query_string"].(bool), - QueryStringBlacklist: convertStringSet(ckpData["query_string_blacklist"].(*schema.Set)), - QueryStringWhitelist: convertStringSet(ckpData["query_string_whitelist"].(*schema.Set)), - ForceSendFields: []string{"IncludeProtocol", "IncludeHost", "IncludeQueryString", "QueryStringWhitelist", "QueryStringBlacklist"}, - }, +func expandComputeBackendServiceIapOauth2ClientSecretSha256(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServicePortName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceProtocol(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceSecurityPolicy(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceSessionAffinity(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeBackendServiceTimeoutSec(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func resourceComputeBackendServiceEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { + // The BackendService API's Update / PUT API is badly formed and behaves like + // a PATCH field for at least IAP. When sent a `null` `iap` field, the API + // doesn't disable an existing field. To work around this, we need to emulate + // the old Terraform behaviour of always sending the block (at both update and + // create), and force sending each subfield as empty when the block isn't + // present in config. + + iapVal := obj["iap"] + if iapVal == nil { + data := map[string]interface{}{} + data["enabled"] = false + data["oauth2ClientId"] = "" + data["oauth2ClientSecret"] = "" + obj["iap"] = data + } else { + iap := iapVal.(map[string]interface{}) + iap["enabled"] = true + obj["iap"] = iap } + + return obj, nil } -func flattenCdnPolicy(pol *computeBeta.BackendServiceCdnPolicy) []map[string]interface{} { - result := []map[string]interface{}{} - if pol == nil || pol.CacheKeyPolicy == nil { - return result +func resourceComputeBackendServiceDecoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { + // We need to pretend IAP isn't there if it's disabled for Terraform to maintain + // BC behaviour with the handwritten resource. + v, ok := res["iap"] + m := v.(map[string]interface{}) + if ok && m["enabled"] == false { + delete(res, "iap") } - return append(result, map[string]interface{}{ - "cache_key_policy": []map[string]interface{}{ - { - "include_host": pol.CacheKeyPolicy.IncludeHost, - "include_protocol": pol.CacheKeyPolicy.IncludeProtocol, - "include_query_string": pol.CacheKeyPolicy.IncludeQueryString, - "query_string_blacklist": schema.NewSet(schema.HashString, convertStringArrToInterface(pol.CacheKeyPolicy.QueryStringBlacklist)), - "query_string_whitelist": schema.NewSet(schema.HashString, convertStringArrToInterface(pol.CacheKeyPolicy.QueryStringWhitelist)), - }, - }, - }) + return res, nil } diff --git a/google-beta/resource_compute_backend_service_generated_test.go b/google-beta/resource_compute_backend_service_generated_test.go new file mode 100644 index 00000000000..1d77ee2234d --- /dev/null +++ b/google-beta/resource_compute_backend_service_generated_test.go @@ -0,0 +1,90 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeBackendService_backendServiceBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(10), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeBackendService_backendServiceBasicExample(context), + }, + { + ResourceName: "google_compute_backend_service.default", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccComputeBackendService_backendServiceBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_compute_backend_service" "default" { + name = "backend-service-%{random_suffix}" + health_checks = ["${google_compute_http_health_check.default.self_link}"] +} + +resource "google_compute_http_health_check" "default" { + name = "health-check-%{random_suffix}" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, context) +} + +func testAccCheckComputeBackendServiceDestroy(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_backend_service" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := testAccProvider.Meta().(*Config) + + url, err := replaceVarsForTest(rs, "https://www.googleapis.com/compute/beta/projects/{{project}}/global/backendServices/{{name}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", url, nil) + if err == nil { + return fmt.Errorf("ComputeBackendService still exists at %s", url) + } + } + + return nil +} diff --git a/google-beta/resource_compute_backend_service_migrate.go b/google-beta/resource_compute_backend_service_migrate.go index 386c5cfd6bf..037df2e1ddf 100644 --- a/google-beta/resource_compute_backend_service_migrate.go +++ b/google-beta/resource_compute_backend_service_migrate.go @@ -98,6 +98,7 @@ func resourceGoogleComputeBackendServiceBackendHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) + log.Printf("[DEBUG] hashing %v", m) if group, err := getRelativePath(m["group"].(string)); err != nil { log.Printf("[WARN] Error on retrieving relative path of instance group: %s", err) @@ -107,29 +108,115 @@ func resourceGoogleComputeBackendServiceBackendHash(v interface{}) int { } if v, ok := m["balancing_mode"]; ok { + if v == nil { + v = "" + } + buf.WriteString(fmt.Sprintf("%s-", v.(string))) } if v, ok := m["capacity_scaler"]; ok { + if v == nil { + v = 0.0 + } + buf.WriteString(fmt.Sprintf("%f-", v.(float64))) } if v, ok := m["description"]; ok { + if v == nil { + v = "" + } + + log.Printf("[DEBUG] writing description %s", v) buf.WriteString(fmt.Sprintf("%s-", v.(string))) } if v, ok := m["max_rate"]; ok { + if v == nil { + v = 0 + } + buf.WriteString(fmt.Sprintf("%d-", int64(v.(int)))) } if v, ok := m["max_rate_per_instance"]; ok { + if v == nil { + v = 0.0 + } + buf.WriteString(fmt.Sprintf("%f-", v.(float64))) } if v, ok := m["max_connections"]; ok { - buf.WriteString(fmt.Sprintf("%d-", int64(v.(int)))) + if v == nil { + v = 0 + } + + switch v := v.(type) { + case float64: + // The Golang JSON library can't tell int values apart from floats, + // because MM doesn't give fields strong types. Since another value + // in this block was a real float, it assumed this was a float too. + // It's not. + // Note that math.Round in Go is from float64 -> float64, so it will + // be a noop. int(floatVal) truncates extra parts, so if the float64 + // representation of an int falls below the real value we'll have + // the wrong value. eg if 3 was represented as 2.999999, that would + // convert to 2. So we add 0.5, ensuring that we'll truncate to the + // correct value. This wouldn't remain true if we were far enough + // from 0 that we were off by > 0.5, but no float conversion *could* + // work correctly in that case. 53-bit floating types as the only + // numeric type was not a good idea, thanks Javascript. + var vInt int + if v < 0 { + vInt = int(v - 0.5) + } else { + vInt = int(v + 0.5) + } + + log.Printf("[DEBUG] writing float value %f as integer value %v", v, vInt) + buf.WriteString(fmt.Sprintf("%d-", vInt)) + default: + buf.WriteString(fmt.Sprintf("%d-", int64(v.(int)))) + } } if v, ok := m["max_connections_per_instance"]; ok { - buf.WriteString(fmt.Sprintf("%d-", int64(v.(int)))) + if v == nil { + v = 0 + } + + switch v := v.(type) { + case float64: + // The Golang JSON library can't tell int values apart from floats, + // because MM doesn't give fields strong types. Since another value + // in this block was a real float, it assumed this was a float too. + // It's not. + // Note that math.Round in Go is from float64 -> float64, so it will + // be a noop. int(floatVal) truncates extra parts, so if the float64 + // representation of an int falls below the real value we'll have + // the wrong value. eg if 3 was represented as 2.999999, that would + // convert to 2. So we add 0.5, ensuring that we'll truncate to the + // correct value. This wouldn't remain true if we were far enough + // from 0 that we were off by > 0.5, but no float conversion *could* + // work correctly in that case. 53-bit floating types as the only + // numeric type was not a good idea, thanks Javascript. + var vInt int + if v < 0 { + vInt = int(v - 0.5) + } else { + vInt = int(v + 0.5) + } + + log.Printf("[DEBUG] writing float value %f as integer value %v", v, vInt) + buf.WriteString(fmt.Sprintf("%d-", vInt)) + default: + buf.WriteString(fmt.Sprintf("%d-", int64(v.(int)))) + } } if v, ok := m["max_rate_per_instance"]; ok { + if v == nil { + v = 0.0 + } + buf.WriteString(fmt.Sprintf("%f-", v.(float64))) } + log.Printf("[DEBUG] computed hash value of %v from %v", hashcode.String(buf.String()), buf.String()) return hashcode.String(buf.String()) } diff --git a/google-beta/resource_compute_backend_service_test.go b/google-beta/resource_compute_backend_service_test.go index 62e4124e671..1106cda2bde 100644 --- a/google-beta/resource_compute_backend_service_test.go +++ b/google-beta/resource_compute_backend_service_test.go @@ -327,24 +327,6 @@ func TestAccComputeBackendService_withSecurityPolicy(t *testing.T) { }) } -func testAccCheckComputeBackendServiceDestroy(s *terraform.State) error { - config := testAccProvider.Meta().(*Config) - - for _, rs := range s.RootModule().Resources { - if rs.Type != "google_compute_backend_service" { - continue - } - - _, err := config.clientCompute.BackendServices.Get( - config.Project, rs.Primary.ID).Do() - if err == nil { - return fmt.Errorf("Backend service %s still exists", rs.Primary.ID) - } - } - - return nil -} - func testAccCheckComputeBackendServiceExists(n string, svc *compute.BackendService) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -398,7 +380,7 @@ func testAccCheckComputeBackendServiceExistsWithIAP(n string, svc *compute.Backe } if found.Iap == nil || found.Iap.Enabled == false { - return fmt.Errorf("IAP not found or not enabled.") + return fmt.Errorf("IAP not found or not enabled. Saw %v", found.Iap) } *svc = *found diff --git a/google-beta/resource_compute_region_backend_service.go b/google-beta/resource_compute_region_backend_service.go index b0393db8089..9c7a7768570 100644 --- a/google-beta/resource_compute_region_backend_service.go +++ b/google-beta/resource_compute_region_backend_service.go @@ -2,6 +2,7 @@ package google import ( "bytes" + "errors" "fmt" "log" @@ -359,3 +360,63 @@ func flattenRegionBackends(backends []*compute.Backend) []map[string]interface{} return result } + +func expandBackends(configured []interface{}) ([]*computeBeta.Backend, error) { + backends := make([]*computeBeta.Backend, 0, len(configured)) + + for _, raw := range configured { + data := raw.(map[string]interface{}) + + g, ok := data["group"] + if !ok { + return nil, errors.New("google_compute_backend_service.backend.group must be set") + } + + b := computeBeta.Backend{ + Group: g.(string), + } + + if v, ok := data["balancing_mode"]; ok { + b.BalancingMode = v.(string) + } + if v, ok := data["capacity_scaler"]; ok { + b.CapacityScaler = v.(float64) + b.ForceSendFields = append(b.ForceSendFields, "CapacityScaler") + } + if v, ok := data["description"]; ok { + b.Description = v.(string) + } + if v, ok := data["max_rate"]; ok { + b.MaxRate = int64(v.(int)) + if b.MaxRate == 0 { + b.NullFields = append(b.NullFields, "MaxRate") + } + } + if v, ok := data["max_rate_per_instance"]; ok { + b.MaxRatePerInstance = v.(float64) + if b.MaxRatePerInstance == 0 { + b.NullFields = append(b.NullFields, "MaxRatePerInstance") + } + } + if v, ok := data["max_connections"]; ok { + b.MaxConnections = int64(v.(int)) + if b.MaxConnections == 0 { + b.NullFields = append(b.NullFields, "MaxConnections") + } + } + if v, ok := data["max_connections_per_instance"]; ok { + b.MaxConnectionsPerInstance = int64(v.(int)) + if b.MaxConnectionsPerInstance == 0 { + b.NullFields = append(b.NullFields, "MaxConnectionsPerInstance") + } + } + if v, ok := data["max_utilization"]; ok { + b.MaxUtilization = v.(float64) + b.ForceSendFields = append(b.ForceSendFields, "MaxUtilization") + } + + backends = append(backends, &b) + } + + return backends, nil +} diff --git a/website/docs/r/compute_backend_service.html.markdown b/website/docs/r/compute_backend_service.html.markdown index 54ed5ac59ff..662da5f6d88 100644 --- a/website/docs/r/compute_backend_service.html.markdown +++ b/website/docs/r/compute_backend_service.html.markdown @@ -1,62 +1,48 @@ --- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- layout: "google" page_title: "Google: google_compute_backend_service" sidebar_current: "docs-google-compute-backend-service" description: |- - Creates a Backend Service resource for Google Compute Engine. + Creates a BackendService resource in the specified project using the data + included in the request. --- # google\_compute\_backend\_service -A Backend Service defines a group of virtual machines that will serve traffic for load balancing. For more information -see [the official documentation](https://cloud.google.com/compute/docs/load-balancing/http/backend-service) -and the [API](https://cloud.google.com/compute/docs/reference/latest/backendServices). +Creates a BackendService resource in the specified project using the data +included in the request. -For internal load balancing, use a [google_compute_region_backend_service](/docs/providers/google/r/compute_region_backend_service.html). -## Example Usage -```hcl -resource "google_compute_backend_service" "website" { - name = "my-backend" - description = "Our company website" - port_name = "http" - protocol = "HTTP" - timeout_sec = 10 - enable_cdn = false - - backend { - group = "${google_compute_instance_group_manager.webservers.instance_group}" - } - - health_checks = ["${google_compute_http_health_check.default.self_link}"] -} - -resource "google_compute_instance_group_manager" "webservers" { - name = "my-webservers" - instance_template = "${google_compute_instance_template.webserver.self_link}" - base_instance_name = "webserver" - zone = "us-central1-f" - target_size = 1 -} - -resource "google_compute_instance_template" "webserver" { - name = "standard-webserver" - machine_type = "n1-standard-1" + +## Example Usage - Backend Service Basic - network_interface { - network = "default" - } - disk { - source_image = "debian-cloud/debian-9" - auto_delete = true - boot = true - } +```hcl +resource "google_compute_backend_service" "default" { + name = "backend-service" + health_checks = ["${google_compute_http_health_check.default.self_link}"] } resource "google_compute_http_health_check" "default" { - name = "test" + name = "health-check" request_path = "/" check_interval_sec = 1 timeout_sec = 1 @@ -67,136 +53,267 @@ resource "google_compute_http_health_check" "default" { The following arguments are supported: -* `name` - (Required) The name of the backend service. - -* `health_checks` - (Required) Specifies a list of HTTP/HTTPS health checks - for checking the health of the backend service. Currently at most one health - check can be specified, and a health check is required. - -- - - - -* `backend` - (Optional) The list of backends that serve this BackendService. Structure is documented below. - -* `iap` - (Optional) Specification for the Identity-Aware proxy. Disabled if not specified. Structure is documented below. - -* `cdn_policy` - (Optional) Cloud CDN configuration for this BackendService. Structure is documented below. - -* `connection_draining_timeout_sec` - (Optional) Time for which instance will be drained (not accept new connections, -but still work to finish started ones). Defaults to `300`. - -* `custom_request_headers` - (Optional, [Beta](https://terraform.io/docs/providers/google/provider_versions.html)) Headers that the - HTTP/S load balancer should add to proxied requests. See [guide](https://cloud.google.com/compute/docs/load-balancing/http/backend-service#user-defined-request-headers) for details. - -* `description` - (Optional) The textual description for the backend service. - -* `enable_cdn` - (Optional) Whether or not to enable the Cloud CDN on the backend service. -* `port_name` - (Optional) The name of a service that has been added to an - instance group in this backend. See [related docs](https://cloud.google.com/compute/docs/instance-groups/#specifying_service_endpoints) for details. Defaults to http. +* `health_checks` - + (Required) + The list of URLs to the HttpHealthCheck or HttpsHealthCheck resource + for health checking this BackendService. Currently at most one health + check can be specified, and a health check is required. + For internal load balancing, a URL to a HealthCheck resource must be + specified instead. -* `project` - (Optional) The ID of the project in which the resource belongs. If it - is not provided, the provider project is used. +* `name` - + (Required) + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression `[a-z]([-a-z0-9]*[a-z0-9])?` which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. -* `protocol` - (Optional) The protocol for incoming requests. Defaults to - `HTTP`. -* `security_policy` - (Optional) Name or URI of a - [security policy](https://cloud.google.com/armor/docs/security-policy-concepts) to add to the backend service. +- - - -* `session_affinity` - (Optional) How to distribute load. Options are `NONE` (no - affinity), `CLIENT_IP` (hash of the source/dest addresses / ports), and - `GENERATED_COOKIE` (distribute load using a generated session cookie). -* `affinity_cookie_ttl_sec` - (Optional) Lifetime of cookies in seconds if session_affinity is - `GENERATED_COOKIE`. If set to 0, the cookie is non-persistent and lasts only until the end of - the browser session (or equivalent). The maximum allowed value for TTL is one day. +* `affinity_cookie_ttl_sec` - + (Optional) + Lifetime of cookies in seconds if session_affinity is + GENERATED_COOKIE. If set to 0, the cookie is non-persistent and lasts + only until the end of the browser session (or equivalent). The + maximum allowed value for TTL is one day. + When the load balancing scheme is INTERNAL, this field is not used. + +* `backend` - + (Optional) + The list of backends that serve this BackendService. Structure is documented below. + +* `cdn_policy` - + (Optional) + Cloud CDN configuration for this BackendService. Structure is documented below. + +* `connection_draining_timeout_sec` - + (Optional) + Time for which instance will be drained (not accept new + connections, but still work to finish started). + +* `custom_request_headers` - + (Optional, [Beta](https://terraform.io/docs/providers/google/provider_versions.html)) + Headers that the HTTP/S load balancer should add to proxied + requests. + +* `description` - + (Optional) + An optional description of this resource. + +* `enable_cdn` - + (Optional) + If true, enable Cloud CDN for this BackendService. + When the load balancing scheme is INTERNAL, this field is not used. + +* `iap` - + (Optional) + Settings for enabling Cloud Identity Aware Proxy Structure is documented below. + +* `port_name` - + (Optional) + Name of backend port. The same name should appear in the instance + groups referenced by this service. Required when the load balancing + scheme is EXTERNAL. + When the load balancing scheme is INTERNAL, this field is not used. + +* `protocol` - + (Optional) + The protocol this BackendService uses to communicate with backends. + Possible values are HTTP, HTTPS, TCP, and SSL. The default is HTTP. + For internal load balancing, the possible values are TCP and UDP, and + the default is TCP. + +* `security_policy` - + (Optional) + The security policy associated with this backend service. + +* `session_affinity` - + (Optional) + Type of session affinity to use. The default is NONE. + When the load balancing scheme is EXTERNAL, can be NONE, CLIENT_IP, or + GENERATED_COOKIE. + When the load balancing scheme is INTERNAL, can be NONE, CLIENT_IP, + CLIENT_IP_PROTO, or CLIENT_IP_PORT_PROTO. + When the protocol is UDP, this field is not used. + +* `timeout_sec` - + (Optional) + How many seconds to wait for the backend before considering it a + failed request. Default is 30 seconds. Valid range is [1, 86400]. +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. -* `timeout_sec` - (Optional) The number of secs to wait for a backend to respond - to a request before considering the request failed. Defaults to `30`. The `backend` block supports: -* `group` - (Required) The name or URI of a Compute Engine instance group - (`google_compute_instance_group_manager.xyz.instance_group`) that can - receive traffic. - -* `balancing_mode` - (Optional) Defines the strategy for balancing load. - Defaults to `UTILIZATION` - -* `capacity_scaler` - (Optional) A float in the range [0, 1.0] that scales the - maximum parameters for the group (e.g., max rate). A value of 0.0 will cause - no requests to be sent to the group (i.e., it adds the group in a drained - state). The default is 1.0. - -* `description` - (Optional) Textual description for the backend. - -* `max_rate` - (Optional) Maximum requests per second (RPS) that the group can - handle. - -* `max_rate_per_instance` - (Optional) The maximum per-instance requests per - second (RPS). - -* `max_connections` - (Optional) The max number of simultaneous connections for the - group. Can be used with either CONNECTION or UTILIZATION balancing - modes. For CONNECTION mode, either maxConnections or - maxConnectionsPerInstance must be set. - -* `max_connections_per_instance` - (Optional) The max number of simultaneous connections - that a single backend instance can handle. This is used to calculate - the capacity of the group. Can be used in either CONNECTION or - UTILIZATION balancing modes. For CONNECTION mode, either - maxConnections or maxConnectionsPerInstance must be set. - -* `max_utilization` - (Optional) The target CPU utilization for the group as a - float in the range [0.0, 1.0]. This flag can only be provided when the - balancing mode is `UTILIZATION`. Defaults to `0.8`. +* `balancing_mode` - + (Optional) + Specifies the balancing mode for this backend. + For global HTTP(S) or TCP/SSL load balancing, the default is + UTILIZATION. Valid values are UTILIZATION, RATE (for HTTP(S)) + and CONNECTION (for TCP/SSL). + This cannot be used for internal load balancing. + +* `capacity_scaler` - + (Optional) + A multiplier applied to the group's maximum servicing capacity + (based on UTILIZATION, RATE or CONNECTION). + Default value is 1, which means the group will serve up to 100% + of its configured capacity (depending on balancingMode). A + setting of 0 means the group is completely drained, offering + 0% of its available Capacity. Valid range is [0.0,1.0]. + This cannot be used for internal load balancing. + +* `description` - + (Optional) + An optional description of this resource. + Provide this property when you create the resource. + +* `group` - + (Optional) + This instance group defines the list of instances that serve + traffic. Member virtual machine instances from each instance + group must live in the same zone as the instance group itself. + No two backends in a backend service are allowed to use same + Instance Group resource. + When the BackendService has load balancing scheme INTERNAL, the + instance group must be in a zone within the same region as the + BackendService. + +* `max_connections` - + (Optional) + The max number of simultaneous connections for the group. Can + be used with either CONNECTION or UTILIZATION balancing modes. + For CONNECTION mode, either maxConnections or + maxConnectionsPerInstance must be set. + This cannot be used for internal load balancing. + +* `max_connections_per_instance` - + (Optional) + The max number of simultaneous connections that a single + backend instance can handle. This is used to calculate the + capacity of the group. Can be used in either CONNECTION or + UTILIZATION balancing modes. + For CONNECTION mode, either maxConnections or + maxConnectionsPerInstance must be set. + This cannot be used for internal load balancing. + +* `max_rate` - + (Optional) + The max requests per second (RPS) of the group. + Can be used with either RATE or UTILIZATION balancing modes, + but required if RATE mode. For RATE mode, either maxRate or + maxRatePerInstance must be set. + This cannot be used for internal load balancing. + +* `max_rate_per_instance` - + (Optional) + The max requests per second (RPS) that a single backend + instance can handle. This is used to calculate the capacity of + the group. Can be used in either balancing mode. For RATE mode, + either maxRate or maxRatePerInstance must be set. + This cannot be used for internal load balancing. + +* `max_utilization` - + (Optional) + Used when balancingMode is UTILIZATION. This ratio defines the + CPU utilization target for the group. The default is 0.8. Valid + range is [0.0, 1.0]. + This cannot be used for internal load balancing. The `cdn_policy` block supports: -* `cache_key_policy` - (Optional) The CacheKeyPolicy for this CdnPolicy. - Structure is documented below. +* `cache_key_policy` - + (Optional) + The CacheKeyPolicy for this CdnPolicy. Structure is documented below. + The `cache_key_policy` block supports: -* `include_host` - (Optional) If true, requests to different hosts will be cached separately. +* `include_host` - + (Optional) + If true requests to different hosts will be cached separately. + +* `include_protocol` - + (Optional) + If true, http and https requests will be cached separately. + +* `include_query_string` - + (Optional) + If true, include query string parameters in the cache key + according to query_string_whitelist and + query_string_blacklist. If neither is set, the entire query + string will be included. + If false, the query string will be excluded from the cache + key entirely. + +* `query_string_blacklist` - + (Optional) + Names of query string parameters to exclude in cache keys. + All other parameters will be included. Either specify + query_string_whitelist or query_string_blacklist, not both. + '&' and '=' will be percent encoded and not treated as + delimiters. + +* `query_string_whitelist` - + (Optional) + Names of query string parameters to include in cache keys. + All other parameters will be excluded. Either specify + query_string_whitelist or query_string_blacklist, not both. + '&' and '=' will be percent encoded and not treated as + delimiters. -* `include_protocol` - (Optional) If true, http and https requests will be cached separately. +The `iap` block supports: -* `include_query_string` - (Optional) If true, include query string parameters in the cache key - according to `query_string_whitelist` and `query_string_blacklist`. If neither is set, the entire - query string will be included. If false, the query string will be excluded from the cache key entirely. +* `oauth2_client_id` - + (Required) + OAuth2 Client ID for IAP -* `query_string_blacklist` - (Optional) Names of query string parameters to exclude in cache keys. - All other parameters will be included. Either specify `query_string_whitelist` or - `query_string_blacklist`, not both. '&' and '=' will be percent encoded and not treated as delimiters. +* `oauth2_client_secret` - + (Required) + OAuth2 Client Secret for IAP -* `query_string_whitelist` - (Optional) Names of query string parameters to include in cache keys. - All other parameters will be excluded. Either specify `query_string_whitelist` or - `query_string_blacklist`, not both. '&' and '=' will be percent encoded and not treated as delimiters. +* `oauth2_client_secret_sha256` - + OAuth2 Client Secret SHA-256 for IAP -The `iap` block supports: +## Attributes Reference -* `oauth2_client_id` - (Required) The client ID for use with OAuth 2.0. +In addition to the arguments listed above, the following computed attributes are exported: -* `oauth2_client_secret` - (Required) The client secret for use with OAuth 2.0. -Out of band changes to this field will not be detected by Terraform, and it may -perform spurious no-op updates when imported, or upgraded from pre-`2.0.0`. -## Attributes Reference +* `creation_timestamp` - + Creation timestamp in RFC3339 text format. -In addition to the arguments listed above, the following computed attributes are -exported: +* `fingerprint` - + Fingerprint of this resource. A hash of the contents stored in this + object. This field is used in optimistic locking. +* `self_link` - The URI of the created resource. -* `iap.0.oauth2_client_secret_sha256` - The SHA256 hash of the OAuth 2.0 client secret value. -* `fingerprint` - The fingerprint of the backend service. +## Timeouts -* `self_link` - The URI of the created resource. +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. ## Import -Backend services can be imported using the `name`, e.g. +BackendService can be imported using any of these accepted formats: ``` -$ terraform import google_compute_backend_service.website my-backend +$ terraform import google_compute_backend_service.default projects/{{project}}/global/backendServices/{{name}} +$ terraform import google_compute_backend_service.default {{project}}/{{name}} +$ terraform import google_compute_backend_service.default {{name}} ``` + +-> If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource.