Skip to content

Commit 0952e97

Browse files
add send_secondary_ip_range_if_empty (#11410) (#19122)
[upstream:4472fffc4067e62b24edb37e22f4fe61dfcc555c] Signed-off-by: Modular Magician <magic-modules@google.com>
1 parent 345e98d commit 0952e97

File tree

5 files changed

+315
-8
lines changed

5 files changed

+315
-8
lines changed

.changelog/11410.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
```release-note:deprecation
2+
compute: setting `google_compute_subnetwork.secondary_ip_range = []` to explicitly set a list of empty objects is deprecated and will produce an error in the upcoming major release. Use `send_secondary_ip_range_if_empty` while removing `secondary_ip_range` from config instead.
3+
```
4+
```release-note:enhancement
5+
compute: added `send_secondary_ip_range_if_empty` to `google_compute_subnetwork`
6+
```

google/services/compute/resource_compute_subnetwork.go

Lines changed: 119 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,37 @@ func IsShrinkageIpCidr(_ context.Context, old, new, _ interface{}) bool {
5555
return true
5656
}
5757

58+
func sendSecondaryIpRangeIfEmptyDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error {
59+
// on create, return immediately as we don't need to determine if the value is empty or not
60+
if diff.Id() == "" {
61+
return nil
62+
}
63+
64+
sendZero := diff.Get("send_secondary_ip_range_if_empty").(bool)
65+
if !sendZero {
66+
return nil
67+
}
68+
69+
configSecondaryIpRange := diff.GetRawConfig().GetAttr("secondary_ip_range")
70+
if !configSecondaryIpRange.IsKnown() {
71+
return nil
72+
}
73+
configValueIsEmpty := configSecondaryIpRange.IsNull() || configSecondaryIpRange.LengthInt() == 0
74+
75+
stateSecondaryIpRange := diff.GetRawState().GetAttr("secondary_ip_range")
76+
if !stateSecondaryIpRange.IsKnown() {
77+
return nil
78+
}
79+
stateValueIsEmpty := stateSecondaryIpRange.IsNull() || stateSecondaryIpRange.LengthInt() == 0
80+
81+
if configValueIsEmpty && !stateValueIsEmpty {
82+
log.Printf("[DEBUG] setting secondary_ip_range to newly empty")
83+
diff.SetNew("secondary_ip_range", make([]interface{}, 0))
84+
}
85+
86+
return nil
87+
}
88+
5889
func ResourceComputeSubnetwork() *schema.Resource {
5990
return &schema.Resource{
6091
Create: resourceComputeSubnetworkCreate,
@@ -75,6 +106,7 @@ func ResourceComputeSubnetwork() *schema.Resource {
75106
CustomizeDiff: customdiff.All(
76107
resourceComputeSubnetworkSecondaryIpRangeSetStyleDiff,
77108
customdiff.ForceNewIfChange("ip_cidr_range", IsShrinkageIpCidr),
109+
sendSecondaryIpRangeIfEmptyDiff,
78110
tpgresource.DefaultProviderProject,
79111
),
80112

@@ -251,10 +283,8 @@ to the primary ipCidrRange of the subnetwork. The alias IPs may belong
251283
to either primary or secondary ranges.
252284
253285
**Note**: This field uses [attr-as-block mode](https://www.terraform.io/docs/configuration/attr-as-blocks.html) to avoid
254-
breaking users during the 0.12 upgrade. To explicitly send a list
255-
of zero objects you must use the following syntax:
256-
'example=[]'
257-
For more details about this behavior, see [this section](https://www.terraform.io/docs/configuration/attr-as-blocks.html#defining-a-fixed-object-collection-value).`,
286+
breaking users during the 0.12 upgrade. To explicitly send a list of zero objects,
287+
set 'send_secondary_ip_range_if_empty = true'`,
258288
Elem: &schema.Resource{
259289
Schema: map[string]*schema.Schema{
260290
"ip_cidr_range": {
@@ -307,6 +337,16 @@ outside this subnetwork.`,
307337
Computed: true,
308338
Description: `The range of internal IPv6 addresses that are owned by this subnetwork.`,
309339
},
340+
"send_secondary_ip_range_if_empty": {
341+
Type: schema.TypeBool,
342+
Optional: true,
343+
Description: `Controls the removal behavior of secondary_ip_range.
344+
When false, removing secondary_ip_range from config will not produce a diff as
345+
the provider will default to the API's value.
346+
When true, the provider will treat removing secondary_ip_range as sending an
347+
empty list of secondary IP ranges to the API.
348+
Defaults to false.`,
349+
},
310350
"fingerprint": {
311351
Type: schema.TypeString,
312352
Computed: true,
@@ -557,6 +597,7 @@ func resourceComputeSubnetworkRead(d *schema.ResourceData, meta interface{}) err
557597
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("ComputeSubnetwork %q", d.Id()))
558598
}
559599

600+
// Explicitly set virtual fields to default values if unset
560601
if err := d.Set("project", project); err != nil {
561602
return fmt.Errorf("Error reading Subnetwork: %s", err)
562603
}
@@ -1016,6 +1057,78 @@ func resourceComputeSubnetworkUpdate(d *schema.ResourceData, meta interface{}) e
10161057

10171058
d.Partial(false)
10181059

1060+
if v, ok := d.GetOk("send_secondary_ip_range_if_empty"); ok && v.(bool) {
1061+
if sv, ok := d.GetOk("secondary_ip_range"); ok {
1062+
configValue := d.GetRawConfig().GetAttr("secondary_ip_range")
1063+
stateValue := sv.([]interface{})
1064+
if configValue.LengthInt() == 0 && len(stateValue) != 0 {
1065+
log.Printf("[DEBUG] Sending empty secondary_ip_range in update")
1066+
obj := make(map[string]interface{})
1067+
obj["secondaryIpRanges"] = make([]interface{}, 0)
1068+
1069+
// The rest is the same as the secondary_ip_range generated update code
1070+
// without the secondaryIpRangesProp logic
1071+
1072+
getUrl, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/regions/{{region}}/subnetworks/{{name}}")
1073+
if err != nil {
1074+
return err
1075+
}
1076+
1077+
// err == nil indicates that the billing_project value was found
1078+
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
1079+
billingProject = bp
1080+
}
1081+
1082+
getRes, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1083+
Config: config,
1084+
Method: "GET",
1085+
Project: billingProject,
1086+
RawURL: getUrl,
1087+
UserAgent: userAgent,
1088+
})
1089+
if err != nil {
1090+
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("ComputeSubnetwork %q", d.Id()))
1091+
}
1092+
1093+
obj["fingerprint"] = getRes["fingerprint"]
1094+
1095+
url, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/regions/{{region}}/subnetworks/{{name}}")
1096+
if err != nil {
1097+
return err
1098+
}
1099+
1100+
headers := make(http.Header)
1101+
1102+
// err == nil indicates that the billing_project value was found
1103+
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
1104+
billingProject = bp
1105+
}
1106+
1107+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1108+
Config: config,
1109+
Method: "PATCH",
1110+
Project: billingProject,
1111+
RawURL: url,
1112+
UserAgent: userAgent,
1113+
Body: obj,
1114+
Timeout: d.Timeout(schema.TimeoutUpdate),
1115+
Headers: headers,
1116+
})
1117+
if err != nil {
1118+
return fmt.Errorf("Error updating Subnetwork %q: %s", d.Id(), err)
1119+
} else {
1120+
log.Printf("[DEBUG] Finished updating Subnetwork %q: %#v", d.Id(), res)
1121+
}
1122+
1123+
err = ComputeOperationWaitTime(
1124+
config, res, project, "Updating Subnetwork", userAgent,
1125+
d.Timeout(schema.TimeoutUpdate))
1126+
if err != nil {
1127+
return err
1128+
}
1129+
}
1130+
}
1131+
}
10191132
return resourceComputeSubnetworkRead(d, meta)
10201133
}
10211134

