From 48ce5d4dcec67db64036fa695f18c1642f7dd07d Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Mon, 18 Sep 2023 21:57:04 +0000 Subject: [PATCH] Add Network and Subnet resources for edgenetwork (#8905) * Add Network and Subnet resources for edgenetwork * Fix lint * Added labels field to acceptance tests; Removed send_empty_value:false from vlan_id field in Subnet * Skip edgenetwork acceptance tests Signed-off-by: Modular Magician --- .changelog/8905.txt | 6 + .teamcity/components/generated/services.kt | 5 + google/fwmodels/provider_model.go | 1 + google/fwprovider/framework_provider.go | 6 + google/fwtransport/framework_config.go | 10 + google/provider/provider.go | 13 +- .../edgenetwork/edgenetwork_operation.go | 92 ++++ .../resource_edgenetwork_network.go | 389 ++++++++++++++ .../resource_edgenetwork_network_sweeper.go | 161 ++++++ .../resource_edgenetwork_subnet.go | 485 ++++++++++++++++++ .../resource_edgenetwork_subnet_sweeper.go | 161 ++++++ google/sweeper/gcp_sweeper_test.go | 1 + google/transport/config.go | 9 + .../docs/r/edgenetwork_network.html.markdown | 128 +++++ .../docs/r/edgenetwork_subnet.html.markdown | 180 +++++++ 15 files changed, 1645 insertions(+), 2 deletions(-) create mode 100644 .changelog/8905.txt create mode 100644 google/services/edgenetwork/edgenetwork_operation.go create mode 100644 google/services/edgenetwork/resource_edgenetwork_network.go create mode 100644 google/services/edgenetwork/resource_edgenetwork_network_sweeper.go create mode 100644 google/services/edgenetwork/resource_edgenetwork_subnet.go create mode 100644 google/services/edgenetwork/resource_edgenetwork_subnet_sweeper.go create mode 100644 website/docs/r/edgenetwork_network.html.markdown create mode 100644 website/docs/r/edgenetwork_subnet.html.markdown diff --git a/.changelog/8905.txt b/.changelog/8905.txt new file mode 100644 index 00000000000..abbc8ad1ac6 --- /dev/null +++ b/.changelog/8905.txt @@ -0,0 +1,6 @@ +```release-note:new-resource +edgenetwork : `google_edgenetwork_network` +``` +```release-note:new-resource +edgenetwork : `google_edgenetwork_subnet` +``` diff --git a/.teamcity/components/generated/services.kt b/.teamcity/components/generated/services.kt index 105d90b1112..03b7dc079b3 100644 --- a/.teamcity/components/generated/services.kt +++ b/.teamcity/components/generated/services.kt @@ -311,6 +311,11 @@ var services = mapOf( "displayName" to "Documentaiwarehouse", "path" to "./google/services/documentaiwarehouse" ), + "edgenetwork" to mapOf( + "name" to "edgenetwork", + "displayName" to "Edgenetwork", + "path" to "./google/services/edgenetwork" + ), "essentialcontacts" to mapOf( "name" to "essentialcontacts", "displayName" to "Essentialcontacts", diff --git a/google/fwmodels/provider_model.go b/google/fwmodels/provider_model.go index 4a7db47b4b7..2e79c52e6a2 100644 --- a/google/fwmodels/provider_model.go +++ b/google/fwmodels/provider_model.go @@ -75,6 +75,7 @@ type ProviderModel struct { DNSCustomEndpoint types.String `tfsdk:"dns_custom_endpoint"` DocumentAICustomEndpoint types.String `tfsdk:"document_ai_custom_endpoint"` DocumentAIWarehouseCustomEndpoint types.String `tfsdk:"document_ai_warehouse_custom_endpoint"` + EdgenetworkCustomEndpoint types.String `tfsdk:"edgenetwork_custom_endpoint"` EssentialContactsCustomEndpoint types.String `tfsdk:"essential_contacts_custom_endpoint"` FilestoreCustomEndpoint types.String `tfsdk:"filestore_custom_endpoint"` FirestoreCustomEndpoint types.String `tfsdk:"firestore_custom_endpoint"` diff --git a/google/fwprovider/framework_provider.go b/google/fwprovider/framework_provider.go index b327648bf36..70680cc422c 100644 --- a/google/fwprovider/framework_provider.go +++ b/google/fwprovider/framework_provider.go @@ -419,6 +419,12 @@ func (p *FrameworkProvider) Schema(_ context.Context, _ provider.SchemaRequest, transport_tpg.CustomEndpointValidator(), }, }, + "edgenetwork_custom_endpoint": &schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + transport_tpg.CustomEndpointValidator(), + }, + }, "essential_contacts_custom_endpoint": &schema.StringAttribute{ Optional: true, Validators: []validator.String{ diff --git a/google/fwtransport/framework_config.go b/google/fwtransport/framework_config.go index c98c9724b16..e55e1da497c 100644 --- a/google/fwtransport/framework_config.go +++ b/google/fwtransport/framework_config.go @@ -100,6 +100,7 @@ type FrameworkProviderConfig struct { DNSBasePath string DocumentAIBasePath string DocumentAIWarehouseBasePath string + EdgenetworkBasePath string EssentialContactsBasePath string FilestoreBasePath string FirestoreBasePath string @@ -245,6 +246,7 @@ func (p *FrameworkProviderConfig) LoadAndValidateFramework(ctx context.Context, p.DNSBasePath = data.DNSCustomEndpoint.ValueString() p.DocumentAIBasePath = data.DocumentAICustomEndpoint.ValueString() p.DocumentAIWarehouseBasePath = data.DocumentAIWarehouseCustomEndpoint.ValueString() + p.EdgenetworkBasePath = data.EdgenetworkCustomEndpoint.ValueString() p.EssentialContactsBasePath = data.EssentialContactsCustomEndpoint.ValueString() p.FilestoreBasePath = data.FilestoreCustomEndpoint.ValueString() p.FirestoreBasePath = data.FirestoreCustomEndpoint.ValueString() @@ -896,6 +898,14 @@ func (p *FrameworkProviderConfig) HandleDefaults(ctx context.Context, data *fwmo data.DocumentAIWarehouseCustomEndpoint = types.StringValue(customEndpoint.(string)) } } + if data.EdgenetworkCustomEndpoint.IsNull() { + customEndpoint := transport_tpg.MultiEnvDefault([]string{ + "GOOGLE_EDGENETWORK_CUSTOM_ENDPOINT", + }, transport_tpg.DefaultBasePaths[transport_tpg.EdgenetworkBasePathKey]) + if customEndpoint != nil { + data.EdgenetworkCustomEndpoint = types.StringValue(customEndpoint.(string)) + } + } if data.EssentialContactsCustomEndpoint.IsNull() { customEndpoint := transport_tpg.MultiEnvDefault([]string{ "GOOGLE_ESSENTIAL_CONTACTS_CUSTOM_ENDPOINT", diff --git a/google/provider/provider.go b/google/provider/provider.go index 26e4ee742dc..2dd7e09a729 100644 --- a/google/provider/provider.go +++ b/google/provider/provider.go @@ -63,6 +63,7 @@ import ( "github.com/hashicorp/terraform-provider-google/google/services/dns" "github.com/hashicorp/terraform-provider-google/google/services/documentai" "github.com/hashicorp/terraform-provider-google/google/services/documentaiwarehouse" + "github.com/hashicorp/terraform-provider-google/google/services/edgenetwork" "github.com/hashicorp/terraform-provider-google/google/services/essentialcontacts" "github.com/hashicorp/terraform-provider-google/google/services/filestore" "github.com/hashicorp/terraform-provider-google/google/services/firestore" @@ -479,6 +480,11 @@ func Provider() *schema.Provider { Optional: true, ValidateFunc: transport_tpg.ValidateCustomEndpoint, }, + "edgenetwork_custom_endpoint": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: transport_tpg.ValidateCustomEndpoint, + }, "essential_contacts_custom_endpoint": { Type: schema.TypeString, Optional: true, @@ -987,9 +993,9 @@ func DatasourceMapWithErrors() (map[string]*schema.Resource, error) { }) } -// Generated resources: 323 +// Generated resources: 325 // Generated IAM resources: 210 -// Total generated resources: 533 +// Total generated resources: 535 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -1311,6 +1317,8 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_document_ai_processor_default_version": documentai.ResourceDocumentAIProcessorDefaultVersion(), "google_document_ai_warehouse_document_schema": documentaiwarehouse.ResourceDocumentAIWarehouseDocumentSchema(), "google_document_ai_warehouse_location": documentaiwarehouse.ResourceDocumentAIWarehouseLocation(), + "google_edgenetwork_network": edgenetwork.ResourceEdgenetworkNetwork(), + "google_edgenetwork_subnet": edgenetwork.ResourceEdgenetworkSubnet(), "google_essential_contacts_contact": essentialcontacts.ResourceEssentialContactsContact(), "google_filestore_backup": filestore.ResourceFilestoreBackup(), "google_filestore_instance": filestore.ResourceFilestoreInstance(), @@ -1807,6 +1815,7 @@ func ProviderConfigure(ctx context.Context, d *schema.ResourceData, p *schema.Pr config.DNSBasePath = d.Get("dns_custom_endpoint").(string) config.DocumentAIBasePath = d.Get("document_ai_custom_endpoint").(string) config.DocumentAIWarehouseBasePath = d.Get("document_ai_warehouse_custom_endpoint").(string) + config.EdgenetworkBasePath = d.Get("edgenetwork_custom_endpoint").(string) config.EssentialContactsBasePath = d.Get("essential_contacts_custom_endpoint").(string) config.FilestoreBasePath = d.Get("filestore_custom_endpoint").(string) config.FirestoreBasePath = d.Get("firestore_custom_endpoint").(string) diff --git a/google/services/edgenetwork/edgenetwork_operation.go b/google/services/edgenetwork/edgenetwork_operation.go new file mode 100644 index 00000000000..ef9d90f1ff6 --- /dev/null +++ b/google/services/edgenetwork/edgenetwork_operation.go @@ -0,0 +1,92 @@ +// 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 edgenetwork + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +type EdgenetworkOperationWaiter struct { + Config *transport_tpg.Config + UserAgent string + Project string + tpgresource.CommonOperationWaiter +} + +func (w *EdgenetworkOperationWaiter) QueryOp() (interface{}, error) { + if w == nil { + return nil, fmt.Errorf("Cannot query operation, it's unset or nil.") + } + // Returns the proper get. + url := fmt.Sprintf("%s%s", w.Config.EdgenetworkBasePath, w.CommonOperationWaiter.Op.Name) + + return transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: w.Config, + Method: "GET", + Project: w.Project, + RawURL: url, + UserAgent: w.UserAgent, + }) +} + +func createEdgenetworkWaiter(config *transport_tpg.Config, op map[string]interface{}, project, activity, userAgent string) (*EdgenetworkOperationWaiter, error) { + w := &EdgenetworkOperationWaiter{ + Config: config, + UserAgent: userAgent, + Project: project, + } + if err := w.CommonOperationWaiter.SetOp(op); err != nil { + return nil, err + } + return w, nil +} + +// nolint: deadcode,unused +func EdgenetworkOperationWaitTimeWithResponse(config *transport_tpg.Config, op map[string]interface{}, response *map[string]interface{}, project, activity, userAgent string, timeout time.Duration) error { + w, err := createEdgenetworkWaiter(config, op, project, activity, userAgent) + if err != nil { + return err + } + if err := tpgresource.OperationWait(w, activity, timeout, config.PollInterval); err != nil { + return err + } + rawResponse := []byte(w.CommonOperationWaiter.Op.Response) + if len(rawResponse) == 0 { + return errors.New("`resource` not set in operation response") + } + return json.Unmarshal(rawResponse, response) +} + +func EdgenetworkOperationWaitTime(config *transport_tpg.Config, op map[string]interface{}, project, activity, userAgent string, timeout time.Duration) error { + if val, ok := op["name"]; !ok || val == "" { + // This was a synchronous call - there is no operation to wait for. + return nil + } + w, err := createEdgenetworkWaiter(config, op, project, activity, userAgent) + if err != nil { + // If w is nil, the op was synchronous. + return err + } + return tpgresource.OperationWait(w, activity, timeout, config.PollInterval) +} diff --git a/google/services/edgenetwork/resource_edgenetwork_network.go b/google/services/edgenetwork/resource_edgenetwork_network.go new file mode 100644 index 00000000000..1afa781f88b --- /dev/null +++ b/google/services/edgenetwork/resource_edgenetwork_network.go @@ -0,0 +1,389 @@ +// 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 edgenetwork + +import ( + "fmt" + "log" + "reflect" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func ResourceEdgenetworkNetwork() *schema.Resource { + return &schema.Resource{ + Create: resourceEdgenetworkNetworkCreate, + Read: resourceEdgenetworkNetworkRead, + Delete: resourceEdgenetworkNetworkDelete, + + Importer: &schema.ResourceImporter{ + State: resourceEdgenetworkNetworkImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The Google Cloud region to which the target Distributed Cloud Edge zone belongs.`, + }, + "network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `A unique ID that identifies this network.`, + }, + "zone": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The name of the target Distributed Cloud Edge zone.`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `A free-text description of the resource. Max length 1024 characters.`, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Description: `Labels associated with this resource.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "mtu": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Description: `IP (L3) MTU value of the network. Default value is '1500'. Possible values are: '1500', '9000'.`, + Default: 1500, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + Description: `The time when the subnet was created. +A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine +fractional digits. Examples: '2014-10-02T15:01:23Z' and '2014-10-02T15:01:23.045123456Z'.`, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The canonical name of this resource, with format +'projects/{{project}}/locations/{{location}}/zones/{{zone}}/networks/{{network_id}}'`, + }, + "update_time": { + Type: schema.TypeString, + Computed: true, + Description: `The time when the subnet was last updated. +A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine +fractional digits. Examples: '2014-10-02T15:01:23Z' and '2014-10-02T15:01:23.045123456Z'.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func resourceEdgenetworkNetworkCreate(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{}) + labelsProp, err := expandEdgenetworkNetworkLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !tpgresource.IsEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + descriptionProp, err := expandEdgenetworkNetworkDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !tpgresource.IsEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + mtuProp, err := expandEdgenetworkNetworkMtu(d.Get("mtu"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("mtu"); !tpgresource.IsEmptyValue(reflect.ValueOf(mtuProp)) && (ok || !reflect.DeepEqual(v, mtuProp)) { + obj["mtu"] = mtuProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{EdgenetworkBasePath}}projects/{{project}}/locations/{{location}}/zones/{{zone}}/networks?networkId={{network_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Network: %#v", obj) + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for Network: %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 + } + + 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), + }) + if err != nil { + return fmt.Errorf("Error creating Network: %s", err) + } + + // Store the ID now + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/zones/{{zone}}/networks/{{network_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + err = EdgenetworkOperationWaitTime( + config, res, project, "Creating Network", userAgent, + d.Timeout(schema.TimeoutCreate)) + + if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create Network: %s", err) + } + + log.Printf("[DEBUG] Finished creating Network %q: %#v", d.Id(), res) + + return resourceEdgenetworkNetworkRead(d, meta) +} + +func resourceEdgenetworkNetworkRead(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, "{{EdgenetworkBasePath}}projects/{{project}}/locations/{{location}}/zones/{{zone}}/networks/{{network_id}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for Network: %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 + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("EdgenetworkNetwork %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading Network: %s", err) + } + + if err := d.Set("name", flattenEdgenetworkNetworkName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading Network: %s", err) + } + if err := d.Set("labels", flattenEdgenetworkNetworkLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading Network: %s", err) + } + if err := d.Set("description", flattenEdgenetworkNetworkDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading Network: %s", err) + } + if err := d.Set("create_time", flattenEdgenetworkNetworkCreateTime(res["createTime"], d, config)); err != nil { + return fmt.Errorf("Error reading Network: %s", err) + } + if err := d.Set("update_time", flattenEdgenetworkNetworkUpdateTime(res["updateTime"], d, config)); err != nil { + return fmt.Errorf("Error reading Network: %s", err) + } + if err := d.Set("mtu", flattenEdgenetworkNetworkMtu(res["mtu"], d, config)); err != nil { + return fmt.Errorf("Error reading Network: %s", err) + } + + return nil +} + +func resourceEdgenetworkNetworkDelete(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 Network: %s", err) + } + billingProject = project + + url, err := tpgresource.ReplaceVars(d, config, "{{EdgenetworkBasePath}}projects/{{project}}/locations/{{location}}/zones/{{zone}}/networks/{{network_id}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting Network %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + 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), + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, "Network") + } + + err = EdgenetworkOperationWaitTime( + config, res, project, "Deleting Network", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting Network %q: %#v", d.Id(), res) + return nil +} + +func resourceEdgenetworkNetworkImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*transport_tpg.Config) + if err := tpgresource.ParseImportId([]string{ + "projects/(?P[^/]+)/locations/(?P[^/]+)/zones/(?P[^/]+)/networks/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + "(?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}}/zones/{{zone}}/networks/{{network_id}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenEdgenetworkNetworkName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenEdgenetworkNetworkLabels(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenEdgenetworkNetworkDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenEdgenetworkNetworkCreateTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenEdgenetworkNetworkUpdateTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenEdgenetworkNetworkMtu(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := tpgresource.StringToFixed64(strVal); err == nil { + return intVal + } + } + + // number values are represented as float64 + if floatVal, ok := v.(float64); ok { + intVal := int(floatVal) + return intVal + } + + return v // let terraform core handle it otherwise +} + +func expandEdgenetworkNetworkLabels(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandEdgenetworkNetworkDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandEdgenetworkNetworkMtu(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} diff --git a/google/services/edgenetwork/resource_edgenetwork_network_sweeper.go b/google/services/edgenetwork/resource_edgenetwork_network_sweeper.go new file mode 100644 index 00000000000..4ec70cf2a70 --- /dev/null +++ b/google/services/edgenetwork/resource_edgenetwork_network_sweeper.go @@ -0,0 +1,161 @@ +// 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 edgenetwork + +import ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-provider-google/google/envvar" + "github.com/hashicorp/terraform-provider-google/google/sweeper" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func init() { + sweeper.AddTestSweepers("EdgenetworkNetwork", testSweepEdgenetworkNetwork) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepEdgenetworkNetwork(region string) error { + resourceName := "EdgenetworkNetwork" + 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://edgenetwork.googleapis.com/v1/projects/{{project}}/locations/{{location}}/aggregated/networks", "?")[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["networks"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + var rl []interface{} + zones := resourceList.(map[string]interface{}) + // Loop through every zone in the list response + for _, zonesValue := range zones { + zone := zonesValue.(map[string]interface{}) + for k, v := range zone { + // Zone map either has resources or a warning stating there were no resources found in the zone + if k != "warning" { + resourcesInZone := v.([]interface{}) + rl = append(rl, resourcesInZone...) + } + } + } + + 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://edgenetwork.googleapis.com/v1/projects/{{project}}/locations/{{location}}/zones/{{zone}}/networks/{{network_id}}" + if obj["zone"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource zone was nil", resourceName) + return nil + } + zone := tpgresource.GetResourceNameFromSelfLink(obj["zone"].(string)) + deleteTemplate = strings.Replace(deleteTemplate, "{{zone}}", zone, -1) + + 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/services/edgenetwork/resource_edgenetwork_subnet.go b/google/services/edgenetwork/resource_edgenetwork_subnet.go new file mode 100644 index 00000000000..793de68a57c --- /dev/null +++ b/google/services/edgenetwork/resource_edgenetwork_subnet.go @@ -0,0 +1,485 @@ +// 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 edgenetwork + +import ( + "fmt" + "log" + "reflect" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func ResourceEdgenetworkSubnet() *schema.Resource { + return &schema.Resource{ + Create: resourceEdgenetworkSubnetCreate, + Read: resourceEdgenetworkSubnetRead, + Delete: resourceEdgenetworkSubnetDelete, + + Importer: &schema.ResourceImporter{ + State: resourceEdgenetworkSubnetImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The Google Cloud region to which the target Distributed Cloud Edge zone belongs.`, + }, + "network": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, + Description: `The ID of the network to which this router belongs. +Must be of the form: 'projects/{{project}}/locations/{{location}}/zones/{{zone}}/networks/{{network_id}}'`, + }, + "subnet_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `A unique ID that identifies this subnet.`, + }, + "zone": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The name of the target Distributed Cloud Edge zone.`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `A free-text description of the resource. Max length 1024 characters.`, + }, + "ipv4_cidr": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `The ranges of ipv4 addresses that are owned by this subnetwork, in CIDR format.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "ipv6_cidr": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `The ranges of ipv6 addresses that are owned by this subnetwork, in CIDR format.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Description: `Labels associated with this resource.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "vlan_id": { + Type: schema.TypeInt, + Computed: true, + Optional: true, + ForceNew: true, + Description: `VLAN ID for this subnetwork. If not specified, one is assigned automatically.`, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + Description: `The time when the subnet was created. +A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine +fractional digits. Examples: '2014-10-02T15:01:23Z' and '2014-10-02T15:01:23.045123456Z'.`, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The canonical name of this resource, with format +'projects/{{project}}/locations/{{location}}/zones/{{zone}}/subnets/{{subnet_id}}'`, + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: `Current stage of the resource to the device by config push.`, + }, + "update_time": { + Type: schema.TypeString, + Computed: true, + Description: `The time when the subnet was last updated. +A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine +fractional digits. Examples: '2014-10-02T15:01:23Z' and '2014-10-02T15:01:23.045123456Z'.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func resourceEdgenetworkSubnetCreate(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{}) + labelsProp, err := expandEdgenetworkSubnetLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !tpgresource.IsEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + descriptionProp, err := expandEdgenetworkSubnetDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !tpgresource.IsEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + networkProp, err := expandEdgenetworkSubnetNetwork(d.Get("network"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("network"); !tpgresource.IsEmptyValue(reflect.ValueOf(networkProp)) && (ok || !reflect.DeepEqual(v, networkProp)) { + obj["network"] = networkProp + } + ipv4CidrProp, err := expandEdgenetworkSubnetIpv4Cidr(d.Get("ipv4_cidr"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("ipv4_cidr"); !tpgresource.IsEmptyValue(reflect.ValueOf(ipv4CidrProp)) && (ok || !reflect.DeepEqual(v, ipv4CidrProp)) { + obj["ipv4Cidr"] = ipv4CidrProp + } + ipv6CidrProp, err := expandEdgenetworkSubnetIpv6Cidr(d.Get("ipv6_cidr"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("ipv6_cidr"); !tpgresource.IsEmptyValue(reflect.ValueOf(ipv6CidrProp)) && (ok || !reflect.DeepEqual(v, ipv6CidrProp)) { + obj["ipv6Cidr"] = ipv6CidrProp + } + vlanIdProp, err := expandEdgenetworkSubnetVlanId(d.Get("vlan_id"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("vlan_id"); !tpgresource.IsEmptyValue(reflect.ValueOf(vlanIdProp)) && (ok || !reflect.DeepEqual(v, vlanIdProp)) { + obj["vlanId"] = vlanIdProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{EdgenetworkBasePath}}projects/{{project}}/locations/{{location}}/zones/{{zone}}/subnets?subnetId={{subnet_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Subnet: %#v", obj) + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for Subnet: %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 + } + + 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), + }) + if err != nil { + return fmt.Errorf("Error creating Subnet: %s", err) + } + + // Store the ID now + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/zones/{{zone}}/subnets/{{subnet_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + err = EdgenetworkOperationWaitTime( + config, res, project, "Creating Subnet", userAgent, + d.Timeout(schema.TimeoutCreate)) + + if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create Subnet: %s", err) + } + + log.Printf("[DEBUG] Finished creating Subnet %q: %#v", d.Id(), res) + + return resourceEdgenetworkSubnetRead(d, meta) +} + +func resourceEdgenetworkSubnetRead(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, "{{EdgenetworkBasePath}}projects/{{project}}/locations/{{location}}/zones/{{zone}}/subnets/{{subnet_id}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for Subnet: %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 + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("EdgenetworkSubnet %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading Subnet: %s", err) + } + + if err := d.Set("name", flattenEdgenetworkSubnetName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading Subnet: %s", err) + } + if err := d.Set("labels", flattenEdgenetworkSubnetLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading Subnet: %s", err) + } + if err := d.Set("description", flattenEdgenetworkSubnetDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading Subnet: %s", err) + } + if err := d.Set("create_time", flattenEdgenetworkSubnetCreateTime(res["createTime"], d, config)); err != nil { + return fmt.Errorf("Error reading Subnet: %s", err) + } + if err := d.Set("update_time", flattenEdgenetworkSubnetUpdateTime(res["updateTime"], d, config)); err != nil { + return fmt.Errorf("Error reading Subnet: %s", err) + } + if err := d.Set("network", flattenEdgenetworkSubnetNetwork(res["network"], d, config)); err != nil { + return fmt.Errorf("Error reading Subnet: %s", err) + } + if err := d.Set("ipv4_cidr", flattenEdgenetworkSubnetIpv4Cidr(res["ipv4Cidr"], d, config)); err != nil { + return fmt.Errorf("Error reading Subnet: %s", err) + } + if err := d.Set("ipv6_cidr", flattenEdgenetworkSubnetIpv6Cidr(res["ipv6Cidr"], d, config)); err != nil { + return fmt.Errorf("Error reading Subnet: %s", err) + } + if err := d.Set("vlan_id", flattenEdgenetworkSubnetVlanId(res["vlanId"], d, config)); err != nil { + return fmt.Errorf("Error reading Subnet: %s", err) + } + if err := d.Set("state", flattenEdgenetworkSubnetState(res["state"], d, config)); err != nil { + return fmt.Errorf("Error reading Subnet: %s", err) + } + + return nil +} + +func resourceEdgenetworkSubnetDelete(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 Subnet: %s", err) + } + billingProject = project + + url, err := tpgresource.ReplaceVars(d, config, "{{EdgenetworkBasePath}}projects/{{project}}/locations/{{location}}/zones/{{zone}}/subnets/{{subnet_id}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting Subnet %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + 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), + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, "Subnet") + } + + err = EdgenetworkOperationWaitTime( + config, res, project, "Deleting Subnet", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting Subnet %q: %#v", d.Id(), res) + return nil +} + +func resourceEdgenetworkSubnetImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*transport_tpg.Config) + if err := tpgresource.ParseImportId([]string{ + "projects/(?P[^/]+)/locations/(?P[^/]+)/zones/(?P[^/]+)/subnets/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + "(?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}}/zones/{{zone}}/subnets/{{subnet_id}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenEdgenetworkSubnetName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenEdgenetworkSubnetLabels(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenEdgenetworkSubnetDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenEdgenetworkSubnetCreateTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenEdgenetworkSubnetUpdateTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenEdgenetworkSubnetNetwork(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + return tpgresource.ConvertSelfLinkToV1(v.(string)) +} + +func flattenEdgenetworkSubnetIpv4Cidr(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenEdgenetworkSubnetIpv6Cidr(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenEdgenetworkSubnetVlanId(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := tpgresource.StringToFixed64(strVal); err == nil { + return intVal + } + } + + // number values are represented as float64 + if floatVal, ok := v.(float64); ok { + intVal := int(floatVal) + return intVal + } + + return v // let terraform core handle it otherwise +} + +func flattenEdgenetworkSubnetState(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func expandEdgenetworkSubnetLabels(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandEdgenetworkSubnetDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandEdgenetworkSubnetNetwork(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + f, err := tpgresource.ParseZonalFieldValue("networks", v.(string), "project", "zone", d, config, true) + if err != nil { + return nil, fmt.Errorf("Invalid value for network: %s", err) + } + return f.RelativeLink(), nil +} + +func expandEdgenetworkSubnetIpv4Cidr(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandEdgenetworkSubnetIpv6Cidr(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandEdgenetworkSubnetVlanId(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} diff --git a/google/services/edgenetwork/resource_edgenetwork_subnet_sweeper.go b/google/services/edgenetwork/resource_edgenetwork_subnet_sweeper.go new file mode 100644 index 00000000000..eaa091aa9b4 --- /dev/null +++ b/google/services/edgenetwork/resource_edgenetwork_subnet_sweeper.go @@ -0,0 +1,161 @@ +// 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 edgenetwork + +import ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-provider-google/google/envvar" + "github.com/hashicorp/terraform-provider-google/google/sweeper" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func init() { + sweeper.AddTestSweepers("EdgenetworkSubnet", testSweepEdgenetworkSubnet) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepEdgenetworkSubnet(region string) error { + resourceName := "EdgenetworkSubnet" + 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://edgenetwork.googleapis.com/v1/projects/{{project}}/locations/{{location}}/aggregated/subnets", "?")[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["subnets"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + var rl []interface{} + zones := resourceList.(map[string]interface{}) + // Loop through every zone in the list response + for _, zonesValue := range zones { + zone := zonesValue.(map[string]interface{}) + for k, v := range zone { + // Zone map either has resources or a warning stating there were no resources found in the zone + if k != "warning" { + resourcesInZone := v.([]interface{}) + rl = append(rl, resourcesInZone...) + } + } + } + + 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://edgenetwork.googleapis.com/v1/projects/{{project}}/locations/{{location}}/zones/{{zone}}/subnets/{{subnet_id}}" + if obj["zone"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource zone was nil", resourceName) + return nil + } + zone := tpgresource.GetResourceNameFromSelfLink(obj["zone"].(string)) + deleteTemplate = strings.Replace(deleteTemplate, "{{zone}}", zone, -1) + + 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/sweeper/gcp_sweeper_test.go b/google/sweeper/gcp_sweeper_test.go index 8e2a805384a..dc50a90d4dd 100644 --- a/google/sweeper/gcp_sweeper_test.go +++ b/google/sweeper/gcp_sweeper_test.go @@ -58,6 +58,7 @@ import ( _ "github.com/hashicorp/terraform-provider-google/google/services/dns" _ "github.com/hashicorp/terraform-provider-google/google/services/documentai" _ "github.com/hashicorp/terraform-provider-google/google/services/documentaiwarehouse" + _ "github.com/hashicorp/terraform-provider-google/google/services/edgenetwork" _ "github.com/hashicorp/terraform-provider-google/google/services/essentialcontacts" _ "github.com/hashicorp/terraform-provider-google/google/services/filestore" _ "github.com/hashicorp/terraform-provider-google/google/services/firestore" diff --git a/google/transport/config.go b/google/transport/config.go index 0dd69ec4b84..c132c7c314e 100644 --- a/google/transport/config.go +++ b/google/transport/config.go @@ -233,6 +233,7 @@ type Config struct { DNSBasePath string DocumentAIBasePath string DocumentAIWarehouseBasePath string + EdgenetworkBasePath string EssentialContactsBasePath string FilestoreBasePath string FirestoreBasePath string @@ -352,6 +353,7 @@ const DialogflowCXBasePathKey = "DialogflowCX" const DNSBasePathKey = "DNS" const DocumentAIBasePathKey = "DocumentAI" const DocumentAIWarehouseBasePathKey = "DocumentAIWarehouse" +const EdgenetworkBasePathKey = "Edgenetwork" const EssentialContactsBasePathKey = "EssentialContacts" const FilestoreBasePathKey = "Filestore" const FirestoreBasePathKey = "Firestore" @@ -465,6 +467,7 @@ var DefaultBasePaths = map[string]string{ DNSBasePathKey: "https://dns.googleapis.com/dns/v1/", DocumentAIBasePathKey: "https://{{location}}-documentai.googleapis.com/v1/", DocumentAIWarehouseBasePathKey: "https://contentwarehouse.googleapis.com/v1/", + EdgenetworkBasePathKey: "https://edgenetwork.googleapis.com/v1/", EssentialContactsBasePathKey: "https://essentialcontacts.googleapis.com/v1/", FilestoreBasePathKey: "https://file.googleapis.com/v1/", FirestoreBasePathKey: "https://firestore.googleapis.com/v1/", @@ -845,6 +848,11 @@ func HandleSDKDefaults(d *schema.ResourceData) error { "GOOGLE_DOCUMENT_AI_WAREHOUSE_CUSTOM_ENDPOINT", }, DefaultBasePaths[DocumentAIWarehouseBasePathKey])) } + if d.Get("edgenetwork_custom_endpoint") == "" { + d.Set("edgenetwork_custom_endpoint", MultiEnvDefault([]string{ + "GOOGLE_EDGENETWORK_CUSTOM_ENDPOINT", + }, DefaultBasePaths[EdgenetworkBasePathKey])) + } if d.Get("essential_contacts_custom_endpoint") == "" { d.Set("essential_contacts_custom_endpoint", MultiEnvDefault([]string{ "GOOGLE_ESSENTIAL_CONTACTS_CUSTOM_ENDPOINT", @@ -1971,6 +1979,7 @@ func ConfigureBasePaths(c *Config) { c.DNSBasePath = DefaultBasePaths[DNSBasePathKey] c.DocumentAIBasePath = DefaultBasePaths[DocumentAIBasePathKey] c.DocumentAIWarehouseBasePath = DefaultBasePaths[DocumentAIWarehouseBasePathKey] + c.EdgenetworkBasePath = DefaultBasePaths[EdgenetworkBasePathKey] c.EssentialContactsBasePath = DefaultBasePaths[EssentialContactsBasePathKey] c.FilestoreBasePath = DefaultBasePaths[FilestoreBasePathKey] c.FirestoreBasePath = DefaultBasePaths[FirestoreBasePathKey] diff --git a/website/docs/r/edgenetwork_network.html.markdown b/website/docs/r/edgenetwork_network.html.markdown new file mode 100644 index 00000000000..fffb5d1d2d9 --- /dev/null +++ b/website/docs/r/edgenetwork_network.html.markdown @@ -0,0 +1,128 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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: "Distributed Cloud Edge Network" +description: |- + A Distributed Cloud Edge network, which provides L3 isolation within a zone. +--- + +# google\_edgenetwork\_network + +A Distributed Cloud Edge network, which provides L3 isolation within a zone. + + +To get more information about Network, see: + +* [API documentation](https://cloud.google.com/distributed-cloud/edge/latest/docs/reference/network/rest/v1/projects.locations.zones.networks) +* How-to Guides + * [Create and manage networks](https://cloud.google.com/distributed-cloud/edge/latest/docs/networks#api) + +## Example Usage - Edgenetwork Network + + +```hcl +resource "google_edgenetwork_network" "example_network" { + network_id = "example-network" + location = "us-west1" + zone = "" + description = "Example network." + mtu = 9000 + labels = { + "environment" : "dev" + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `location` - + (Required) + The Google Cloud region to which the target Distributed Cloud Edge zone belongs. + +* `zone` - + (Required) + The name of the target Distributed Cloud Edge zone. + +* `network_id` - + (Required) + A unique ID that identifies this network. + + +- - - + + +* `labels` - + (Optional) + Labels associated with this resource. + +* `description` - + (Optional) + A free-text description of the resource. Max length 1024 characters. + +* `mtu` - + (Optional) + IP (L3) MTU value of the network. Default value is `1500`. Possible values are: `1500`, `9000`. + +* `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}}/locations/{{location}}/zones/{{zone}}/networks/{{network_id}}` + +* `name` - + The canonical name of this resource, with format + `projects/{{project}}/locations/{{location}}/zones/{{zone}}/networks/{{network_id}}` + +* `create_time` - + The time when the subnet was created. + A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine + fractional digits. Examples: `2014-10-02T15:01:23Z` and `2014-10-02T15:01:23.045123456Z`. + +* `update_time` - + The time when the subnet was last updated. + A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine + fractional digits. Examples: `2014-10-02T15:01:23Z` and `2014-10-02T15:01:23.045123456Z`. + + +## 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. +- `delete` - Default is 30 minutes. + +## Import + + +Network can be imported using any of these accepted formats: + +``` +$ terraform import google_edgenetwork_network.default projects/{{project}}/locations/{{location}}/zones/{{zone}}/networks/{{network_id}} +$ terraform import google_edgenetwork_network.default {{project}}/{{location}}/{{zone}}/{{network_id}} +$ terraform import google_edgenetwork_network.default {{location}}/{{zone}}/{{network_id}} +$ terraform import google_edgenetwork_network.default {{location}}/{{network_id}} +$ terraform import google_edgenetwork_network.default {{name}} +``` + +## User Project Overrides + +This resource supports [User Project Overrides](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference#user_project_override). diff --git a/website/docs/r/edgenetwork_subnet.html.markdown b/website/docs/r/edgenetwork_subnet.html.markdown new file mode 100644 index 00000000000..b39980d0fbb --- /dev/null +++ b/website/docs/r/edgenetwork_subnet.html.markdown @@ -0,0 +1,180 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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: "Distributed Cloud Edge Network" +description: |- + A Distributed Cloud Edge subnet, which provides L2 isolation within a network. +--- + +# google\_edgenetwork\_subnet + +A Distributed Cloud Edge subnet, which provides L2 isolation within a network. + + +To get more information about Subnet, see: + +* [API documentation](https://cloud.google.com/distributed-cloud/edge/latest/docs/reference/network/rest/v1/projects.locations.zones.subnets) +* How-to Guides + * [Create and manage subnetworks](https://cloud.google.com/distributed-cloud/edge/latest/docs/subnetworks#api) + +## Example Usage - Edgenetwork Subnet + + +```hcl + +resource "google_edgenetwork_subnet" "example_subnet" { + subnet_id = "example-subnet" + location = "us-west1" + zone = "" + description = "Example subnet." + network = google_edgenetwork_network.example_network.id + ipv4_cidr = ["4.4.4.1/24"] + labels = { + "environment" : "dev" + } +} + +resource "google_edgenetwork_network" "example_network" { + network_id = "example-network" + location = "us-west1" + zone = "" + description = "Example network." + mtu = 9000 +} +``` +## Example Usage - Edgenetwork Subnet With Vlan Id + + +```hcl + +resource "google_edgenetwork_subnet" "example_subnet_with_vlan_id" { + subnet_id = "example-subnet-with-vlan-id" + location = "us-west1" + zone = "" + description = "Example subnet with VLAN ID." + network = google_edgenetwork_network.example_network.id + ipv6_cidr = ["4444:4444:4444:4444::1/64"] + vlan_id = 44 + labels = { + "environment" : "dev" + } +} + +resource "google_edgenetwork_network" "example_network" { + network_id = "example-network" + location = "us-west1" + zone = "" + description = "Example network." + mtu = 9000 +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `network` - + (Required) + The ID of the network to which this router belongs. + Must be of the form: `projects/{{project}}/locations/{{location}}/zones/{{zone}}/networks/{{network_id}}` + +* `location` - + (Required) + The Google Cloud region to which the target Distributed Cloud Edge zone belongs. + +* `zone` - + (Required) + The name of the target Distributed Cloud Edge zone. + +* `subnet_id` - + (Required) + A unique ID that identifies this subnet. + + +- - - + + +* `labels` - + (Optional) + Labels associated with this resource. + +* `description` - + (Optional) + A free-text description of the resource. Max length 1024 characters. + +* `ipv4_cidr` - + (Optional) + The ranges of ipv4 addresses that are owned by this subnetwork, in CIDR format. + +* `ipv6_cidr` - + (Optional) + The ranges of ipv6 addresses that are owned by this subnetwork, in CIDR format. + +* `vlan_id` - + (Optional) + VLAN ID for this subnetwork. If not specified, one is assigned automatically. + +* `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}}/locations/{{location}}/zones/{{zone}}/subnets/{{subnet_id}}` + +* `name` - + The canonical name of this resource, with format + `projects/{{project}}/locations/{{location}}/zones/{{zone}}/subnets/{{subnet_id}}` + +* `create_time` - + The time when the subnet was created. + A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine + fractional digits. Examples: `2014-10-02T15:01:23Z` and `2014-10-02T15:01:23.045123456Z`. + +* `update_time` - + The time when the subnet was last updated. + A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine + fractional digits. Examples: `2014-10-02T15:01:23Z` and `2014-10-02T15:01:23.045123456Z`. + +* `state` - + Current stage of the resource to the device by config push. + + +## 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. +- `delete` - Default is 30 minutes. + +## Import + + +Subnet can be imported using any of these accepted formats: + +``` +$ terraform import google_edgenetwork_subnet.default projects/{{project}}/locations/{{location}}/zones/{{zone}}/subnets/{{subnet_id}} +$ terraform import google_edgenetwork_subnet.default {{project}}/{{location}}/{{zone}}/{{subnet_id}} +$ terraform import google_edgenetwork_subnet.default {{location}}/{{zone}}/{{subnet_id}} +$ terraform import google_edgenetwork_subnet.default {{location}}/{{subnet_id}} +$ terraform import google_edgenetwork_subnet.default {{name}} +``` + +## User Project Overrides + +This resource supports [User Project Overrides](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference#user_project_override).