Skip to content

Commit e6c39e1

Browse files
authored
Fix perma-diff in google_service_networking_connection for reserved_peering_ranges ordering (GoogleCloudPlatform#16685)
1 parent 0c55b33 commit e6c39e1

2 files changed

Lines changed: 115 additions & 5 deletions

File tree

mmv1/third_party/terraform/services/servicenetworking/resource_service_networking_connection.go

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"fmt"
55
"log"
66
"net/url"
7+
"reflect"
78
"regexp"
9+
"sort"
810
"strings"
911
"time"
1012

@@ -62,10 +64,11 @@ func ResourceServiceNetworkingConnection() *schema.Resource {
6264
Description: `Provider peering service that is managing peering connectivity for a service provider organization. For Google services that support this functionality it is 'servicenetworking.googleapis.com'.`,
6365
},
6466
"reserved_peering_ranges": {
65-
Type: schema.TypeList,
66-
Required: true,
67-
Elem: &schema.Schema{Type: schema.TypeString},
68-
Description: `Named IP address range(s) of PEERING type reserved for this service provider. Note that invoking this method with a different range when connection is already established will not reallocate already provisioned service producer subnetworks.`,
67+
Type: schema.TypeList,
68+
Required: true,
69+
Elem: &schema.Schema{Type: schema.TypeString},
70+
DiffSuppressFunc: stringListDiffSuppress,
71+
Description: `Named IP address range(s) of PEERING type reserved for this service provider. Note that invoking this method with a different range when connection is already established will not reallocate already provisioned service producer subnetworks.`,
6972
},
7073
"deletion_policy": {
7174
Type: schema.TypeString,
@@ -227,9 +230,14 @@ func resourceServiceNetworkingConnectionRead(d *schema.ResourceData, meta interf
227230
if err := d.Set("peering", connection.Peering); err != nil {
228231
return fmt.Errorf("Error setting peering: %s", err)
229232
}
233+
234+
// removed the intermediate `ranges` variable — it was a
235+
// leftover from when sort.Strings() lived here. The DiffSuppressFunc
236+
// already handles ordering, so we write the API response directly to state.
230237
if err := d.Set("reserved_peering_ranges", connection.ReservedPeeringRanges); err != nil {
231238
return fmt.Errorf("Error setting reserved_peering_ranges: %s", err)
232239
}
240+
233241
return nil
234242
}
235243

@@ -433,7 +441,6 @@ func RetrieveServiceNetworkingNetworkName(d *schema.ResourceData, config *transp
433441

434442
// return the network name formatting unique to this API
435443
return fmt.Sprintf("projects/%v/global/networks/%v", project.ProjectNumber, networkName), nil
436-
437444
}
438445

439446
const parentServicePattern = "^services/.+$"
@@ -457,3 +464,37 @@ func init() {
457464
Schema: ResourceServiceNetworkingConnection(),
458465
}.Register()
459466
}
467+
468+
// stringListDiffSuppress suppresses diffs for TypeList fields where the
469+
// order of elements does not matter. It derives the root key from k by
470+
// stripping the element index suffix.
471+
func stringListDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
472+
root := k
473+
if idx := strings.IndexByte(k, '.'); idx != -1 {
474+
root = k[:idx]
475+
}
476+
477+
o, n := d.GetChange(root)
478+
479+
oldList, ok1 := o.([]interface{})
480+
newList, ok2 := n.([]interface{})
481+
482+
if !ok1 || !ok2 || len(oldList) != len(newList) {
483+
return false
484+
}
485+
486+
oldStrs := make([]string, len(oldList))
487+
for i, v := range oldList {
488+
oldStrs[i] = fmt.Sprintf("%v", v)
489+
}
490+
491+
newStrs := make([]string, len(newList))
492+
for i, v := range newList {
493+
newStrs[i] = fmt.Sprintf("%v", v)
494+
}
495+
496+
sort.Strings(oldStrs)
497+
sort.Strings(newStrs)
498+
499+
return reflect.DeepEqual(oldStrs, newStrs)
500+
}

mmv1/third_party/terraform/services/servicenetworking/resource_service_networking_connection_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,72 @@ resource "google_service_networking_connection" "foobar" {
312312
}
313313
`, addressRangeName, addressRangeName, org_id, billing_account, networkName, addressRangeName, serviceName)
314314
}
315+
316+
func TestAccServiceNetworkingConnection_reorder(t *testing.T) {
317+
t.Parallel()
318+
319+
network := fmt.Sprintf("tf-test-snc-%s", acctest.RandString(t, 10))
320+
range1 := fmt.Sprintf("tf-test-range1-%s", acctest.RandString(t, 10))
321+
range2 := fmt.Sprintf("tf-test-range2-%s", acctest.RandString(t, 10))
322+
323+
service := "servicenetworking.googleapis.com"
324+
325+
acctest.VcrTest(t, resource.TestCase{
326+
PreCheck: func() { acctest.AccTestPreCheck(t) },
327+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
328+
CheckDestroy: testServiceNetworkingConnectionDestroy(t, service, network),
329+
Steps: []resource.TestStep{
330+
{
331+
Config: testAccServiceNetworkingConnectionOrder(network, range1, range2, service, "forward"),
332+
Check: resource.ComposeTestCheckFunc(
333+
resource.TestCheckResourceAttr("google_service_networking_connection.foobar", "reserved_peering_ranges.#", "2"),
334+
),
335+
},
336+
{
337+
ResourceName: "google_service_networking_connection.foobar",
338+
ImportState: true,
339+
ImportStateVerify: true,
340+
},
341+
{
342+
Config: testAccServiceNetworkingConnectionOrder(network, range1, range2, service, "reverse"),
343+
PlanOnly: true,
344+
},
345+
},
346+
})
347+
}
348+
349+
func testAccServiceNetworkingConnectionOrder(networkName, r1, r2, serviceName, order string) string {
350+
ranges := fmt.Sprintf(`["%s", "%s"]`, r1, r2)
351+
if order == "reverse" {
352+
ranges = fmt.Sprintf(`["%s", "%s"]`, r2, r1)
353+
}
354+
355+
return fmt.Sprintf(`
356+
resource "google_compute_network" "servicenet" {
357+
name = "%s"
358+
}
359+
360+
resource "google_compute_global_address" "r1" {
361+
name = "%s"
362+
purpose = "VPC_PEERING"
363+
address_type = "INTERNAL"
364+
prefix_length = 16
365+
network = google_compute_network.servicenet.self_link
366+
}
367+
368+
resource "google_compute_global_address" "r2" {
369+
name = "%s"
370+
purpose = "VPC_PEERING"
371+
address_type = "INTERNAL"
372+
prefix_length = 16
373+
network = google_compute_network.servicenet.self_link
374+
}
375+
376+
resource "google_service_networking_connection" "foobar" {
377+
network = google_compute_network.servicenet.self_link
378+
service = "%s"
379+
reserved_peering_ranges = %s
380+
depends_on = [google_compute_global_address.r1, google_compute_global_address.r2]
381+
}
382+
`, networkName, r1, r2, serviceName, ranges)
383+
}

0 commit comments

Comments
 (0)