From 6d0c641c89eba1a065bd9f439191c8afd6fe10ca Mon Sep 17 00:00:00 2001 From: The Magician Date: Tue, 15 Sep 2020 10:05:29 -0700 Subject: [PATCH] Added new resource for Target Director with proxyless grpc (#3978) (#7277) * init commit * Added support Traffic Director with proxyless gRPC * added a handwritten update test and misc Co-authored-by: Edward Sun Signed-off-by: Modular Magician Co-authored-by: Edward Sun --- .changelog/3978.txt | 3 + google/provider.go | 5 +- google/resource_compute_target_grpc_proxy.go | 422 ++++++++++++++++++ ...ompute_target_grpc_proxy_generated_test.go | 167 +++++++ ..._compute_target_grpc_proxy_sweeper_test.go | 124 +++++ ...resource_compute_target_grpc_proxy_test.go | 134 ++++++ .../r/compute_target_grpc_proxy.html.markdown | 225 ++++++++++ website/google.erb | 4 + 8 files changed, 1082 insertions(+), 2 deletions(-) create mode 100644 .changelog/3978.txt create mode 100644 google/resource_compute_target_grpc_proxy.go create mode 100644 google/resource_compute_target_grpc_proxy_generated_test.go create mode 100644 google/resource_compute_target_grpc_proxy_sweeper_test.go create mode 100644 google/resource_compute_target_grpc_proxy_test.go create mode 100644 website/docs/r/compute_target_grpc_proxy.html.markdown diff --git a/.changelog/3978.txt b/.changelog/3978.txt new file mode 100644 index 00000000000..42b5a88168c --- /dev/null +++ b/.changelog/3978.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +`google_compute_target_grpc_proxy` +``` diff --git a/google/provider.go b/google/provider.go index 44d2fa3f8e3..201b42e7ce0 100644 --- a/google/provider.go +++ b/google/provider.go @@ -635,9 +635,9 @@ func Provider() terraform.ResourceProvider { return provider } -// Generated resources: 158 +// Generated resources: 159 // Generated IAM resources: 69 -// Total generated resources: 227 +// Total generated resources: 228 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -760,6 +760,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_compute_vpn_gateway": resourceComputeVpnGateway(), "google_compute_url_map": resourceComputeUrlMap(), "google_compute_vpn_tunnel": resourceComputeVpnTunnel(), + "google_compute_target_grpc_proxy": resourceComputeTargetGrpcProxy(), "google_container_analysis_note": resourceContainerAnalysisNote(), "google_container_analysis_occurrence": resourceContainerAnalysisOccurrence(), "google_data_catalog_entry_group": resourceDataCatalogEntryGroup(), diff --git a/google/resource_compute_target_grpc_proxy.go b/google/resource_compute_target_grpc_proxy.go new file mode 100644 index 00000000000..f0a5c3b5a7c --- /dev/null +++ b/google/resource_compute_target_grpc_proxy.go @@ -0,0 +1,422 @@ +// ---------------------------------------------------------------------------- +// +// *** 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" + "log" + "reflect" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceComputeTargetGrpcProxy() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeTargetGrpcProxyCreate, + Read: resourceComputeTargetGrpcProxyRead, + Update: resourceComputeTargetGrpcProxyUpdate, + Delete: resourceComputeTargetGrpcProxyDelete, + + Importer: &schema.ResourceImporter{ + State: resourceComputeTargetGrpcProxyImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(4 * time.Minute), + Update: schema.DefaultTimeout(4 * time.Minute), + Delete: schema.DefaultTimeout(4 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `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.`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `An optional description of this resource.`, + }, + "url_map": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + Description: `URL to the UrlMap resource that defines the mapping from URL to +the BackendService. The protocol field in the BackendService +must be set to GRPC.`, + }, + "validate_for_proxyless": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Description: `If true, indicates that the BackendServices referenced by +the urlMap may be accessed by gRPC applications without using +a sidecar proxy. This will enable configuration checks on urlMap +and its referenced BackendServices to not allow unsupported features. +A gRPC application must use "xds:///" scheme in the target URI +of the service it is connecting to. If false, indicates that the +BackendServices referenced by the urlMap will be accessed by gRPC +applications via a sidecar proxy. In this case, a gRPC application +must not use "xds:///" scheme in the target URI of the service +it is connecting to`, + }, + "creation_timestamp": { + Type: schema.TypeString, + Computed: true, + Description: `Creation timestamp in RFC3339 text format.`, + }, + "fingerprint": { + Type: schema.TypeString, + Computed: true, + Description: `Fingerprint of this resource. A hash of the contents stored in +this object. This field is used in optimistic locking. This field +will be ignored when inserting a TargetGrpcProxy. An up-to-date +fingerprint must be provided in order to patch/update the +TargetGrpcProxy; otherwise, the request will fail with error +412 conditionNotMet. To see the latest fingerprint, make a get() +request to retrieve the TargetGrpcProxy. A base64-encoded string.`, + }, + "self_link_with_id": { + Type: schema.TypeString, + Computed: true, + Description: `Server-defined URL with id for the resource.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "self_link": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceComputeTargetGrpcProxyCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + nameProp, err := expandComputeTargetGrpcProxyName(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 + } + descriptionProp, err := expandComputeTargetGrpcProxyDescription(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 + } + urlMapProp, err := expandComputeTargetGrpcProxyUrlMap(d.Get("url_map"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("url_map"); !isEmptyValue(reflect.ValueOf(urlMapProp)) && (ok || !reflect.DeepEqual(v, urlMapProp)) { + obj["urlMap"] = urlMapProp + } + validateForProxylessProp, err := expandComputeTargetGrpcProxyValidateForProxyless(d.Get("validate_for_proxyless"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("validate_for_proxyless"); !isEmptyValue(reflect.ValueOf(validateForProxylessProp)) && (ok || !reflect.DeepEqual(v, validateForProxylessProp)) { + obj["validateForProxyless"] = validateForProxylessProp + } + fingerprintProp, err := expandComputeTargetGrpcProxyFingerprint(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 + } + + url, err := replaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/global/targetGrpcProxies") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new TargetGrpcProxy: %#v", obj) + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return err + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "POST", billingProject, url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating TargetGrpcProxy: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "projects/{{project}}/global/targetGrpcProxies/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + err = computeOperationWaitTime( + config, res, project, "Creating TargetGrpcProxy", + d.Timeout(schema.TimeoutCreate)) + + if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create TargetGrpcProxy: %s", err) + } + + log.Printf("[DEBUG] Finished creating TargetGrpcProxy %q: %#v", d.Id(), res) + + return resourceComputeTargetGrpcProxyRead(d, meta) +} + +func resourceComputeTargetGrpcProxyRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/global/targetGrpcProxies/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return err + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequest(config, "GET", billingProject, url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("ComputeTargetGrpcProxy %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading TargetGrpcProxy: %s", err) + } + + if err := d.Set("creation_timestamp", flattenComputeTargetGrpcProxyCreationTimestamp(res["creationTimestamp"], d, config)); err != nil { + return fmt.Errorf("Error reading TargetGrpcProxy: %s", err) + } + if err := d.Set("name", flattenComputeTargetGrpcProxyName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading TargetGrpcProxy: %s", err) + } + if err := d.Set("description", flattenComputeTargetGrpcProxyDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading TargetGrpcProxy: %s", err) + } + if err := d.Set("self_link_with_id", flattenComputeTargetGrpcProxySelfLinkWithId(res["selfLinkWithId"], d, config)); err != nil { + return fmt.Errorf("Error reading TargetGrpcProxy: %s", err) + } + if err := d.Set("url_map", flattenComputeTargetGrpcProxyUrlMap(res["urlMap"], d, config)); err != nil { + return fmt.Errorf("Error reading TargetGrpcProxy: %s", err) + } + if err := d.Set("validate_for_proxyless", flattenComputeTargetGrpcProxyValidateForProxyless(res["validateForProxyless"], d, config)); err != nil { + return fmt.Errorf("Error reading TargetGrpcProxy: %s", err) + } + if err := d.Set("fingerprint", flattenComputeTargetGrpcProxyFingerprint(res["fingerprint"], d, config)); err != nil { + return fmt.Errorf("Error reading TargetGrpcProxy: %s", err) + } + if err := d.Set("self_link", ConvertSelfLinkToV1(res["selfLink"].(string))); err != nil { + return fmt.Errorf("Error reading TargetGrpcProxy: %s", err) + } + + return nil +} + +func resourceComputeTargetGrpcProxyUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return err + } + billingProject = project + + obj := make(map[string]interface{}) + descriptionProp, err := expandComputeTargetGrpcProxyDescription(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 + } + fingerprintProp, err := expandComputeTargetGrpcProxyFingerprint(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 + } + + url, err := replaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/global/targetGrpcProxies/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating TargetGrpcProxy %q: %#v", d.Id(), obj) + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "PATCH", billingProject, url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating TargetGrpcProxy %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating TargetGrpcProxy %q: %#v", d.Id(), res) + } + + err = computeOperationWaitTime( + config, res, project, "Updating TargetGrpcProxy", + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + + return resourceComputeTargetGrpcProxyRead(d, meta) +} + +func resourceComputeTargetGrpcProxyDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return err + } + billingProject = project + + url, err := replaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/global/targetGrpcProxies/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting TargetGrpcProxy %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "DELETE", billingProject, url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "TargetGrpcProxy") + } + + err = computeOperationWaitTime( + config, res, project, "Deleting TargetGrpcProxy", + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting TargetGrpcProxy %q: %#v", d.Id(), res) + return nil +} + +func resourceComputeTargetGrpcProxyImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)/global/targetGrpcProxies/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/global/targetGrpcProxies/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenComputeTargetGrpcProxyCreationTimestamp(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenComputeTargetGrpcProxyName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenComputeTargetGrpcProxyDescription(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenComputeTargetGrpcProxySelfLinkWithId(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenComputeTargetGrpcProxyUrlMap(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenComputeTargetGrpcProxyValidateForProxyless(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenComputeTargetGrpcProxyFingerprint(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandComputeTargetGrpcProxyName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeTargetGrpcProxyDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeTargetGrpcProxyUrlMap(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeTargetGrpcProxyValidateForProxyless(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeTargetGrpcProxyFingerprint(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google/resource_compute_target_grpc_proxy_generated_test.go b/google/resource_compute_target_grpc_proxy_generated_test.go new file mode 100644 index 00000000000..6af80dd87c7 --- /dev/null +++ b/google/resource_compute_target_grpc_proxy_generated_test.go @@ -0,0 +1,167 @@ +// ---------------------------------------------------------------------------- +// +// *** 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-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccComputeTargetGrpcProxy_targetGrpcProxyBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeTargetGrpcProxyDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeTargetGrpcProxy_targetGrpcProxyBasicExample(context), + }, + { + ResourceName: "google_compute_target_grpc_proxy.default", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccComputeTargetGrpcProxy_targetGrpcProxyBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_compute_target_grpc_proxy" "default" { + name = "proxy%{random_suffix}" + url_map = google_compute_url_map.urlmap.id + validate_for_proxyless = true +} + + +resource "google_compute_url_map" "urlmap" { + name = "urlmap%{random_suffix}" + description = "a description" + default_service = google_compute_backend_service.home.id + host_rule { + hosts = ["mysite.com"] + path_matcher = "allpaths" + } + path_matcher { + name = "allpaths" + default_service = google_compute_backend_service.home.id + route_rules { + priority = 1 + header_action { + request_headers_to_remove = ["RemoveMe2"] + request_headers_to_add { + header_name = "AddSomethingElse" + header_value = "MyOtherValue" + replace = true + } + response_headers_to_remove = ["RemoveMe3"] + response_headers_to_add { + header_name = "AddMe" + header_value = "MyValue" + replace = false + } + } + match_rules { + full_path_match = "a full path" + header_matches { + header_name = "someheader" + exact_match = "match this exactly" + invert_match = true + } + ignore_case = true + metadata_filters { + filter_match_criteria = "MATCH_ANY" + filter_labels { + name = "PLANET" + value = "MARS" + } + } + query_parameter_matches { + name = "a query parameter" + present_match = true + } + } + url_redirect { + host_redirect = "A host" + https_redirect = false + path_redirect = "some/path" + redirect_response_code = "TEMPORARY_REDIRECT" + strip_query = true + } + } + } + test { + service = google_compute_backend_service.home.id + host = "hi.com" + path = "/home" + } +} +resource "google_compute_backend_service" "home" { + name = "backend%{random_suffix}" + port_name = "grpc" + protocol = "GRPC" + timeout_sec = 10 + health_checks = [google_compute_health_check.default.id] + load_balancing_scheme = "INTERNAL_SELF_MANAGED" +} +resource "google_compute_health_check" "default" { + name = "healthcheck%{random_suffix}" + timeout_sec = 1 + check_interval_sec = 1 + grpc_health_check { + port_name = "health-check-port" + port_specification = "USE_NAMED_PORT" + grpc_service_name = "testservice" + } +} +`, context) +} + +func testAccCheckComputeTargetGrpcProxyDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_target_grpc_proxy" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{ComputeBasePath}}projects/{{project}}/global/targetGrpcProxies/{{name}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", "", url, nil) + if err == nil { + return fmt.Errorf("ComputeTargetGrpcProxy still exists at %s", url) + } + } + + return nil + } +} diff --git a/google/resource_compute_target_grpc_proxy_sweeper_test.go b/google/resource_compute_target_grpc_proxy_sweeper_test.go new file mode 100644 index 00000000000..65273e357fe --- /dev/null +++ b/google/resource_compute_target_grpc_proxy_sweeper_test.go @@ -0,0 +1,124 @@ +// ---------------------------------------------------------------------------- +// +// *** 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 ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func init() { + resource.AddTestSweepers("ComputeTargetGrpcProxy", &resource.Sweeper{ + Name: "ComputeTargetGrpcProxy", + F: testSweepComputeTargetGrpcProxy, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepComputeTargetGrpcProxy(region string) error { + resourceName := "ComputeTargetGrpcProxy" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + t := &testing.T{} + billingId := getTestBillingAccountFromEnv(t) + + // Setup variables to replace in list template + d := &ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://compute.googleapis.com/compute/v1/projects/{{project}}/global/targetGrpcProxies", "?")[0] + listUrl, err := replaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := sendRequest(config, "GET", config.Project, listUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["items"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := GetResourceNameFromSelfLink(obj["name"].(string)) + // Skip resources that shouldn't be sweeped + if !isSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://compute.googleapis.com/compute/v1/projects/{{project}}/global/targetGrpcProxies/{{name}}" + deleteUrl, err := replaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = sendRequest(config, "DELETE", config.Project, deleteUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) + } + + return nil +} diff --git a/google/resource_compute_target_grpc_proxy_test.go b/google/resource_compute_target_grpc_proxy_test.go new file mode 100644 index 00000000000..237c7b46ac6 --- /dev/null +++ b/google/resource_compute_target_grpc_proxy_test.go @@ -0,0 +1,134 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + // "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccComputeTargetGrpcProxy_update(t *testing.T) { + t.Parallel() + + proxy := fmt.Sprintf("tf-manual-proxy-%s", randString(t, 10)) + urlmap1 := fmt.Sprintf("tf-manual-urlmap1-%s", randString(t, 10)) + urlmap2 := fmt.Sprintf("tf-manual-urlmap2-%s", randString(t, 10)) + backend := fmt.Sprintf("tf-manual-backend-%s", randString(t, 10)) + healthcheck := fmt.Sprintf("tf-manual-healthcheck-%s", randString(t, 10)) + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeTargetGrpcProxyDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeTargetGrpcProxy_basic(proxy, urlmap1, backend, healthcheck), + }, + { + ResourceName: "google_compute_target_grpc_proxy.default", + ImportState: true, + ImportStateVerify: true, + }, + + { + Config: testAccComputeTargetGrpcProxy_basic(proxy, urlmap2, backend, healthcheck), + }, + { + ResourceName: "google_compute_target_grpc_proxy.default", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccComputeTargetGrpcProxy_basic(proxy, urlmap, backend, healthcheck string) string { + return fmt.Sprintf(` +resource "google_compute_target_grpc_proxy" "default" { + name = "%s" + url_map = google_compute_url_map.urlmap.id + validate_for_proxyless = true +} +resource "google_compute_url_map" "urlmap" { + name = "%s" + description = "a description" + default_service = google_compute_backend_service.home.id + host_rule { + hosts = ["mysite.com"] + path_matcher = "allpaths" + } + path_matcher { + name = "allpaths" + default_service = google_compute_backend_service.home.id + route_rules { + priority = 1 + header_action { + request_headers_to_remove = ["RemoveMe2"] + request_headers_to_add { + header_name = "AddSomethingElse" + header_value = "MyOtherValue" + replace = true + } + response_headers_to_remove = ["RemoveMe3"] + response_headers_to_add { + header_name = "AddMe" + header_value = "MyValue" + replace = false + } + } + match_rules { + full_path_match = "a full path" + header_matches { + header_name = "someheader" + exact_match = "match this exactly" + invert_match = true + } + ignore_case = true + metadata_filters { + filter_match_criteria = "MATCH_ANY" + filter_labels { + name = "PLANET" + value = "MARS" + } + } + query_parameter_matches { + name = "a query parameter" + present_match = true + } + } + url_redirect { + host_redirect = "A host" + https_redirect = false + path_redirect = "some/path" + redirect_response_code = "TEMPORARY_REDIRECT" + strip_query = true + } + } + } + test { + service = google_compute_backend_service.home.id + host = "hi.com" + path = "/home" + } +} +resource "google_compute_backend_service" "home" { + name = "%s" + port_name = "grpc" + protocol = "GRPC" + timeout_sec = 10 + health_checks = [google_compute_health_check.default.id] + load_balancing_scheme = "INTERNAL_SELF_MANAGED" +} +resource "google_compute_health_check" "default" { + name = "%s" + timeout_sec = 1 + check_interval_sec = 1 + grpc_health_check { + port_name = "health-check-port" + port_specification = "USE_NAMED_PORT" + grpc_service_name = "testservice" + } +} +`, proxy, urlmap, backend, healthcheck) +} diff --git a/website/docs/r/compute_target_grpc_proxy.html.markdown b/website/docs/r/compute_target_grpc_proxy.html.markdown new file mode 100644 index 00000000000..bfd0d265d4e --- /dev/null +++ b/website/docs/r/compute_target_grpc_proxy.html.markdown @@ -0,0 +1,225 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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. +# +# ---------------------------------------------------------------------------- +subcategory: "Compute Engine" +layout: "google" +page_title: "Google: google_compute_target_grpc_proxy" +sidebar_current: "docs-google-compute-target-grpc-proxy" +description: |- + Represents a Target gRPC Proxy resource. +--- + +# google\_compute\_target\_grpc\_proxy + +Represents a Target gRPC Proxy resource. A target gRPC proxy is a component +of load balancers intended for load balancing gRPC traffic. Global forwarding +rules reference a target gRPC proxy. The Target gRPC Proxy references +a URL map which specifies how traffic routes to gRPC backend services. + + +To get more information about TargetGrpcProxy, see: + +* [API documentation](https://cloud.google.com/compute/docs/reference/rest/v1/targetGrpcProxies) +* How-to Guides + * [Using Target gRPC Proxies](https://cloud.google.com/traffic-director/docs/proxyless-overview) + + +## Example Usage - Target Grpc Proxy Basic + + +```hcl +resource "google_compute_target_grpc_proxy" "default" { + name = "proxy" + url_map = google_compute_url_map.urlmap.id + validate_for_proxyless = true +} + + +resource "google_compute_url_map" "urlmap" { + name = "urlmap" + description = "a description" + default_service = google_compute_backend_service.home.id + host_rule { + hosts = ["mysite.com"] + path_matcher = "allpaths" + } + path_matcher { + name = "allpaths" + default_service = google_compute_backend_service.home.id + route_rules { + priority = 1 + header_action { + request_headers_to_remove = ["RemoveMe2"] + request_headers_to_add { + header_name = "AddSomethingElse" + header_value = "MyOtherValue" + replace = true + } + response_headers_to_remove = ["RemoveMe3"] + response_headers_to_add { + header_name = "AddMe" + header_value = "MyValue" + replace = false + } + } + match_rules { + full_path_match = "a full path" + header_matches { + header_name = "someheader" + exact_match = "match this exactly" + invert_match = true + } + ignore_case = true + metadata_filters { + filter_match_criteria = "MATCH_ANY" + filter_labels { + name = "PLANET" + value = "MARS" + } + } + query_parameter_matches { + name = "a query parameter" + present_match = true + } + } + url_redirect { + host_redirect = "A host" + https_redirect = false + path_redirect = "some/path" + redirect_response_code = "TEMPORARY_REDIRECT" + strip_query = true + } + } + } + test { + service = google_compute_backend_service.home.id + host = "hi.com" + path = "/home" + } +} +resource "google_compute_backend_service" "home" { + name = "backend" + port_name = "grpc" + protocol = "GRPC" + timeout_sec = 10 + health_checks = [google_compute_health_check.default.id] + load_balancing_scheme = "INTERNAL_SELF_MANAGED" +} +resource "google_compute_health_check" "default" { + name = "healthcheck" + timeout_sec = 1 + check_interval_sec = 1 + grpc_health_check { + port_name = "health-check-port" + port_specification = "USE_NAMED_PORT" + grpc_service_name = "testservice" + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `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. + + +- - - + + +* `description` - + (Optional) + An optional description of this resource. + +* `url_map` - + (Optional) + URL to the UrlMap resource that defines the mapping from URL to + the BackendService. The protocol field in the BackendService + must be set to GRPC. + +* `validate_for_proxyless` - + (Optional) + If true, indicates that the BackendServices referenced by + the urlMap may be accessed by gRPC applications without using + a sidecar proxy. This will enable configuration checks on urlMap + and its referenced BackendServices to not allow unsupported features. + A gRPC application must use "xds:///" scheme in the target URI + of the service it is connecting to. If false, indicates that the + BackendServices referenced by the urlMap will be accessed by gRPC + applications via a sidecar proxy. In this case, a gRPC application + must not use "xds:///" scheme in the target URI of the service + it is connecting to + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/global/targetGrpcProxies/{{name}}` + +* `creation_timestamp` - + Creation timestamp in RFC3339 text format. + +* `self_link_with_id` - + Server-defined URL with id for the resource. + +* `fingerprint` - + Fingerprint of this resource. A hash of the contents stored in + this object. This field is used in optimistic locking. This field + will be ignored when inserting a TargetGrpcProxy. An up-to-date + fingerprint must be provided in order to patch/update the + TargetGrpcProxy; otherwise, the request will fail with error + 412 conditionNotMet. To see the latest fingerprint, make a get() + request to retrieve the TargetGrpcProxy. A base64-encoded string. +* `self_link` - The URI of the created resource. + + +## Timeouts + +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 + +TargetGrpcProxy can be imported using any of these accepted formats: + +``` +$ terraform import google_compute_target_grpc_proxy.default projects/{{project}}/global/targetGrpcProxies/{{name}} +$ terraform import google_compute_target_grpc_proxy.default {{project}}/{{name}} +$ terraform import google_compute_target_grpc_proxy.default {{name}} +``` + +## User Project Overrides + +This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override). diff --git a/website/google.erb b/website/google.erb index 2318f4778db..6bfffecc70d 100644 --- a/website/google.erb +++ b/website/google.erb @@ -1516,6 +1516,10 @@ google_compute_subnetwork_iam +
  • + google_compute_target_grpc_proxy +
  • +
  • google_compute_target_http_proxy