Skip to content

Commit c8419d8

Browse files
Fix interconnect immutability issue causing macsec failure (#11700) (#8203)
[upstream:74483d6cc29cbe29f2c710efb4caa93f102d8324] Signed-off-by: Modular Magician <[email protected]>
1 parent 6a903d6 commit c8419d8

File tree

3 files changed

+190
-11
lines changed

3 files changed

+190
-11
lines changed

.changelog/11700.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note: enhancement
2+
compute: added in-place update in `google_compute_interconnect` resource except for `remote_location ` and `requested_features` fields
3+
```

google-beta/services/compute/resource_compute_interconnect.go

+96-11
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ lowercase letter, or digit, except the last character, which cannot be a dash.`,
114114
"admin_enabled": {
115115
Type: schema.TypeBool,
116116
Optional: true,
117-
ForceNew: true,
118117
Description: `Administrative status of the interconnect. When this is set to true, the Interconnect is
119118
functional and can carry traffic. When set to false, no packets can be carried over the
120119
interconnect and no BGP routes are exchanged over it. By default, the status is set to true.`,
@@ -123,7 +122,6 @@ interconnect and no BGP routes are exchanged over it. By default, the status is
123122
"description": {
124123
Type: schema.TypeString,
125124
Optional: true,
126-
ForceNew: true,
127125
Description: `An optional description of this resource. Provide this property when you create the resource.`,
128126
},
129127
"labels": {
@@ -140,7 +138,6 @@ Please refer to the field 'effective_labels' for all of the labels present on th
140138
"macsec": {
141139
Type: schema.TypeList,
142140
Optional: true,
143-
ForceNew: true,
144141
Description: `Configuration that enables Media Access Control security (MACsec) on the Cloud
145142
Interconnect connection between Google and your on-premises router.`,
146143
MaxItems: 1,
@@ -149,7 +146,6 @@ Interconnect connection between Google and your on-premises router.`,
149146
"pre_shared_keys": {
150147
Type: schema.TypeList,
151148
Required: true,
152-
ForceNew: true,
153149
Description: `A keychain placeholder describing a set of named key objects along with their
154150
start times. A MACsec CKN/CAK is generated for each key in the key chain.
155151
Google router automatically picks the key with the most recent startTime when establishing
@@ -159,7 +155,6 @@ or re-establishing a MACsec secure link.`,
159155
"name": {
160156
Type: schema.TypeString,
161157
Required: true,
162-
ForceNew: true,
163158
ValidateFunc: verify.ValidateRegexp(`^[a-z]([-a-z0-9]*[a-z0-9])?$`),
164159
Description: `A name for this pre-shared key. The name must be 1-63 characters long, and
165160
comply with RFC1035. Specifically, the name must be 1-63 characters long and match
@@ -170,7 +165,6 @@ or re-establishing a MACsec secure link.`,
170165
"fail_open": {
171166
Type: schema.TypeBool,
172167
Optional: true,
173-
ForceNew: true,
174168
Description: `If set to true, the Interconnect connection is configured with a should-secure
175169
MACsec security policy, that allows the Google router to fallback to cleartext
176170
traffic if the MKA session cannot be established. By default, the Interconnect
@@ -180,7 +174,6 @@ if the MKA session cannot be established with your router.`,
180174
"start_time": {
181175
Type: schema.TypeString,
182176
Optional: true,
183-
ForceNew: true,
184177
Description: `A RFC3339 timestamp on or after which the key is valid. startTime can be in the
185178
future. If the keychain has a single key, startTime can be omitted. If the keychain
186179
has multiple keys, startTime is mandatory for each key. The start times of keys must
@@ -196,14 +189,12 @@ hours apart.`,
196189
"macsec_enabled": {
197190
Type: schema.TypeBool,
198191
Optional: true,
199-
ForceNew: true,
200192
Description: `Enable or disable MACsec on this Interconnect connection.
201193
MACsec enablement fails if the MACsec object is not specified.`,
202194
},
203195
"noc_contact_email": {
204196
Type: schema.TypeString,
205197
Optional: true,
206-
ForceNew: true,
207198
Description: `Email address to contact the customer NOC for operations and maintenance notifications
208199
regarding this Interconnect. If specified, this will be used for notifications in addition to
209200
all other forms described, such as Cloud Monitoring logs alerting and Cloud Notifications.
@@ -275,7 +266,6 @@ Google to the customer in the LOA.`,
275266
"effective_labels": {
276267
Type: schema.TypeMap,
277268
Computed: true,
278-
ForceNew: true,
279269
Description: `All of labels (key/value pairs) present on the resource in GCP, including the labels configured through Terraform, other clients and services.`,
280270
Elem: &schema.Schema{Type: schema.TypeString},
281271
},
@@ -721,7 +711,102 @@ func resourceComputeInterconnectRead(d *schema.ResourceData, meta interface{}) e
721711
}
722712

723713
func resourceComputeInterconnectUpdate(d *schema.ResourceData, meta interface{}) error {
724-
// Only the root field "labels" and "terraform_labels" are mutable
714+
config := meta.(*transport_tpg.Config)
715+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
716+
if err != nil {
717+
return err
718+
}
719+
720+
billingProject := ""
721+
722+
project, err := tpgresource.GetProject(d, config)
723+
if err != nil {
724+
return fmt.Errorf("Error fetching project for Interconnect: %s", err)
725+
}
726+
billingProject = project
727+
728+
obj := make(map[string]interface{})
729+
descriptionProp, err := expandComputeInterconnectDescription(d.Get("description"), d, config)
730+
if err != nil {
731+
return err
732+
} else if v, ok := d.GetOkExists("description"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, descriptionProp)) {
733+
obj["description"] = descriptionProp
734+
}
735+
adminEnabledProp, err := expandComputeInterconnectAdminEnabled(d.Get("admin_enabled"), d, config)
736+
if err != nil {
737+
return err
738+
} else if v, ok := d.GetOkExists("admin_enabled"); ok || !reflect.DeepEqual(v, adminEnabledProp) {
739+
obj["adminEnabled"] = adminEnabledProp
740+
}
741+
nocContactEmailProp, err := expandComputeInterconnectNocContactEmail(d.Get("noc_contact_email"), d, config)
742+
if err != nil {
743+
return err
744+
} else if v, ok := d.GetOkExists("noc_contact_email"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, nocContactEmailProp)) {
745+
obj["nocContactEmail"] = nocContactEmailProp
746+
}
747+
labelFingerprintProp, err := expandComputeInterconnectLabelFingerprint(d.Get("label_fingerprint"), d, config)
748+
if err != nil {
749+
return err
750+
} else if v, ok := d.GetOkExists("label_fingerprint"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelFingerprintProp)) {
751+
obj["labelFingerprint"] = labelFingerprintProp
752+
}
753+
macsecProp, err := expandComputeInterconnectMacsec(d.Get("macsec"), d, config)
754+
if err != nil {
755+
return err
756+
} else if v, ok := d.GetOkExists("macsec"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, macsecProp)) {
757+
obj["macsec"] = macsecProp
758+
}
759+
macsecEnabledProp, err := expandComputeInterconnectMacsecEnabled(d.Get("macsec_enabled"), d, config)
760+
if err != nil {
761+
return err
762+
} else if v, ok := d.GetOkExists("macsec_enabled"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, macsecEnabledProp)) {
763+
obj["macsecEnabled"] = macsecEnabledProp
764+
}
765+
labelsProp, err := expandComputeInterconnectEffectiveLabels(d.Get("effective_labels"), d, config)
766+
if err != nil {
767+
return err
768+
} else if v, ok := d.GetOkExists("effective_labels"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) {
769+
obj["labels"] = labelsProp
770+
}
771+
772+
url, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/global/interconnects/{{name}}")
773+
if err != nil {
774+
return err
775+
}
776+
777+
log.Printf("[DEBUG] Updating Interconnect %q: %#v", d.Id(), obj)
778+
headers := make(http.Header)
779+
780+
// err == nil indicates that the billing_project value was found
781+
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
782+
billingProject = bp
783+
}
784+
785+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
786+
Config: config,
787+
Method: "PATCH",
788+
Project: billingProject,
789+
RawURL: url,
790+
UserAgent: userAgent,
791+
Body: obj,
792+
Timeout: d.Timeout(schema.TimeoutUpdate),
793+
Headers: headers,
794+
})
795+
796+
if err != nil {
797+
return fmt.Errorf("Error updating Interconnect %q: %s", d.Id(), err)
798+
} else {
799+
log.Printf("[DEBUG] Finished updating Interconnect %q: %#v", d.Id(), res)
800+
}
801+
802+
err = ComputeOperationWaitTime(
803+
config, res, project, "Updating Interconnect", userAgent,
804+
d.Timeout(schema.TimeoutUpdate))
805+
806+
if err != nil {
807+
return err
808+
}
809+
725810
return resourceComputeInterconnectRead(d, meta)
726811
}
727812

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
package compute_test
4+
5+
import (
6+
"testing"
7+
8+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
9+
10+
"github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest"
11+
)
12+
13+
func TestAccComputeInterconnect_computeInterconnectMacsecTest(t *testing.T) {
14+
t.Parallel()
15+
16+
context := map[string]interface{}{
17+
"random_suffix": acctest.RandString(t, 10),
18+
}
19+
20+
acctest.VcrTest(t, resource.TestCase{
21+
PreCheck: func() { acctest.AccTestPreCheck(t) },
22+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
23+
CheckDestroy: testAccCheckComputeInterconnectDestroyProducer(t),
24+
Steps: []resource.TestStep{
25+
{
26+
Config: testAccComputeInterconnect_computeInterconnectCreate(context),
27+
},
28+
{
29+
ResourceName: "google_compute_interconnect.example-interconnect",
30+
ImportState: true,
31+
ImportStateVerify: true,
32+
ImportStateVerifyIgnore: []string{"labels", "location", "terraform_labels"},
33+
},
34+
{
35+
Config: testAccComputeInterconnect_computeInterconnectEnableMacsec(context),
36+
},
37+
{
38+
ResourceName: "google_compute_interconnect.example-interconnect",
39+
ImportState: true,
40+
ImportStateVerify: true,
41+
ImportStateVerifyIgnore: []string{"labels", "location", "terraform_labels"},
42+
},
43+
},
44+
})
45+
}
46+
47+
func testAccComputeInterconnect_computeInterconnectCreate(context map[string]interface{}) string {
48+
return acctest.Nprintf(`
49+
data "google_project" "project" {}
50+
51+
resource "google_compute_interconnect" "example-interconnect" {
52+
name = "tf-test-example-interconnect%{random_suffix}"
53+
customer_name = "internal_customer" # Special customer only available for Google testing.
54+
interconnect_type = "DEDICATED"
55+
link_type = "LINK_TYPE_ETHERNET_100G_LR"
56+
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.
57+
requested_link_count = 1
58+
admin_enabled = true
59+
description = "example description"
60+
macsec_enabled = false
61+
noc_contact_email = "[email protected]"
62+
requested_features = ["IF_MACSEC"]
63+
}
64+
`, context)
65+
}
66+
67+
func testAccComputeInterconnect_computeInterconnectEnableMacsec(context map[string]interface{}) string {
68+
return acctest.Nprintf(`
69+
data "google_project" "project" {}
70+
71+
resource "google_compute_interconnect" "example-interconnect" {
72+
name = "tf-test-example-interconnect%{random_suffix}"
73+
customer_name = "internal_customer" # Special customer only available for Google testing.
74+
interconnect_type = "DEDICATED"
75+
link_type = "LINK_TYPE_ETHERNET_100G_LR"
76+
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.
77+
requested_link_count = 1
78+
admin_enabled = true
79+
description = "example description"
80+
macsec_enabled = true
81+
noc_contact_email = "[email protected]"
82+
requested_features = ["IF_MACSEC"]
83+
macsec {
84+
pre_shared_keys {
85+
name = "test-key"
86+
start_time = "2023-07-01T21:00:01.000Z"
87+
}
88+
}
89+
}
90+
`, context)
91+
}

0 commit comments

Comments
 (0)