From 04604c3b2140bf5a71e5847682e8e2223f4bf26a Mon Sep 17 00:00:00 2001 From: The Magician Date: Thu, 9 May 2024 06:46:53 -0700 Subject: [PATCH] Add Terraform definition and tests for Interconnect resources. (#10577) (#18064) [upstream:9947898b274c9a3e5e1b972854931abf9e163e90] Signed-off-by: Modular Magician --- google/provider/provider_mmv1_resources.go | 5 +- .../compute/resource_compute_interconnect.go | 1215 +++++++++++++++++ ...rce_compute_interconnect_generated_test.go | 115 ++ .../resource_compute_interconnect_sweeper.go | 139 ++ .../docs/r/compute_interconnect.html.markdown | 365 +++++ 5 files changed, 1837 insertions(+), 2 deletions(-) create mode 100644 google/services/compute/resource_compute_interconnect.go create mode 100644 google/services/compute/resource_compute_interconnect_generated_test.go create mode 100644 google/services/compute/resource_compute_interconnect_sweeper.go create mode 100644 website/docs/r/compute_interconnect.html.markdown diff --git a/google/provider/provider_mmv1_resources.go b/google/provider/provider_mmv1_resources.go index fdac8411514..579582380b0 100644 --- a/google/provider/provider_mmv1_resources.go +++ b/google/provider/provider_mmv1_resources.go @@ -405,9 +405,9 @@ var handwrittenIAMDatasources = map[string]*schema.Resource{ } // Resources -// Generated resources: 410 +// Generated resources: 411 // Generated IAM resources: 234 -// Total generated resources: 644 +// Total generated resources: 645 var generatedResources = map[string]*schema.Resource{ "google_folder_access_approval_settings": accessapproval.ResourceAccessApprovalFolderSettings(), "google_organization_access_approval_settings": accessapproval.ResourceAccessApprovalOrganizationSettings(), @@ -597,6 +597,7 @@ var generatedResources = map[string]*schema.Resource{ "google_compute_instance_group_membership": compute.ResourceComputeInstanceGroupMembership(), "google_compute_instance_group_named_port": compute.ResourceComputeInstanceGroupNamedPort(), "google_compute_instance_settings": compute.ResourceComputeInstanceSettings(), + "google_compute_interconnect": compute.ResourceComputeInterconnect(), "google_compute_interconnect_attachment": compute.ResourceComputeInterconnectAttachment(), "google_compute_managed_ssl_certificate": compute.ResourceComputeManagedSslCertificate(), "google_compute_network": compute.ResourceComputeNetwork(), diff --git a/google/services/compute/resource_compute_interconnect.go b/google/services/compute/resource_compute_interconnect.go new file mode 100644 index 00000000000..711f4395779 --- /dev/null +++ b/google/services/compute/resource_compute_interconnect.go @@ -0,0 +1,1215 @@ +// 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 compute + +import ( + "fmt" + "log" + "net/http" + "reflect" + "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/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" + "github.com/hashicorp/terraform-provider-google/google/verify" +) + +func ResourceComputeInterconnect() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeInterconnectCreate, + Read: resourceComputeInterconnectRead, + Update: resourceComputeInterconnectUpdate, + Delete: resourceComputeInterconnectDelete, + + Importer: &schema.ResourceImporter{ + State: resourceComputeInterconnectImport, + }, + + 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.SetLabelsDiff, + tpgresource.DefaultProviderProject, + ), + + Schema: map[string]*schema.Schema{ + "customer_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Customer name, to put in the Letter of Authorization as the party authorized to request a +crossconnect.`, + }, + "interconnect_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidateEnum([]string{"DEDICATED", "PARTNER", "IT_PRIVATE"}), + Description: `Type of interconnect. Note that a value IT_PRIVATE has been deprecated in favor of DEDICATED. +Can take one of the following values: + - PARTNER: A partner-managed interconnection shared between customers though a partner. + - DEDICATED: A dedicated physical interconnection with the customer. Possible values: ["DEDICATED", "PARTNER", "IT_PRIVATE"]`, + }, + "link_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidateEnum([]string{"LINK_TYPE_ETHERNET_10G_LR", "LINK_TYPE_ETHERNET_100G_LR"}), + Description: `Type of link requested. Note that this field indicates the speed of each of the links in the +bundle, not the speed of the entire bundle. Can take one of the following values: + - LINK_TYPE_ETHERNET_10G_LR: A 10G Ethernet with LR optics. + - LINK_TYPE_ETHERNET_100G_LR: A 100G Ethernet with LR optics. Possible values: ["LINK_TYPE_ETHERNET_10G_LR", "LINK_TYPE_ETHERNET_100G_LR"]`, + }, + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, + Description: `URL of the InterconnectLocation object that represents where this connection is to be provisioned.`, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidateRegexp(`^[a-z]([-a-z0-9]*[a-z0-9])?$`), + Description: `Name of the resource. Provided by the client when the resource is created. The name must be +1-63 characters long, and comply with RFC1035. Specifically, the name must be 1-63 characters +long and match the regular expression '[a-z]([-a-z0-9]*[a-z0-9])?' which means the first +character must be a lowercase letter, and all following characters must be a dash, +lowercase letter, or digit, except the last character, which cannot be a dash.`, + }, + "requested_link_count": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: `Target number of physical links in the link bundle, as requested by the customer.`, + }, + "admin_enabled": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Description: `Administrative status of the interconnect. When this is set to true, the Interconnect is +functional and can carry traffic. When set to false, no packets can be carried over the +interconnect and no BGP routes are exchanged over it. By default, the status is set to true.`, + Default: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `An optional description of this resource. Provide this property when you create the resource.`, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Description: `Labels for this resource. These can only be added or modified by the setLabels +method. Each label key/value pair must comply with RFC1035. Label values may be empty. + + +**Note**: This field is non-authoritative, and will only manage the labels present in your configuration. +Please refer to the field 'effective_labels' for all of the labels present on the resource.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "macsec": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `Configuration that enables Media Access Control security (MACsec) on the Cloud +Interconnect connection between Google and your on-premises router.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "pre_shared_keys": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Description: `A keychain placeholder describing a set of named key objects along with their +start times. A MACsec CKN/CAK is generated for each key in the key chain. +Google router automatically picks the key with the most recent startTime when establishing +or re-establishing a MACsec secure link.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidateRegexp(`^[a-z]([-a-z0-9]*[a-z0-9])?$`), + Description: `A name for this pre-shared key. The name must be 1-63 characters long, and + comply with RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression '[a-z]([-a-z0-9]*[a-z0-9])?' which means the first character + must be a lowercase letter, and all following characters must be a dash, lowercase + letter, or digit, except the last character, which cannot be a dash.`, + }, + "fail_open": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Description: `If set to true, the Interconnect connection is configured with a should-secure +MACsec security policy, that allows the Google router to fallback to cleartext +traffic if the MKA session cannot be established. By default, the Interconnect +connection is configured with a must-secure security policy that drops all traffic +if the MKA session cannot be established with your router.`, + }, + "start_time": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `A RFC3339 timestamp on or after which the key is valid. startTime can be in the +future. If the keychain has a single key, startTime can be omitted. If the keychain +has multiple keys, startTime is mandatory for each key. The start times of keys must +be in increasing order. The start times of two consecutive keys must be at least 6 +hours apart.`, + }, + }, + }, + }, + }, + }, + }, + "macsec_enabled": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Description: `Enable or disable MACsec on this Interconnect connection. +MACsec enablement fails if the MACsec object is not specified.`, + }, + "noc_contact_email": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `Email address to contact the customer NOC for operations and maintenance notifications +regarding this Interconnect. If specified, this will be used for notifications in addition to +all other forms described, such as Cloud Monitoring logs alerting and Cloud Notifications. +This field is required for users who sign up for Cloud Interconnect using workforce identity +federation.`, + }, + "remote_location": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `Indicates that this is a Cross-Cloud Interconnect. This field specifies the location outside +of Google's network that the interconnect is connected to.`, + }, + "requested_features": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `interconnects.list of features requested for this Interconnect connection. Options: MACSEC ( +If specified then the connection is created on MACsec capable hardware ports. If not +specified, the default value is false, which allocates non-MACsec capable ports first if +available). Possible values: ["MACSEC"]`, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: verify.ValidateEnum([]string{"MACSEC"}), + }, + }, + "available_features": { + Type: schema.TypeList, + Computed: true, + Description: `interconnects.list of features available for this Interconnect connection. Can take the value: +MACSEC. If present then the Interconnect connection is provisioned on MACsec capable hardware +ports. If not present then the Interconnect connection is provisioned on non-MACsec capable +ports and MACsec isn't supported and enabling MACsec fails).`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "circuit_infos": { + Type: schema.TypeList, + Computed: true, + Description: `A list of CircuitInfo objects, that describe the individual circuits in this LAG.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "customer_demarc_id": { + Type: schema.TypeString, + Computed: true, + Description: `Customer-side demarc ID for this circuit.`, + }, + "google_circuit_id": { + Type: schema.TypeString, + Computed: true, + Description: `Google-assigned unique ID for this circuit. Assigned at circuit turn-up.`, + }, + "google_demarc_id": { + Type: schema.TypeString, + Computed: true, + Description: `Google-side demarc ID for this circuit. Assigned at circuit turn-up and provided by +Google to the customer in the LOA.`, + }, + }, + }, + }, + "creation_timestamp": { + Type: schema.TypeString, + Computed: true, + Description: `Creation timestamp in RFC3339 text format.`, + }, + "effective_labels": { + Type: schema.TypeMap, + Computed: true, + ForceNew: true, + Description: `All of labels (key/value pairs) present on the resource in GCP, including the labels configured through Terraform, other clients and services.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "expected_outages": { + Type: schema.TypeList, + Computed: true, + Description: `A list of outages expected for this Interconnect.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "affected_circuits": { + Type: schema.TypeList, + Computed: true, + Description: `If issueType is IT_PARTIAL_OUTAGE, a list of the Google-side circuit IDs that will be +affected.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: `A description about the purpose of the outage.`, + }, + "end_time": { + Type: schema.TypeString, + Computed: true, + Description: `Scheduled end time for the outage (milliseconds since Unix epoch).`, + }, + "issue_type": { + Type: schema.TypeString, + Computed: true, + Description: `Form this outage is expected to take. Note that the versions of this enum prefixed with +"IT_" have been deprecated in favor of the unprefixed values. Can take one of the +following values: + - OUTAGE: The Interconnect may be completely out of service for some or all of the + specified window. + - PARTIAL_OUTAGE: Some circuits comprising the Interconnect as a whole should remain + up, but with reduced bandwidth.`, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `Unique identifier for this outage notification.`, + }, + "source": { + Type: schema.TypeString, + Computed: true, + Description: `The party that generated this notification. Note that the value of NSRC_GOOGLE has been +deprecated in favor of GOOGLE. Can take the following value: + - GOOGLE: this notification as generated by Google.`, + }, + "start_time": { + Type: schema.TypeString, + Computed: true, + Description: `Scheduled start time for the outage (milliseconds since Unix epoch).`, + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: `State of this notification. Note that the versions of this enum prefixed with "NS_" have +been deprecated in favor of the unprefixed values. Can take one of the following values: + - ACTIVE: This outage notification is active. The event could be in the past, present, + or future. See startTime and endTime for scheduling. + - CANCELLED: The outage associated with this notification was cancelled before the + outage was due to start. + - COMPLETED: The outage associated with this notification is complete.`, + }, + }, + }, + }, + "google_ip_address": { + Type: schema.TypeString, + Computed: true, + Description: `IP address configured on the Google side of the Interconnect link. +This can be used only for ping tests.`, + }, + "google_reference_id": { + Type: schema.TypeString, + Computed: true, + Description: `Google reference ID to be used when raising support tickets with Google or otherwise to debug +backend connectivity issues.`, + }, + "interconnect_attachments": { + Type: schema.TypeList, + Computed: true, + Description: `A list of the URLs of all InterconnectAttachments configured to use this Interconnect.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "label_fingerprint": { + Type: schema.TypeString, + Computed: true, + Description: `A fingerprint for the labels being applied to this Interconnect, which is essentially a hash +of the labels set used for optimistic locking. The fingerprint is initially generated by +Compute Engine and changes after every request to modify or update labels. +You must always provide an up-to-date fingerprint hash in order to update or change labels, +otherwise the request will fail with error 412 conditionNotMet.`, + }, + "operational_status": { + Type: schema.TypeString, + Computed: true, + Description: `The current status of this Interconnect's functionality, which can take one of the following values: + - OS_ACTIVE: A valid Interconnect, which is turned up and is ready to use. Attachments may + be provisioned on this Interconnect. + - OS_UNPROVISIONED: An Interconnect that has not completed turnup. No attachments may be + provisioned on this Interconnect. + - OS_UNDER_MAINTENANCE: An Interconnect that is undergoing internal maintenance. No + attachments may be provisioned or updated on this Interconnect.`, + }, + "peer_ip_address": { + Type: schema.TypeString, + Computed: true, + Description: `IP address configured on the customer side of the Interconnect link. +The customer should configure this IP address during turnup when prompted by Google NOC. +This can be used only for ping tests.`, + }, + "provisioned_link_count": { + Type: schema.TypeInt, + Computed: true, + Description: `Number of links actually provisioned in this interconnect.`, + }, + "satisfies_pzs": { + Type: schema.TypeBool, + Computed: true, + Description: `Reserved for future use.`, + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: `The current state of Interconnect functionality, which can take one of the following values: + - ACTIVE: The Interconnect is valid, turned up and ready to use. + Attachments may be provisioned on this Interconnect. + - UNPROVISIONED: The Interconnect has not completed turnup. No attachments may b + provisioned on this Interconnect. + - UNDER_MAINTENANCE: The Interconnect is undergoing internal maintenance. No attachments may + be provisioned or updated on this Interconnect.`, + }, + "terraform_labels": { + Type: schema.TypeMap, + Computed: true, + Description: `The combination of labels configured directly on the resource + and default labels configured on the provider.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func resourceComputeInterconnectCreate(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{}) + descriptionProp, err := expandComputeInterconnectDescription(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 + } + nameProp, err := expandComputeInterconnectName(d.Get("name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("name"); !tpgresource.IsEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + locationProp, err := expandComputeInterconnectLocation(d.Get("location"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("location"); !tpgresource.IsEmptyValue(reflect.ValueOf(locationProp)) && (ok || !reflect.DeepEqual(v, locationProp)) { + obj["location"] = locationProp + } + linkTypeProp, err := expandComputeInterconnectLinkType(d.Get("link_type"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("link_type"); !tpgresource.IsEmptyValue(reflect.ValueOf(linkTypeProp)) && (ok || !reflect.DeepEqual(v, linkTypeProp)) { + obj["linkType"] = linkTypeProp + } + requestedLinkCountProp, err := expandComputeInterconnectRequestedLinkCount(d.Get("requested_link_count"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("requested_link_count"); !tpgresource.IsEmptyValue(reflect.ValueOf(requestedLinkCountProp)) && (ok || !reflect.DeepEqual(v, requestedLinkCountProp)) { + obj["requestedLinkCount"] = requestedLinkCountProp + } + interconnectTypeProp, err := expandComputeInterconnectInterconnectType(d.Get("interconnect_type"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("interconnect_type"); !tpgresource.IsEmptyValue(reflect.ValueOf(interconnectTypeProp)) && (ok || !reflect.DeepEqual(v, interconnectTypeProp)) { + obj["interconnectType"] = interconnectTypeProp + } + adminEnabledProp, err := expandComputeInterconnectAdminEnabled(d.Get("admin_enabled"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("admin_enabled"); ok || !reflect.DeepEqual(v, adminEnabledProp) { + obj["adminEnabled"] = adminEnabledProp + } + nocContactEmailProp, err := expandComputeInterconnectNocContactEmail(d.Get("noc_contact_email"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("noc_contact_email"); !tpgresource.IsEmptyValue(reflect.ValueOf(nocContactEmailProp)) && (ok || !reflect.DeepEqual(v, nocContactEmailProp)) { + obj["nocContactEmail"] = nocContactEmailProp + } + customerNameProp, err := expandComputeInterconnectCustomerName(d.Get("customer_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("customer_name"); !tpgresource.IsEmptyValue(reflect.ValueOf(customerNameProp)) && (ok || !reflect.DeepEqual(v, customerNameProp)) { + obj["customerName"] = customerNameProp + } + labelFingerprintProp, err := expandComputeInterconnectLabelFingerprint(d.Get("label_fingerprint"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("label_fingerprint"); !tpgresource.IsEmptyValue(reflect.ValueOf(labelFingerprintProp)) && (ok || !reflect.DeepEqual(v, labelFingerprintProp)) { + obj["labelFingerprint"] = labelFingerprintProp + } + macsecProp, err := expandComputeInterconnectMacsec(d.Get("macsec"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("macsec"); !tpgresource.IsEmptyValue(reflect.ValueOf(macsecProp)) && (ok || !reflect.DeepEqual(v, macsecProp)) { + obj["macsec"] = macsecProp + } + macsecEnabledProp, err := expandComputeInterconnectMacsecEnabled(d.Get("macsec_enabled"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("macsec_enabled"); !tpgresource.IsEmptyValue(reflect.ValueOf(macsecEnabledProp)) && (ok || !reflect.DeepEqual(v, macsecEnabledProp)) { + obj["macsecEnabled"] = macsecEnabledProp + } + remoteLocationProp, err := expandComputeInterconnectRemoteLocation(d.Get("remote_location"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("remote_location"); !tpgresource.IsEmptyValue(reflect.ValueOf(remoteLocationProp)) && (ok || !reflect.DeepEqual(v, remoteLocationProp)) { + obj["remoteLocation"] = remoteLocationProp + } + requestedFeaturesProp, err := expandComputeInterconnectRequestedFeatures(d.Get("requested_features"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("requested_features"); !tpgresource.IsEmptyValue(reflect.ValueOf(requestedFeaturesProp)) && (ok || !reflect.DeepEqual(v, requestedFeaturesProp)) { + obj["requestedFeatures"] = requestedFeaturesProp + } + labelsProp, err := expandComputeInterconnectEffectiveLabels(d.Get("effective_labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("effective_labels"); !tpgresource.IsEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/global/interconnects") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Interconnect: %#v", obj) + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for Interconnect: %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 Interconnect: %s", err) + } + + // Store the ID now + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/global/interconnects/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + err = ComputeOperationWaitTime( + config, res, project, "Creating Interconnect", userAgent, + d.Timeout(schema.TimeoutCreate)) + + if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create Interconnect: %s", err) + } + + log.Printf("[DEBUG] Finished creating Interconnect %q: %#v", d.Id(), res) + + return resourceComputeInterconnectRead(d, meta) +} + +func resourceComputeInterconnectRead(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, "{{ComputeBasePath}}projects/{{project}}/global/interconnects/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for Interconnect: %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("ComputeInterconnect %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + + if err := d.Set("description", flattenComputeInterconnectDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("creation_timestamp", flattenComputeInterconnectCreationTimestamp(res["creationTimestamp"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("name", flattenComputeInterconnectName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("location", flattenComputeInterconnectLocation(res["location"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("link_type", flattenComputeInterconnectLinkType(res["linkType"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("requested_link_count", flattenComputeInterconnectRequestedLinkCount(res["requestedLinkCount"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("interconnect_type", flattenComputeInterconnectInterconnectType(res["interconnectType"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("admin_enabled", flattenComputeInterconnectAdminEnabled(res["adminEnabled"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("noc_contact_email", flattenComputeInterconnectNocContactEmail(res["nocContactEmail"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("customer_name", flattenComputeInterconnectCustomerName(res["customerName"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("operational_status", flattenComputeInterconnectOperationalStatus(res["operationalStatus"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("provisioned_link_count", flattenComputeInterconnectProvisionedLinkCount(res["provisionedLinkCount"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("interconnect_attachments", flattenComputeInterconnectInterconnectAttachments(res["interconnectAttachments"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("peer_ip_address", flattenComputeInterconnectPeerIpAddress(res["peerIpAddress"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("google_ip_address", flattenComputeInterconnectGoogleIpAddress(res["googleIpAddress"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("google_reference_id", flattenComputeInterconnectGoogleReferenceId(res["googleReferenceId"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("expected_outages", flattenComputeInterconnectExpectedOutages(res["expectedOutages"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("circuit_infos", flattenComputeInterconnectCircuitInfos(res["circuitInfos"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("labels", flattenComputeInterconnectLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("label_fingerprint", flattenComputeInterconnectLabelFingerprint(res["labelFingerprint"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("state", flattenComputeInterconnectState(res["state"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("satisfies_pzs", flattenComputeInterconnectSatisfiesPzs(res["satisfiesPzs"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("macsec", flattenComputeInterconnectMacsec(res["macsec"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("macsec_enabled", flattenComputeInterconnectMacsecEnabled(res["macsecEnabled"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("remote_location", flattenComputeInterconnectRemoteLocation(res["remoteLocation"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("requested_features", flattenComputeInterconnectRequestedFeatures(res["requestedFeatures"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("available_features", flattenComputeInterconnectAvailableFeatures(res["availableFeatures"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("terraform_labels", flattenComputeInterconnectTerraformLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + if err := d.Set("effective_labels", flattenComputeInterconnectEffectiveLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading Interconnect: %s", err) + } + + return nil +} + +func resourceComputeInterconnectUpdate(d *schema.ResourceData, meta interface{}) error { + // Only the root field "labels" and "terraform_labels" are mutable + return resourceComputeInterconnectRead(d, meta) +} + +func resourceComputeInterconnectDelete(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 Interconnect: %s", err) + } + billingProject = project + + url, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/global/interconnects/{{name}}") + 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 Interconnect %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, "Interconnect") + } + + err = ComputeOperationWaitTime( + config, res, project, "Deleting Interconnect", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting Interconnect %q: %#v", d.Id(), res) + return nil +} + +func resourceComputeInterconnectImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*transport_tpg.Config) + if err := tpgresource.ParseImportId([]string{ + "^projects/(?P[^/]+)/global/interconnects/(?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}}/global/interconnects/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenComputeInterconnectDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectCreationTimestamp(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectLocation(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + return tpgresource.ConvertSelfLinkToV1(v.(string)) +} + +func flattenComputeInterconnectLinkType(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectRequestedLinkCount(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 flattenComputeInterconnectInterconnectType(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectAdminEnabled(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectNocContactEmail(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectCustomerName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectOperationalStatus(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectProvisionedLinkCount(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 flattenComputeInterconnectInterconnectAttachments(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectPeerIpAddress(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectGoogleIpAddress(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectGoogleReferenceId(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectExpectedOutages(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "name": flattenComputeInterconnectExpectedOutagesName(original["name"], d, config), + "description": flattenComputeInterconnectExpectedOutagesDescription(original["description"], d, config), + "source": flattenComputeInterconnectExpectedOutagesSource(original["source"], d, config), + "state": flattenComputeInterconnectExpectedOutagesState(original["state"], d, config), + "issue_type": flattenComputeInterconnectExpectedOutagesIssueType(original["issueType"], d, config), + "affected_circuits": flattenComputeInterconnectExpectedOutagesAffectedCircuits(original["affectedCircuits"], d, config), + "start_time": flattenComputeInterconnectExpectedOutagesStartTime(original["startTime"], d, config), + "end_time": flattenComputeInterconnectExpectedOutagesEndTime(original["endTime"], d, config), + }) + } + return transformed +} +func flattenComputeInterconnectExpectedOutagesName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectExpectedOutagesDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectExpectedOutagesSource(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectExpectedOutagesState(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectExpectedOutagesIssueType(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectExpectedOutagesAffectedCircuits(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectExpectedOutagesStartTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectExpectedOutagesEndTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectCircuitInfos(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "google_circuit_id": flattenComputeInterconnectCircuitInfosGoogleCircuitId(original["googleCircuitId"], d, config), + "google_demarc_id": flattenComputeInterconnectCircuitInfosGoogleDemarcId(original["googleDemarcId"], d, config), + "customer_demarc_id": flattenComputeInterconnectCircuitInfosCustomerDemarcId(original["customerDemarcId"], d, config), + }) + } + return transformed +} +func flattenComputeInterconnectCircuitInfosGoogleCircuitId(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectCircuitInfosGoogleDemarcId(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectCircuitInfosCustomerDemarcId(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectLabels(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + + transformed := make(map[string]interface{}) + if l, ok := d.GetOkExists("labels"); ok { + for k := range l.(map[string]interface{}) { + transformed[k] = v.(map[string]interface{})[k] + } + } + + return transformed +} + +func flattenComputeInterconnectLabelFingerprint(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectState(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectSatisfiesPzs(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectMacsec(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["pre_shared_keys"] = + flattenComputeInterconnectMacsecPreSharedKeys(original["preSharedKeys"], d, config) + return []interface{}{transformed} +} +func flattenComputeInterconnectMacsecPreSharedKeys(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "name": flattenComputeInterconnectMacsecPreSharedKeysName(original["name"], d, config), + "start_time": flattenComputeInterconnectMacsecPreSharedKeysStartTime(original["startTime"], d, config), + "fail_open": flattenComputeInterconnectMacsecPreSharedKeysFailOpen(original["failOpen"], d, config), + }) + } + return transformed +} +func flattenComputeInterconnectMacsecPreSharedKeysName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectMacsecPreSharedKeysStartTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectMacsecPreSharedKeysFailOpen(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectMacsecEnabled(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectRemoteLocation(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectRequestedFeatures(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectAvailableFeatures(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenComputeInterconnectTerraformLabels(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + + transformed := make(map[string]interface{}) + if l, ok := d.GetOkExists("terraform_labels"); ok { + for k := range l.(map[string]interface{}) { + transformed[k] = v.(map[string]interface{})[k] + } + } + + return transformed +} + +func flattenComputeInterconnectEffectiveLabels(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func expandComputeInterconnectDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectLocation(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectLinkType(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectRequestedLinkCount(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectInterconnectType(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectAdminEnabled(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectNocContactEmail(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectCustomerName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectLabelFingerprint(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectMacsec(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{}) + + transformedPreSharedKeys, err := expandComputeInterconnectMacsecPreSharedKeys(original["pre_shared_keys"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPreSharedKeys); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["preSharedKeys"] = transformedPreSharedKeys + } + + return transformed, nil +} + +func expandComputeInterconnectMacsecPreSharedKeys(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedName, err := expandComputeInterconnectMacsecPreSharedKeysName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["name"] = transformedName + } + + transformedStartTime, err := expandComputeInterconnectMacsecPreSharedKeysStartTime(original["start_time"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedStartTime); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["startTime"] = transformedStartTime + } + + transformedFailOpen, err := expandComputeInterconnectMacsecPreSharedKeysFailOpen(original["fail_open"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedFailOpen); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["failOpen"] = transformedFailOpen + } + + req = append(req, transformed) + } + return req, nil +} + +func expandComputeInterconnectMacsecPreSharedKeysName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectMacsecPreSharedKeysStartTime(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectMacsecPreSharedKeysFailOpen(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectMacsecEnabled(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectRemoteLocation(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectRequestedFeatures(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandComputeInterconnectEffectiveLabels(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 +} diff --git a/google/services/compute/resource_compute_interconnect_generated_test.go b/google/services/compute/resource_compute_interconnect_generated_test.go new file mode 100644 index 00000000000..a135b0cd2fb --- /dev/null +++ b/google/services/compute/resource_compute_interconnect_generated_test.go @@ -0,0 +1,115 @@ +// 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 compute_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func TestAccComputeInterconnect_computeInterconnectBasicTestExample(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: testAccCheckComputeInterconnectDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeInterconnect_computeInterconnectBasicTestExample(context), + }, + { + ResourceName: "google_compute_interconnect.example-interconnect", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "labels", "terraform_labels"}, + }, + }, + }) +} + +func testAccComputeInterconnect_computeInterconnectBasicTestExample(context map[string]interface{}) string { + return acctest.Nprintf(` +data "google_project" "project" {} + +resource "google_compute_interconnect" "example-interconnect" { + name = "tf-test-example-interconnect%{random_suffix}" + customer_name = "internal_customer" # Special customer only available for Google testing. + interconnect_type = "IT_PRIVATE" # Special type only available for Google testing. + link_type = "LINK_TYPE_ETHERNET_10G_LR" + location = "https://www.googleapis.com/compute/v1/projects/${data.google_project.project.name}/global/interconnectLocations/z2z-us-east4-zone1-lciadl-a" # Special location only available for Google testing. + requested_link_count = 1 + admin_enabled = true + description = "example description" + macsec_enabled = false + noc_contact_email = "user@example.com" + requested_features = [] +} +`, context) +} + +func testAccCheckComputeInterconnectDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_interconnect" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := acctest.GoogleProviderConfig(t) + + url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{ComputeBasePath}}projects/{{project}}/global/interconnects/{{name}}") + 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("ComputeInterconnect still exists at %s", url) + } + } + + return nil + } +} diff --git a/google/services/compute/resource_compute_interconnect_sweeper.go b/google/services/compute/resource_compute_interconnect_sweeper.go new file mode 100644 index 00000000000..b5e0a9c1906 --- /dev/null +++ b/google/services/compute/resource_compute_interconnect_sweeper.go @@ -0,0 +1,139 @@ +// 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 compute + +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("ComputeInterconnect", testSweepComputeInterconnect) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepComputeInterconnect(region string) error { + resourceName := "ComputeInterconnect" + 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://compute.googleapis.com/compute/v1/projects/{{project}}/global/interconnects", "?")[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["interconnects"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := tpgresource.GetResourceNameFromSelfLink(obj["name"].(string)) + // Skip resources that shouldn't be sweeped + if !sweeper.IsSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://compute.googleapis.com/compute/v1/projects/{{project}}/global/interconnects/{{name}}" + 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/website/docs/r/compute_interconnect.html.markdown b/website/docs/r/compute_interconnect.html.markdown new file mode 100644 index 00000000000..e5e59a25dc9 --- /dev/null +++ b/website/docs/r/compute_interconnect.html.markdown @@ -0,0 +1,365 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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: "Compute Engine" +description: |- + Represents an Interconnect resource. +--- + +# google\_compute\_interconnect + +Represents an Interconnect resource. The Interconnect resource is a dedicated connection between +Google's network and your on-premises network. + + +To get more information about Interconnect, see: + +* [API documentation](https://cloud.google.com/compute/docs/reference/rest/v1/interconnects) +* How-to Guides + * [Create a Dedicated Interconnect](https://cloud.google.com/network-connectivity/docs/interconnect/concepts/dedicated-overview) + +## Example Usage - Compute Interconnect Basic + + +```hcl +data "google_project" "project" {} + +resource "google_compute_interconnect" "example-interconnect" { + name = "example-interconnect" + customer_name = "example_customer" + interconnect_type = "DEDICATED" + link_type = "LINK_TYPE_ETHERNET_10G_LR" + location = "https://www.googleapis.com/compute/v1/projects/${data.google_project.project.name}/global/interconnectLocations/iad-zone1-1" + requested_link_count = 1 +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + Name of the resource. Provided by the client when the resource is created. The name must be + 1-63 characters long, and comply with RFC1035. Specifically, the name must be 1-63 characters + long and match the regular expression `[a-z]([-a-z0-9]*[a-z0-9])?` which means the first + character must be a lowercase letter, and all following characters must be a dash, + lowercase letter, or digit, except the last character, which cannot be a dash. + +* `location` - + (Required) + URL of the InterconnectLocation object that represents where this connection is to be provisioned. + +* `link_type` - + (Required) + Type of link requested. Note that this field indicates the speed of each of the links in the + bundle, not the speed of the entire bundle. Can take one of the following values: + - LINK_TYPE_ETHERNET_10G_LR: A 10G Ethernet with LR optics. + - LINK_TYPE_ETHERNET_100G_LR: A 100G Ethernet with LR optics. + Possible values are: `LINK_TYPE_ETHERNET_10G_LR`, `LINK_TYPE_ETHERNET_100G_LR`. + +* `requested_link_count` - + (Required) + Target number of physical links in the link bundle, as requested by the customer. + +* `interconnect_type` - + (Required) + Type of interconnect. Note that a value IT_PRIVATE has been deprecated in favor of DEDICATED. + Can take one of the following values: + - PARTNER: A partner-managed interconnection shared between customers though a partner. + - DEDICATED: A dedicated physical interconnection with the customer. + Possible values are: `DEDICATED`, `PARTNER`, `IT_PRIVATE`. + +* `customer_name` - + (Required) + Customer name, to put in the Letter of Authorization as the party authorized to request a + crossconnect. + + +- - - + + +* `description` - + (Optional) + An optional description of this resource. Provide this property when you create the resource. + +* `admin_enabled` - + (Optional) + Administrative status of the interconnect. When this is set to true, the Interconnect is + functional and can carry traffic. When set to false, no packets can be carried over the + interconnect and no BGP routes are exchanged over it. By default, the status is set to true. + +* `noc_contact_email` - + (Optional) + Email address to contact the customer NOC for operations and maintenance notifications + regarding this Interconnect. If specified, this will be used for notifications in addition to + all other forms described, such as Cloud Monitoring logs alerting and Cloud Notifications. + This field is required for users who sign up for Cloud Interconnect using workforce identity + federation. + +* `labels` - + (Optional) + Labels for this resource. These can only be added or modified by the setLabels + method. Each label key/value pair must comply with RFC1035. Label values may be empty. + + **Note**: This field is non-authoritative, and will only manage the labels present in your configuration. + Please refer to the field `effective_labels` for all of the labels present on the resource. + +* `macsec` - + (Optional) + Configuration that enables Media Access Control security (MACsec) on the Cloud + Interconnect connection between Google and your on-premises router. + Structure is [documented below](#nested_macsec). + +* `macsec_enabled` - + (Optional) + Enable or disable MACsec on this Interconnect connection. + MACsec enablement fails if the MACsec object is not specified. + +* `remote_location` - + (Optional) + Indicates that this is a Cross-Cloud Interconnect. This field specifies the location outside + of Google's network that the interconnect is connected to. + +* `requested_features` - + (Optional) + interconnects.list of features requested for this Interconnect connection. Options: MACSEC ( + If specified then the connection is created on MACsec capable hardware ports. If not + specified, the default value is false, which allocates non-MACsec capable ports first if + available). + Each value may be one of: `MACSEC`. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +The `macsec` block supports: + +* `pre_shared_keys` - + (Required) + A keychain placeholder describing a set of named key objects along with their + start times. A MACsec CKN/CAK is generated for each key in the key chain. + Google router automatically picks the key with the most recent startTime when establishing + or re-establishing a MACsec secure link. + Structure is [documented below](#nested_pre_shared_keys). + + +The `pre_shared_keys` block supports: + +* `name` - + (Required) + A name for this pre-shared key. The name must be 1-63 characters long, and + comply with RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression `[a-z]([-a-z0-9]*[a-z0-9])?` which means the first character + must be a lowercase letter, and all following characters must be a dash, lowercase + letter, or digit, except the last character, which cannot be a dash. + +* `start_time` - + (Optional) + A RFC3339 timestamp on or after which the key is valid. startTime can be in the + future. If the keychain has a single key, startTime can be omitted. If the keychain + has multiple keys, startTime is mandatory for each key. The start times of keys must + be in increasing order. The start times of two consecutive keys must be at least 6 + hours apart. + +* `fail_open` - + (Optional) + If set to true, the Interconnect connection is configured with a should-secure + MACsec security policy, that allows the Google router to fallback to cleartext + traffic if the MKA session cannot be established. By default, the Interconnect + connection is configured with a must-secure security policy that drops all traffic + if the MKA session cannot be established with your router. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/global/interconnects/{{name}}` + +* `creation_timestamp` - + Creation timestamp in RFC3339 text format. + +* `operational_status` - + The current status of this Interconnect's functionality, which can take one of the following values: + - OS_ACTIVE: A valid Interconnect, which is turned up and is ready to use. Attachments may + be provisioned on this Interconnect. + - OS_UNPROVISIONED: An Interconnect that has not completed turnup. No attachments may be + provisioned on this Interconnect. + - OS_UNDER_MAINTENANCE: An Interconnect that is undergoing internal maintenance. No + attachments may be provisioned or updated on this Interconnect. + +* `provisioned_link_count` - + Number of links actually provisioned in this interconnect. + +* `interconnect_attachments` - + A list of the URLs of all InterconnectAttachments configured to use this Interconnect. + +* `peer_ip_address` - + IP address configured on the customer side of the Interconnect link. + The customer should configure this IP address during turnup when prompted by Google NOC. + This can be used only for ping tests. + +* `google_ip_address` - + IP address configured on the Google side of the Interconnect link. + This can be used only for ping tests. + +* `google_reference_id` - + Google reference ID to be used when raising support tickets with Google or otherwise to debug + backend connectivity issues. + +* `expected_outages` - + A list of outages expected for this Interconnect. + Structure is [documented below](#nested_expected_outages). + +* `circuit_infos` - + A list of CircuitInfo objects, that describe the individual circuits in this LAG. + Structure is [documented below](#nested_circuit_infos). + +* `label_fingerprint` - + A fingerprint for the labels being applied to this Interconnect, which is essentially a hash + of the labels set used for optimistic locking. The fingerprint is initially generated by + Compute Engine and changes after every request to modify or update labels. + You must always provide an up-to-date fingerprint hash in order to update or change labels, + otherwise the request will fail with error 412 conditionNotMet. + +* `state` - + The current state of Interconnect functionality, which can take one of the following values: + - ACTIVE: The Interconnect is valid, turned up and ready to use. + Attachments may be provisioned on this Interconnect. + - UNPROVISIONED: The Interconnect has not completed turnup. No attachments may b + provisioned on this Interconnect. + - UNDER_MAINTENANCE: The Interconnect is undergoing internal maintenance. No attachments may + be provisioned or updated on this Interconnect. + +* `satisfies_pzs` - + Reserved for future use. + +* `available_features` - + interconnects.list of features available for this Interconnect connection. Can take the value: + MACSEC. If present then the Interconnect connection is provisioned on MACsec capable hardware + ports. If not present then the Interconnect connection is provisioned on non-MACsec capable + ports and MACsec isn't supported and enabling MACsec fails). + +* `terraform_labels` - + The combination of labels configured directly on the resource + and default labels configured on the provider. + +* `effective_labels` - + All of labels (key/value pairs) present on the resource in GCP, including the labels configured through Terraform, other clients and services. + + +The `expected_outages` block contains: + +* `name` - + (Output) + Unique identifier for this outage notification. + +* `description` - + (Output) + A description about the purpose of the outage. + +* `source` - + (Output) + The party that generated this notification. Note that the value of NSRC_GOOGLE has been + deprecated in favor of GOOGLE. Can take the following value: + - GOOGLE: this notification as generated by Google. + +* `state` - + (Output) + State of this notification. Note that the versions of this enum prefixed with "NS_" have + been deprecated in favor of the unprefixed values. Can take one of the following values: + - ACTIVE: This outage notification is active. The event could be in the past, present, + or future. See startTime and endTime for scheduling. + - CANCELLED: The outage associated with this notification was cancelled before the + outage was due to start. + - COMPLETED: The outage associated with this notification is complete. + +* `issue_type` - + (Output) + Form this outage is expected to take. Note that the versions of this enum prefixed with + "IT_" have been deprecated in favor of the unprefixed values. Can take one of the + following values: + - OUTAGE: The Interconnect may be completely out of service for some or all of the + specified window. + - PARTIAL_OUTAGE: Some circuits comprising the Interconnect as a whole should remain + up, but with reduced bandwidth. + +* `affected_circuits` - + (Output) + If issueType is IT_PARTIAL_OUTAGE, a list of the Google-side circuit IDs that will be + affected. + +* `start_time` - + (Output) + Scheduled start time for the outage (milliseconds since Unix epoch). + +* `end_time` - + (Output) + Scheduled end time for the outage (milliseconds since Unix epoch). + +The `circuit_infos` block contains: + +* `google_circuit_id` - + (Output) + Google-assigned unique ID for this circuit. Assigned at circuit turn-up. + +* `google_demarc_id` - + (Output) + Google-side demarc ID for this circuit. Assigned at circuit turn-up and provided by + Google to the customer in the LOA. + +* `customer_demarc_id` - + (Output) + Customer-side demarc ID for this circuit. + +## 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 + + +Interconnect can be imported using any of these accepted formats: + +* `projects/{{project}}/global/interconnects/{{name}}` +* `{{project}}/{{name}}` +* `{{name}}` + + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Interconnect using one of the formats above. For example: + +```tf +import { + id = "projects/{{project}}/global/interconnects/{{name}}" + to = google_compute_interconnect.default +} +``` + +When using the [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import), Interconnect can be imported using one of the formats above. For example: + +``` +$ terraform import google_compute_interconnect.default projects/{{project}}/global/interconnects/{{name}} +$ terraform import google_compute_interconnect.default {{project}}/{{name}} +$ terraform import google_compute_interconnect.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).