From f502afab04b9ae8063d5414c68a79055fa6d924e Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Tue, 7 Jan 2025 22:03:24 +0000 Subject: [PATCH] Add Terraform support for the Security Gateway resource (#12695) [upstream:03a313d13c5d31f8e8fc71cb32d06157bc260f78] Signed-off-by: Modular Magician --- .changelog/12695.txt | 3 + .../provider/provider_mmv1_resources.go | 5 +- .../resource_beyondcorp_security_gateway.go | 586 ++++++++++++++++++ ...dcorp_security_gateway_generated_meta.yaml | 5 + ...ondcorp_security_gateway_generated_test.go | 106 ++++ ...rce_beyondcorp_security_gateway_sweeper.go | 143 +++++ ...source_beyondcorp_security_gateway_test.go | 72 +++ .../beyondcorp_security_gateway.html.markdown | 163 +++++ 8 files changed, 1081 insertions(+), 2 deletions(-) create mode 100644 .changelog/12695.txt create mode 100644 google-beta/services/beyondcorp/resource_beyondcorp_security_gateway.go create mode 100644 google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_generated_meta.yaml create mode 100644 google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_generated_test.go create mode 100644 google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_sweeper.go create mode 100644 google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_test.go create mode 100644 website/docs/r/beyondcorp_security_gateway.html.markdown diff --git a/.changelog/12695.txt b/.changelog/12695.txt new file mode 100644 index 0000000000..59954eb1f6 --- /dev/null +++ b/.changelog/12695.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +`google_beyondcorp_security_gateway` +``` \ No newline at end of file diff --git a/google-beta/provider/provider_mmv1_resources.go b/google-beta/provider/provider_mmv1_resources.go index c3c53bf797..e7d2f77b3e 100644 --- a/google-beta/provider/provider_mmv1_resources.go +++ b/google-beta/provider/provider_mmv1_resources.go @@ -513,9 +513,9 @@ var handwrittenIAMDatasources = map[string]*schema.Resource{ } // Resources -// Generated resources: 577 +// Generated resources: 578 // Generated IAM resources: 294 -// Total generated resources: 871 +// Total generated resources: 872 var generatedResources = map[string]*schema.Resource{ "google_folder_access_approval_settings": accessapproval.ResourceAccessApprovalFolderSettings(), "google_organization_access_approval_settings": accessapproval.ResourceAccessApprovalOrganizationSettings(), @@ -602,6 +602,7 @@ var generatedResources = map[string]*schema.Resource{ "google_beyondcorp_app_connection": beyondcorp.ResourceBeyondcorpAppConnection(), "google_beyondcorp_app_connector": beyondcorp.ResourceBeyondcorpAppConnector(), "google_beyondcorp_app_gateway": beyondcorp.ResourceBeyondcorpAppGateway(), + "google_beyondcorp_security_gateway": beyondcorp.ResourceBeyondcorpSecurityGateway(), "google_biglake_catalog": biglake.ResourceBiglakeCatalog(), "google_biglake_database": biglake.ResourceBiglakeDatabase(), "google_biglake_table": biglake.ResourceBiglakeTable(), diff --git a/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway.go b/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway.go new file mode 100644 index 0000000000..ad1c7f517b --- /dev/null +++ b/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway.go @@ -0,0 +1,586 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// 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 beyondcorp + +import ( + "fmt" + "log" + "net/http" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" +) + +func ResourceBeyondcorpSecurityGateway() *schema.Resource { + return &schema.Resource{ + Create: resourceBeyondcorpSecurityGatewayCreate, + Read: resourceBeyondcorpSecurityGatewayRead, + Update: resourceBeyondcorpSecurityGatewayUpdate, + Delete: resourceBeyondcorpSecurityGatewayDelete, + + Importer: &schema.ResourceImporter{ + State: resourceBeyondcorpSecurityGatewayImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + CustomizeDiff: customdiff.All( + tpgresource.DefaultProviderProject, + ), + + Schema: map[string]*schema.Schema{ + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Resource ID segment making up resource 'name'. It identifies the resource within its parent collection as described in https://google.aip.dev/122.`, + }, + "security_gateway_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Optional. User-settable SecurityGateway resource ID. +* Must start with a letter. +* Must contain between 4-63 characters from '/a-z-/'. +* Must end with a number or letter.`, + }, + "display_name": { + Type: schema.TypeString, + Optional: true, + Description: `Optional. An arbitrary user-provided name for the SecurityGateway. +Cannot exceed 64 characters.`, + }, + "hubs": { + Type: schema.TypeSet, + Optional: true, + Description: `Optional. Map of Hubs that represents regional data path deployment with GCP region +as a key.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Required: true, + }, + "internet_gateway": { + Type: schema.TypeList, + Optional: true, + Description: `Internet Gateway configuration.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "assigned_ips": { + Type: schema.TypeList, + Computed: true, + Description: `Output only. List of IP addresses assigned to the Cloud NAT.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. Timestamp when the resource was created.`, + }, + "external_ips": { + Type: schema.TypeList, + Computed: true, + Description: `Output only. IP addresses that will be used for establishing +connection to the endpoints.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `Identifier. Name of the resource.`, + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. The operational state of the SecurityGateway. +Possible values: +STATE_UNSPECIFIED +CREATING +UPDATING +DELETING +RUNNING +DOWN +ERROR`, + }, + "update_time": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. Timestamp when the resource was last modified.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func resourceBeyondcorpSecurityGatewayCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + hubsProp, err := expandBeyondcorpSecurityGatewayHubs(d.Get("hubs"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("hubs"); !tpgresource.IsEmptyValue(reflect.ValueOf(hubsProp)) && (ok || !reflect.DeepEqual(v, hubsProp)) { + obj["hubs"] = hubsProp + } + displayNameProp, err := expandBeyondcorpSecurityGatewayDisplayName(d.Get("display_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("display_name"); !tpgresource.IsEmptyValue(reflect.ValueOf(displayNameProp)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{location}}/securityGateways?securityGatewayId={{security_gateway_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new SecurityGateway: %#v", obj) + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for SecurityGateway: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + headers := make(http.Header) + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutCreate), + Headers: headers, + }) + if err != nil { + return fmt.Errorf("Error creating SecurityGateway: %s", err) + } + + // Store the ID now + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/securityGateways/{{security_gateway_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // Use the resource in the operation response to populate + // identity fields and d.Id() before read + var opRes map[string]interface{} + err = BeyondcorpOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating SecurityGateway", userAgent, + d.Timeout(schema.TimeoutCreate)) + if err != nil { + // The resource didn't actually create + d.SetId("") + + return fmt.Errorf("Error waiting to create SecurityGateway: %s", err) + } + + if err := d.Set("name", flattenBeyondcorpSecurityGatewayName(opRes["name"], d, config)); err != nil { + return err + } + + // This may have caused the ID to update - update it if so. + id, err = tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/securityGateways/{{security_gateway_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating SecurityGateway %q: %#v", d.Id(), res) + + return resourceBeyondcorpSecurityGatewayRead(d, meta) +} + +func resourceBeyondcorpSecurityGatewayRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + url, err := tpgresource.ReplaceVars(d, config, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{location}}/securityGateways/{{security_gateway_id}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for SecurityGateway: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + headers := make(http.Header) + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Headers: headers, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("BeyondcorpSecurityGateway %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading SecurityGateway: %s", err) + } + + if err := d.Set("state", flattenBeyondcorpSecurityGatewayState(res["state"], d, config)); err != nil { + return fmt.Errorf("Error reading SecurityGateway: %s", err) + } + if err := d.Set("update_time", flattenBeyondcorpSecurityGatewayUpdateTime(res["updateTime"], d, config)); err != nil { + return fmt.Errorf("Error reading SecurityGateway: %s", err) + } + if err := d.Set("create_time", flattenBeyondcorpSecurityGatewayCreateTime(res["createTime"], d, config)); err != nil { + return fmt.Errorf("Error reading SecurityGateway: %s", err) + } + if err := d.Set("hubs", flattenBeyondcorpSecurityGatewayHubs(res["hubs"], d, config)); err != nil { + return fmt.Errorf("Error reading SecurityGateway: %s", err) + } + if err := d.Set("display_name", flattenBeyondcorpSecurityGatewayDisplayName(res["displayName"], d, config)); err != nil { + return fmt.Errorf("Error reading SecurityGateway: %s", err) + } + if err := d.Set("external_ips", flattenBeyondcorpSecurityGatewayExternalIps(res["externalIps"], d, config)); err != nil { + return fmt.Errorf("Error reading SecurityGateway: %s", err) + } + if err := d.Set("name", flattenBeyondcorpSecurityGatewayName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading SecurityGateway: %s", err) + } + + return nil +} + +func resourceBeyondcorpSecurityGatewayUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for SecurityGateway: %s", err) + } + billingProject = project + + obj := make(map[string]interface{}) + hubsProp, err := expandBeyondcorpSecurityGatewayHubs(d.Get("hubs"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("hubs"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, hubsProp)) { + obj["hubs"] = hubsProp + } + displayNameProp, err := expandBeyondcorpSecurityGatewayDisplayName(d.Get("display_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("display_name"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{location}}/securityGateways/{{security_gateway_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating SecurityGateway %q: %#v", d.Id(), obj) + headers := make(http.Header) + updateMask := []string{} + + if d.HasChange("hubs") { + updateMask = append(updateMask, "hubs") + } + + if d.HasChange("display_name") { + updateMask = append(updateMask, "displayName") + } + // updateMask is a URL parameter but not present in the schema, so ReplaceVars + // won't set it + url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + // if updateMask is empty we are not updating anything so skip the post + if len(updateMask) > 0 { + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "PATCH", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutUpdate), + Headers: headers, + }) + + if err != nil { + return fmt.Errorf("Error updating SecurityGateway %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating SecurityGateway %q: %#v", d.Id(), res) + } + + err = BeyondcorpOperationWaitTime( + config, res, project, "Updating SecurityGateway", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + } + + return resourceBeyondcorpSecurityGatewayRead(d, meta) +} + +func resourceBeyondcorpSecurityGatewayDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for SecurityGateway: %s", err) + } + billingProject = project + + url, err := tpgresource.ReplaceVars(d, config, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{location}}/securityGateways/{{security_gateway_id}}") + if err != nil { + return err + } + + var obj map[string]interface{} + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + headers := make(http.Header) + + log.Printf("[DEBUG] Deleting SecurityGateway %q", d.Id()) + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "DELETE", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutDelete), + Headers: headers, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, "SecurityGateway") + } + + err = BeyondcorpOperationWaitTime( + config, res, project, "Deleting SecurityGateway", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting SecurityGateway %q: %#v", d.Id(), res) + return nil +} + +func resourceBeyondcorpSecurityGatewayImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*transport_tpg.Config) + if err := tpgresource.ParseImportId([]string{ + "^projects/(?P[^/]+)/locations/(?P[^/]+)/securityGateways/(?P[^/]+)$", + "^(?P[^/]+)/(?P[^/]+)/(?P[^/]+)$", + "^(?P[^/]+)/(?P[^/]+)$", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/securityGateways/{{security_gateway_id}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenBeyondcorpSecurityGatewayState(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenBeyondcorpSecurityGatewayUpdateTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenBeyondcorpSecurityGatewayCreateTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenBeyondcorpSecurityGatewayHubs(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.(map[string]interface{}) + transformed := make([]interface{}, 0, len(l)) + for k, raw := range l { + original := raw.(map[string]interface{}) + transformed = append(transformed, map[string]interface{}{ + "region": k, + "internet_gateway": flattenBeyondcorpSecurityGatewayHubsInternetGateway(original["internet_gateway"], d, config), + }) + } + return transformed +} +func flattenBeyondcorpSecurityGatewayHubsInternetGateway(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["assigned_ips"] = + flattenBeyondcorpSecurityGatewayHubsInternetGatewayAssignedIps(original["assigned_ips"], d, config) + return []interface{}{transformed} +} +func flattenBeyondcorpSecurityGatewayHubsInternetGatewayAssignedIps(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenBeyondcorpSecurityGatewayDisplayName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenBeyondcorpSecurityGatewayExternalIps(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenBeyondcorpSecurityGatewayName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func expandBeyondcorpSecurityGatewayHubs(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (map[string]interface{}, error) { + if v == nil { + return map[string]interface{}{}, nil + } + m := make(map[string]interface{}) + for _, raw := range v.(*schema.Set).List() { + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedInternetGateway, err := expandBeyondcorpSecurityGatewayHubsInternetGateway(original["internet_gateway"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedInternetGateway); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["internet_gateway"] = transformedInternetGateway + } + + transformedRegion, err := tpgresource.ExpandString(original["region"], d, config) + if err != nil { + return nil, err + } + m[transformedRegion] = transformed + } + return m, nil +} + +func expandBeyondcorpSecurityGatewayHubsInternetGateway(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.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{}) + + transformedAssignedIps, err := expandBeyondcorpSecurityGatewayHubsInternetGatewayAssignedIps(original["assigned_ips"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAssignedIps); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["assigned_ips"] = transformedAssignedIps + } + + return transformed, nil +} + +func expandBeyondcorpSecurityGatewayHubsInternetGatewayAssignedIps(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandBeyondcorpSecurityGatewayDisplayName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} diff --git a/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_generated_meta.yaml b/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_generated_meta.yaml new file mode 100644 index 0000000000..e065af0a7d --- /dev/null +++ b/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_generated_meta.yaml @@ -0,0 +1,5 @@ +resource: 'google_beyondcorp_security_gateway' +generation_type: 'mmv1' +api_service_name: 'beyondcorp.googleapis.com' +api_version: 'v1' +api_resource_type_kind: 'SecurityGateway' diff --git a/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_generated_test.go b/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_generated_test.go new file mode 100644 index 0000000000..d3e0162368 --- /dev/null +++ b/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_generated_test.go @@ -0,0 +1,106 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// 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 beyondcorp_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" +) + +func TestAccBeyondcorpSecurityGateway_beyondcorpSecurityGatewayBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckBeyondcorpSecurityGatewayDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccBeyondcorpSecurityGateway_beyondcorpSecurityGatewayBasicExample(context), + }, + { + ResourceName: "google_beyondcorp_security_gateway.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "security_gateway_id"}, + }, + }, + }) +} + +func testAccBeyondcorpSecurityGateway_beyondcorpSecurityGatewayBasicExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_beyondcorp_security_gateway" "example" { + security_gateway_id = "default%{random_suffix}" + location = "global" + display_name = "My Security Gateway resource" + hubs { region = "us-central1" } +} +`, context) +} + +func testAccCheckBeyondcorpSecurityGatewayDestroyProducer(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_beyondcorp_security_gateway" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := acctest.GoogleProviderConfig(t) + + url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{location}}/securityGateways/{{security_gateway_id}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: config.UserAgent, + }) + if err == nil { + return fmt.Errorf("BeyondcorpSecurityGateway still exists at %s", url) + } + } + + return nil + } +} diff --git a/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_sweeper.go b/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_sweeper.go new file mode 100644 index 0000000000..2045e9ea4d --- /dev/null +++ b/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_sweeper.go @@ -0,0 +1,143 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// 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 beyondcorp + +import ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-provider-google-beta/google-beta/envvar" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/sweeper" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" +) + +func init() { + sweeper.AddTestSweepers("BeyondcorpSecurityGateway", testSweepBeyondcorpSecurityGateway) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepBeyondcorpSecurityGateway(region string) error { + resourceName := "BeyondcorpSecurityGateway" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sweeper.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 := envvar.GetTestBillingAccountFromEnv(t) + + // Setup variables to replace in list template + d := &tpgresource.ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://beyondcorp.googleapis.com/v1/projects/{{project}}/locations/{{location}}/securityGateways", "?")[0] + listUrl, err := tpgresource.ReplaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: config.Project, + RawURL: listUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["securityGateways"] + 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{}) + var name string + // Id detected in the delete URL, attempt to use id. + if obj["id"] != nil { + name = tpgresource.GetResourceNameFromSelfLink(obj["id"].(string)) + } else if obj["name"] != nil { + name = tpgresource.GetResourceNameFromSelfLink(obj["name"].(string)) + } else { + log.Printf("[INFO][SWEEPER_LOG] %s resource name and id were nil", resourceName) + return nil + } + // Skip resources that shouldn't be sweeped + if !sweeper.IsSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://beyondcorp.googleapis.com/v1/projects/{{project}}/locations/{{location}}/securityGateways/{{security_gateway_id}}" + deleteUrl, err := tpgresource.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 = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "DELETE", + Project: config.Project, + RawURL: deleteUrl, + UserAgent: config.UserAgent, + }) + 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-beta/services/beyondcorp/resource_beyondcorp_security_gateway_test.go b/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_test.go new file mode 100644 index 0000000000..b416fc7b63 --- /dev/null +++ b/google-beta/services/beyondcorp/resource_beyondcorp_security_gateway_test.go @@ -0,0 +1,72 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 +package beyondcorp_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + + "github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest" +) + +func TestAccBeyondcorpSecurityGateway_beyondcorpSecurityGatewayBasicExample_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccBeyondcorpSecurityGateway_beyondcorpSecurityGatewayBasicExample_basic(context), + }, + { + ResourceName: "google_beyondcorp_security_gateway.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "security_gateway_id"}, + }, + { + Config: testAccBeyondcorpSecurityGateway_beyondcorpSecurityGatewayBasicExample_update(context), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("google_beyondcorp_security_gateway.example", plancheck.ResourceActionUpdate), + }, + }, + }, + { + ResourceName: "google_beyondcorp_security_gateway.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "security_gateway_id"}, + }, + }, + }) +} + +func testAccBeyondcorpSecurityGateway_beyondcorpSecurityGatewayBasicExample_basic(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_beyondcorp_security_gateway" "example" { + security_gateway_id = "default%{random_suffix}" + location = "global" + display_name = "My Security Gateway resource" + hubs { region = "us-central1" } +} +`, context) +} + +func testAccBeyondcorpSecurityGateway_beyondcorpSecurityGatewayBasicExample_update(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_beyondcorp_security_gateway" "example" { + security_gateway_id = "default%{random_suffix}" + location = "global" + display_name = "My Security Gateway resource" + hubs { region = "us-east1" } +} +`, context) +} diff --git a/website/docs/r/beyondcorp_security_gateway.html.markdown b/website/docs/r/beyondcorp_security_gateway.html.markdown new file mode 100644 index 0000000000..eade9ddc70 --- /dev/null +++ b/website/docs/r/beyondcorp_security_gateway.html.markdown @@ -0,0 +1,163 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: MMv1 *** +# +# ---------------------------------------------------------------------------- +# +# 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: "BeyondCorp" +description: |- + Deployment of Security Gateway. +--- + +# google_beyondcorp_security_gateway + +Deployment of Security Gateway. + + + + +## Example Usage - Beyondcorp Security Gateway Basic + + +```hcl +resource "google_beyondcorp_security_gateway" "example" { + security_gateway_id = "default" + location = "global" + display_name = "My Security Gateway resource" + hubs { region = "us-central1" } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `location` - + (Required) + Resource ID segment making up resource `name`. It identifies the resource within its parent collection as described in https://google.aip.dev/122. + +* `security_gateway_id` - + (Required) + Optional. User-settable SecurityGateway resource ID. + * Must start with a letter. + * Must contain between 4-63 characters from `/a-z-/`. + * Must end with a number or letter. + + +- - - + + +* `hubs` - + (Optional) + Optional. Map of Hubs that represents regional data path deployment with GCP region + as a key. + Structure is [documented below](#nested_hubs). + +* `display_name` - + (Optional) + Optional. An arbitrary user-provided name for the SecurityGateway. + Cannot exceed 64 characters. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +The `hubs` block supports: + +* `region` - (Required) The identifier for this object. Format specified above. + +* `internet_gateway` - + (Optional) + Internet Gateway configuration. + Structure is [documented below](#nested_hubs_hub_internet_gateway). + + +The `internet_gateway` block supports: + +* `assigned_ips` - + (Output) + Output only. List of IP addresses assigned to the Cloud NAT. + +## 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}}/locations/{{location}}/securityGateways/{{security_gateway_id}}` + +* `state` - + Output only. The operational state of the SecurityGateway. + Possible values: + STATE_UNSPECIFIED + CREATING + UPDATING + DELETING + RUNNING + DOWN + ERROR + +* `update_time` - + Output only. Timestamp when the resource was last modified. + +* `create_time` - + Output only. Timestamp when the resource was created. + +* `external_ips` - + Output only. IP addresses that will be used for establishing + connection to the endpoints. + +* `name` - + Identifier. Name of the resource. + + +## Timeouts + +This resource provides the following +[Timeouts](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/retries-and-customizable-timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `update` - Default is 20 minutes. +- `delete` - Default is 20 minutes. + +## Import + + +SecurityGateway can be imported using any of these accepted formats: + +* `projects/{{project}}/locations/{{location}}/securityGateways/{{security_gateway_id}}` +* `{{project}}/{{location}}/{{security_gateway_id}}` +* `{{location}}/{{security_gateway_id}}` + + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import SecurityGateway using one of the formats above. For example: + +```tf +import { + id = "projects/{{project}}/locations/{{location}}/securityGateways/{{security_gateway_id}}" + to = google_beyondcorp_security_gateway.default +} +``` + +When using the [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import), SecurityGateway can be imported using one of the formats above. For example: + +``` +$ terraform import google_beyondcorp_security_gateway.default projects/{{project}}/locations/{{location}}/securityGateways/{{security_gateway_id}} +$ terraform import google_beyondcorp_security_gateway.default {{project}}/{{location}}/{{security_gateway_id}} +$ terraform import google_beyondcorp_security_gateway.default {{location}}/{{security_gateway_id}} +``` + +## User Project Overrides + +This resource supports [User Project Overrides](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference#user_project_override).