@@ -1093,6 +1206,8 @@ func resourceComputeSubnetworkImport(d *schema.ResourceData, meta interface{}) (
10931206
}
10941207
d.SetId(id)
10951208

1209+
// Explicitly set virtual fields to default values on import
1210+
10961211
return []*schema.ResourceData{d}, nil
10971212
}
10981213

google/services/compute/resource_compute_subnetwork_test.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,92 @@ func TestAccComputeSubnetwork_secondaryIpRanges(t *testing.T) {
205205
})
206206
}
207207

208+
func TestAccComputeSubnetwork_secondaryIpRanges_sendEmpty(t *testing.T) {
209+
t.Parallel()
210+
211+
var subnetwork compute.Subnetwork
212+
213+
cnName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))
214+
subnetworkName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))
215+
216+
acctest.VcrTest(t, resource.TestCase{
217+
PreCheck: func() { acctest.AccTestPreCheck(t) },
218+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
219+
CheckDestroy: testAccCheckComputeSubnetworkDestroyProducer(t),
220+
Steps: []resource.TestStep{
221+
// Start without secondary_ip_range at all
222+
{
223+
Config: testAccComputeSubnetwork_sendEmpty_removed(cnName, subnetworkName, "true"),
224+
Check: resource.ComposeTestCheckFunc(
225+
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
226+
),
227+
},
228+
// Add one secondary_ip_range
229+
{
230+
Config: testAccComputeSubnetwork_sendEmpty_single(cnName, subnetworkName, "true"),
231+
Check: resource.ComposeTestCheckFunc(
232+
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
233+
testAccCheckComputeSubnetworkHasSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"),
234+
),
235+
},
236+
// Remove it with send_secondary_ip_range_if_empty = true
237+
{
238+
Config: testAccComputeSubnetwork_sendEmpty_removed(cnName, subnetworkName, "true"),
239+
Check: resource.ComposeTestCheckFunc(
240+
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
241+
testAccCheckComputeSubnetworkHasNotSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"),
242+
),
243+
},
244+
// Check that empty block secondary_ip_range = [] is not different
245+
{
246+
Config: testAccComputeSubnetwork_sendEmpty_emptyBlock(cnName, subnetworkName, "true"),
247+
PlanOnly: true,
248+
ExpectNonEmptyPlan: false,
249+
},
250+
// Apply two secondary_ip_range
251+
{
252+
Config: testAccComputeSubnetwork_sendEmpty_double(cnName, subnetworkName, "true"),
253+
Check: resource.ComposeTestCheckFunc(
254+
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
255+
testAccCheckComputeSubnetworkHasSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"),
256+
testAccCheckComputeSubnetworkHasSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update2", "192.168.11.0/24"),
257+
),
258+
},
259+
// Remove both with send_secondary_ip_range_if_empty = true
260+
{
261+
Config: testAccComputeSubnetwork_sendEmpty_removed(cnName, subnetworkName, "true"),
262+
Check: resource.ComposeTestCheckFunc(
263+
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
264+
testAccCheckComputeSubnetworkHasNotSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"),
265+
testAccCheckComputeSubnetworkHasNotSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update2", "192.168.11.0/24"),
266+
),
267+
},
268+
// Apply one secondary_ip_range
269+
{
270+
Config: testAccComputeSubnetwork_sendEmpty_single(cnName, subnetworkName, "false"),
271+
Check: resource.ComposeTestCheckFunc(
272+
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
273+
testAccCheckComputeSubnetworkHasSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"),
274+
),
275+
},
276+
// Check removing without send_secondary_ip_range_if_empty produces no diff (normal computed behavior)
277+
{
278+
Config: testAccComputeSubnetwork_sendEmpty_removed(cnName, subnetworkName, "false"),
279+
PlanOnly: true,
280+
ExpectNonEmptyPlan: false,
281+
},
282+
// Remove with empty block []
283+
{
284+
Config: testAccComputeSubnetwork_sendEmpty_emptyBlock(cnName, subnetworkName, "true"),
285+
Check: resource.ComposeTestCheckFunc(
286+
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
287+
testAccCheckComputeSubnetworkHasNotSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"),
288+
),
289+
},
290+
},
291+
})
292+
}
293+
208294
func TestAccComputeSubnetwork_flowLogs(t *testing.T) {
209295
t.Parallel()
210296

@@ -619,6 +705,91 @@ resource "google_compute_subnetwork" "network-with-private-secondary-ip-ranges"
619705
`, cnName, subnetworkName)
620706
}
621707

708+
func testAccComputeSubnetwork_sendEmpty_removed(cnName, subnetworkName, sendEmpty string) string {
709+
return fmt.Sprintf(`
710+
resource "google_compute_network" "custom-test" {
711+
name = "%s"
712+
auto_create_subnetworks = false
713+
}
714+
715+
resource "google_compute_subnetwork" "network-with-private-secondary-ip-ranges" {
716+
name = "%s"
717+
ip_cidr_range = "10.2.0.0/16"
718+
region = "us-central1"
719+
network = google_compute_network.custom-test.self_link
720+
send_secondary_ip_range_if_empty = "%s"
721+
}
722+
`, cnName, subnetworkName, sendEmpty)
723+
}
724+
725+
func testAccComputeSubnetwork_sendEmpty_emptyBlock(cnName, subnetworkName, sendEmpty string) string {
726+
return fmt.Sprintf(`
727+
resource "google_compute_network" "custom-test" {
728+
name = "%s"
729+
auto_create_subnetworks = false
730+
}
731+
732+
resource "google_compute_subnetwork" "network-with-private-secondary-ip-ranges" {
733+
name = "%s"
734+
ip_cidr_range = "10.2.0.0/16"
735+
region = "us-central1"
736+
network = google_compute_network.custom-test.self_link
737+
secondary_ip_range = []
738+
send_secondary_ip_range_if_empty = "%s"
739+
}
740+
`, cnName, subnetworkName, sendEmpty)
741+
}
742+
743+
func testAccComputeSubnetwork_sendEmpty_single(cnName, subnetworkName, sendEmpty string) string {
744+
return fmt.Sprintf(`
745+
resource "google_compute_network" "custom-test" {
746+
name = "%s"
747+
auto_create_subnetworks = false
748+
}
749+
750+
resource "google_compute_subnetwork" "network-with-private-secondary-ip-ranges" {
751+
name = "%s"
752+
ip_cidr_range = "10.2.0.0/16"
753+
region = "us-central1"
754+
network = google_compute_network.custom-test.self_link
755+
secondary_ip_range {
756+
range_name = "tf-test-secondary-range-update2"
757+
ip_cidr_range = "192.168.11.0/24"
758+
}
759+
secondary_ip_range {
760+
range_name = "tf-test-secondary-range-update1"
761+
ip_cidr_range = "192.168.10.0/24"
762+
}
763+
send_secondary_ip_range_if_empty = "%s"
764+
}
765+
`, cnName, subnetworkName, sendEmpty)
766+
}
767+
768+
func testAccComputeSubnetwork_sendEmpty_double(cnName, subnetworkName, sendEmpty string) string {
769+
return fmt.Sprintf(`
770+
resource "google_compute_network" "custom-test" {
771+
name = "%s"
772+
auto_create_subnetworks = false
773+
}
774+
775+
resource "google_compute_subnetwork" "network-with-private-secondary-ip-ranges" {
776+
name = "%s"
777+
ip_cidr_range = "10.2.0.0/16"
778+
region = "us-central1"
779+
network = google_compute_network.custom-test.self_link
780+
secondary_ip_range {
781+
range_name = "tf-test-secondary-range-update2"
782+
ip_cidr_range = "192.168.11.0/24"
783+
}
784+
secondary_ip_range {
785+
range_name = "tf-test-secondary-range-update1"
786+
ip_cidr_range = "192.168.10.0/24"
787+
}
788+
send_secondary_ip_range_if_empty = "%s"
789+
}
790+
`, cnName, subnetworkName, sendEmpty)
791+
}
792+
622793
func testAccComputeSubnetwork_flowLogs(cnName, subnetworkName string) string {
623794
return fmt.Sprintf(`
624795
resource "google_compute_network" "custom-test" {

website/docs/guides/version_6_upgrade.html.markdown

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,16 @@ Previously, `containers.env` was a list, making it order-dependent. It is now a
174174

175175
If you were relying on accessing an individual environment variable by index (for example, `google_cloud_run_v2_service.template.containers.0.env.0.name`), then that will now need to by hash (for example, `google_cloud_run_v2_service.template.containers.0.env.<some-hash>.name`).
176176

177+
## Resource: `google_compute_subnetwork`
178+
179+
### `secondary_ip_range = []` is no longer valid configuration
180+
181+
To explicitly set an empty list of objects, use `send_secondary_ip_range_if_empty = true` and completely remove `secondary_ip_range` from config.
182+
183+
Previously, to explicitly set `secondary_ip_range` as an empty list of objects, the specific configuration `secondary_ip_range = []` was necessary.
184+
This was to maintain compatability in behavior between Terraform versions 0.11 and 0.12 using a special setting ["attributes as blocks"](https://developer.hashicorp.com/terraform/language/attr-as-blocks).
185+
This special setting causes other breakages so it is now removed, with `send_secondary_ip_range_if_empty` available instead.
186+
177187
## Resource: `google_compute_backend_service`
178188

179189
## Resource: `google_compute_region_backend_service`

0 commit comments

Comments
 (0)