diff --git a/google/resource_compute_backend_service.go b/google/resource_compute_backend_service.go index e735d83a..41de49bf 100644 --- a/google/resource_compute_backend_service.go +++ b/google/resource_compute_backend_service.go @@ -15,6 +15,7 @@ package google import ( + "bytes" "fmt" "log" "reflect" @@ -22,11 +23,142 @@ import ( "time" "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "google.golang.org/api/compute/v1" ) +func resourceGoogleComputeBackendServiceBackendHash(v interface{}) int { + if v == nil { + return 0 + } + + 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) + buf.WriteString(fmt.Sprintf("%s-", m["group"].(string))) + } else { + buf.WriteString(fmt.Sprintf("%s-", group)) + } + + 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 { + 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 { + 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()) +} + func resourceComputeBackendService() *schema.Resource { return &schema.Resource{ Create: resourceComputeBackendServiceCreate, @@ -116,6 +248,11 @@ func resourceComputeBackendService() *schema.Resource { }, }, }, + "signed_url_cache_max_age_sec": { + Type: schema.TypeInt, + Optional: true, + Default: 3600, + }, }, }, }, @@ -156,6 +293,12 @@ func resourceComputeBackendService() *schema.Resource { }, }, }, + "load_balancing_scheme": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"INTERNAL", "EXTERNAL", ""}, false), + Default: "EXTERNAL", + }, "port_name": { Type: schema.TypeString, Computed: true, @@ -311,6 +454,12 @@ func resourceComputeBackendServiceCreate(d *schema.ResourceData, meta interface{ } else if v, ok := d.GetOkExists("iap"); ok || !reflect.DeepEqual(v, iapProp) { obj["iap"] = iapProp } + loadBalancingSchemeProp, err := expandComputeBackendServiceLoadBalancingScheme(d.Get("load_balancing_scheme"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("load_balancing_scheme"); !isEmptyValue(reflect.ValueOf(loadBalancingSchemeProp)) && (ok || !reflect.DeepEqual(v, loadBalancingSchemeProp)) { + obj["loadBalancingScheme"] = loadBalancingSchemeProp + } nameProp, err := expandComputeBackendServiceName(d.Get("name"), d, config) if err != nil { return err @@ -475,6 +624,9 @@ func resourceComputeBackendServiceRead(d *schema.ResourceData, meta interface{}) if err := d.Set("iap", flattenComputeBackendServiceIap(res["iap"], d)); err != nil { return fmt.Errorf("Error reading BackendService: %s", err) } + if err := d.Set("load_balancing_scheme", flattenComputeBackendServiceLoadBalancingScheme(res["loadBalancingScheme"], 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) } @@ -558,6 +710,12 @@ func resourceComputeBackendServiceUpdate(d *schema.ResourceData, meta interface{ } else if v, ok := d.GetOkExists("iap"); ok || !reflect.DeepEqual(v, iapProp) { obj["iap"] = iapProp } + loadBalancingSchemeProp, err := expandComputeBackendServiceLoadBalancingScheme(d.Get("load_balancing_scheme"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("load_balancing_scheme"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, loadBalancingSchemeProp)) { + obj["loadBalancingScheme"] = loadBalancingSchemeProp + } nameProp, err := expandComputeBackendServiceName(d.Get("name"), d, config) if err != nil { return err @@ -808,6 +966,8 @@ func flattenComputeBackendServiceCdnPolicy(v interface{}, d *schema.ResourceData transformed := make(map[string]interface{}) transformed["cache_key_policy"] = flattenComputeBackendServiceCdnPolicyCacheKeyPolicy(original["cacheKeyPolicy"], d) + transformed["signed_url_cache_max_age_sec"] = + flattenComputeBackendServiceCdnPolicySignedUrlCacheMaxAgeSec(original["signedUrlCacheMaxAgeSec"], d) return []interface{}{transformed} } func flattenComputeBackendServiceCdnPolicyCacheKeyPolicy(v interface{}, d *schema.ResourceData) interface{} { @@ -857,6 +1017,16 @@ func flattenComputeBackendServiceCdnPolicyCacheKeyPolicyQueryStringWhitelist(v i return schema.NewSet(schema.HashString, v.([]interface{})) } +func flattenComputeBackendServiceCdnPolicySignedUrlCacheMaxAgeSec(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 flattenComputeBackendServiceConnectionDrainingConnection_draining_timeout_sec(v interface{}, d *schema.ResourceData) interface{} { // Handles the string fixed64 format if strVal, ok := v.(string); ok { @@ -919,6 +1089,10 @@ func flattenComputeBackendServiceIapOauth2ClientSecretSha256(v interface{}, d *s return v } +func flattenComputeBackendServiceLoadBalancingScheme(v interface{}, d *schema.ResourceData) interface{} { + return v +} + func flattenComputeBackendServiceName(v interface{}, d *schema.ResourceData) interface{} { return v } @@ -1088,6 +1262,13 @@ func expandComputeBackendServiceCdnPolicy(v interface{}, d TerraformResourceData transformed["cacheKeyPolicy"] = transformedCacheKeyPolicy } + transformedSignedUrlCacheMaxAgeSec, err := expandComputeBackendServiceCdnPolicySignedUrlCacheMaxAgeSec(original["signed_url_cache_max_age_sec"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSignedUrlCacheMaxAgeSec); val.IsValid() && !isEmptyValue(val) { + transformed["signedUrlCacheMaxAgeSec"] = transformedSignedUrlCacheMaxAgeSec + } + return transformed, nil } @@ -1160,6 +1341,10 @@ func expandComputeBackendServiceCdnPolicyCacheKeyPolicyQueryStringWhitelist(v in return v, nil } +func expandComputeBackendServiceCdnPolicySignedUrlCacheMaxAgeSec(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + 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. @@ -1239,6 +1424,10 @@ func expandComputeBackendServiceIapOauth2ClientSecretSha256(v interface{}, d Ter return v, nil } +func expandComputeBackendServiceLoadBalancingScheme(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + func expandComputeBackendServiceName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { return v, nil } diff --git a/google/resource_compute_backend_service_migrate.go b/google/resource_compute_backend_service_migrate.go deleted file mode 100644 index 037df2e1..00000000 --- a/google/resource_compute_backend_service_migrate.go +++ /dev/null @@ -1,222 +0,0 @@ -package google - -import ( - "fmt" - "log" - "strconv" - "strings" - - "bytes" - "github.com/hashicorp/terraform/helper/hashcode" - "github.com/hashicorp/terraform/terraform" -) - -func resourceComputeBackendServiceMigrateState( - v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { - if is.Empty() { - log.Println("[DEBUG] Empty InstanceState; nothing to migrate.") - return is, nil - } - - switch v { - case 0: - log.Println("[INFO] Found Compute Backend Service State v0; migrating to v1") - is, err := migrateBackendServiceStateV0toV1(is) - if err != nil { - return is, err - } - return is, nil - default: - return is, fmt.Errorf("Unexpected schema version: %d", v) - } -} - -func migrateBackendServiceStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) { - log.Printf("[DEBUG] Attributes before migration: %#v", is.Attributes) - - oldHashToValue := map[string]map[string]interface{}{} - for k, v := range is.Attributes { - if !strings.HasPrefix(k, "backend.") || k == "backend.#" { - continue - } - - // Key is now of the form backend.%d.%s - kParts := strings.Split(k, ".") - - // Sanity check: two parts should be there and should be a number - badFormat := false - if len(kParts) != 3 { - badFormat = true - } else if _, err := strconv.Atoi(kParts[1]); err != nil { - badFormat = true - } - - if badFormat { - return is, fmt.Errorf("migration error: found backend key in unexpected format: %s", k) - } - - if oldHashToValue[kParts[1]] == nil { - oldHashToValue[kParts[1]] = map[string]interface{}{} - } - oldHashToValue[kParts[1]][kParts[2]] = v - } - - oldHashToNewHash := map[string]int{} - for k, v := range oldHashToValue { - oldHashToNewHash[k] = resourceGoogleComputeBackendServiceBackendHash(v) - } - - values := map[string]string{} - for k, v := range is.Attributes { - if !strings.HasPrefix(k, "backend.") { - continue - } - - if k == "backend.#" { - continue - } - - // Key is now of the form backend.%d.%s - kParts := strings.Split(k, ".") - newKey := fmt.Sprintf("%s.%d.%s", kParts[0], oldHashToNewHash[kParts[1]], kParts[2]) - values[newKey] = v - delete(is.Attributes, k) - } - - for k, v := range values { - is.Attributes[k] = v - } - - log.Printf("[DEBUG] Attributes after migration: %#v", is.Attributes) - return is, nil -} - -func resourceGoogleComputeBackendServiceBackendHash(v interface{}) int { - if v == nil { - return 0 - } - - 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) - buf.WriteString(fmt.Sprintf("%s-", m["group"].(string))) - } else { - buf.WriteString(fmt.Sprintf("%s-", group)) - } - - 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 { - 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 { - 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/resource_compute_backend_service_migrate_test.go b/google/resource_compute_backend_service_migrate_test.go deleted file mode 100644 index 1d162f52..00000000 --- a/google/resource_compute_backend_service_migrate_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package google - -import ( - "testing" - - "github.com/hashicorp/terraform/terraform" -) - -func TestComputeBackendServiceMigrateState(t *testing.T) { - cases := map[string]struct { - StateVersion int - Attributes map[string]string - ExpectedAttributes map[string]string - Meta interface{} - }{ - "v0 to v1": { - StateVersion: 0, - Attributes: map[string]string{ - "backend.#": "1", - "backend.242332812.group": "https://www.googleapis.com/compute/v1/projects/project_name/zones/zone_name/instances/instanceGroups/igName", - "backend.242332812.balancing_mode": "UTILIZATION", - "backend.242332812.max_utilization": "0.8", - }, - ExpectedAttributes: map[string]string{ - "backend.#": "1", - "backend.2573491210.group": "https://www.googleapis.com/compute/v1/projects/project_name/zones/zone_name/instances/instanceGroups/igName", - "backend.2573491210.balancing_mode": "UTILIZATION", - "backend.2573491210.max_utilization": "0.8", - }, - Meta: &Config{}, - }, - } - - for tn, tc := range cases { - is := &terraform.InstanceState{ - ID: "i-abc123", - Attributes: tc.Attributes, - } - is, err := resourceComputeBackendServiceMigrateState( - tc.StateVersion, is, tc.Meta) - - if err != nil { - t.Fatalf("bad: %s, err: %#v", tn, err) - } - - for k, v := range tc.ExpectedAttributes { - if is.Attributes[k] != v { - t.Fatalf( - "bad: %s\n\n expected: %#v -> %#v\n got: %#v -> %#v\n in: %#v", - tn, k, v, k, is.Attributes[k], is.Attributes) - } - } - - for k, v := range is.Attributes { - if tc.ExpectedAttributes[k] != v { - t.Fatalf( - "bad: %s\n\n expected: %#v -> %#v\n got: %#v -> %#v\n in: %#v", - tn, k, tc.ExpectedAttributes[k], k, v, is.Attributes) - } - } - } -} - -func TestComputeBackendServiceMigrateState_empty(t *testing.T) { - cases := map[string]struct { - StateVersion int - }{ - "v0": { - StateVersion: 0, - }, - } - - for tn, tc := range cases { - var is *terraform.InstanceState - var meta *Config - - // should handle nil - is, err := resourceComputeBackendServiceMigrateState(tc.StateVersion, is, meta) - - if err != nil { - t.Fatalf("bad %s, err: %#v", tn, err) - } - if is != nil { - t.Fatalf("bad %s, expected nil instancestate, got: %#v", tn, is) - } - - // should handle non-nil but empty - is = &terraform.InstanceState{} - is, err = resourceComputeBackendServiceMigrateState(tc.StateVersion, is, meta) - - if err != nil { - t.Fatalf("bad %s, err: %#v", tn, err) - } - } -} diff --git a/website/docs/r/compute_backend_service.html.markdown b/website/docs/r/compute_backend_service.html.markdown index 0bb31112..32b8426d 100644 --- a/website/docs/r/compute_backend_service.html.markdown +++ b/website/docs/r/compute_backend_service.html.markdown @@ -110,6 +110,13 @@ The following arguments are supported: (Optional) Settings for enabling Cloud Identity Aware Proxy Structure is documented below. +* `load_balancing_scheme` - + (Optional) + Indicates whether the backend service will be used with internal or + external load balancing. A backend service created for one type of + load balancing cannot be used with the other. One of `INTERNAL` or + `EXTERNAL`. Defaults to `EXTERNAL`. + * `port_name` - (Optional) Name of backend port. The same name should appear in the instance @@ -228,6 +235,18 @@ The `cdn_policy` block supports: (Optional) The CacheKeyPolicy for this CdnPolicy. Structure is documented below. +* `signed_url_cache_max_age_sec` - + (Optional) + Maximum number of seconds the response to a signed URL request + will be considered fresh, defaults to 1hr (3600s). After this + time period, the response will be revalidated before + being served. + When serving responses to signed URL requests, Cloud CDN will + internally behave as though all responses from this backend had a + "Cache-Control: public, max-age=[TTL]" header, regardless of any + existing Cache-Control header. The actual headers served in + responses will not be altered. + The `cache_key_policy` block supports: