Skip to content

add send_secondary_ip_range_if_empty #7961

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changelog/11410.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
```release-note:deprecation
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.
```
```release-note:enhancement
compute: added `send_secondary_ip_range_if_empty` to `google_compute_subnetwork`
```
123 changes: 119 additions & 4 deletions google-beta/services/compute/resource_compute_subnetwork.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,37 @@ func IsShrinkageIpCidr(_ context.Context, old, new, _ interface{}) bool {
return true
}

func sendSecondaryIpRangeIfEmptyDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error {
// on create, return immediately as we don't need to determine if the value is empty or not
if diff.Id() == "" {
return nil
}

sendZero := diff.Get("send_secondary_ip_range_if_empty").(bool)
if !sendZero {
return nil
}

configSecondaryIpRange := diff.GetRawConfig().GetAttr("secondary_ip_range")
if !configSecondaryIpRange.IsKnown() {
return nil
}
configValueIsEmpty := configSecondaryIpRange.IsNull() || configSecondaryIpRange.LengthInt() == 0

stateSecondaryIpRange := diff.GetRawState().GetAttr("secondary_ip_range")
if !stateSecondaryIpRange.IsKnown() {
return nil
}
stateValueIsEmpty := stateSecondaryIpRange.IsNull() || stateSecondaryIpRange.LengthInt() == 0

if configValueIsEmpty && !stateValueIsEmpty {
log.Printf("[DEBUG] setting secondary_ip_range to newly empty")
diff.SetNew("secondary_ip_range", make([]interface{}, 0))
}

return nil
}

func ResourceComputeSubnetwork() *schema.Resource {
return &schema.Resource{
Create: resourceComputeSubnetworkCreate,
Expand All @@ -75,6 +106,7 @@ func ResourceComputeSubnetwork() *schema.Resource {
CustomizeDiff: customdiff.All(
resourceComputeSubnetworkSecondaryIpRangeSetStyleDiff,
customdiff.ForceNewIfChange("ip_cidr_range", IsShrinkageIpCidr),
sendSecondaryIpRangeIfEmptyDiff,
tpgresource.DefaultProviderProject,
),

Expand Down Expand Up @@ -260,10 +292,8 @@ to the primary ipCidrRange of the subnetwork. The alias IPs may belong
to either primary or secondary ranges.

**Note**: This field uses [attr-as-block mode](https://www.terraform.io/docs/configuration/attr-as-blocks.html) to avoid
breaking users during the 0.12 upgrade. To explicitly send a list
of zero objects you must use the following syntax:
'example=[]'
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).`,
breaking users during the 0.12 upgrade. To explicitly send a list of zero objects,
set 'send_secondary_ip_range_if_empty = true'`,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"ip_cidr_range": {
Expand Down Expand Up @@ -316,6 +346,16 @@ outside this subnetwork.`,
Computed: true,
Description: `The range of internal IPv6 addresses that are owned by this subnetwork.`,
},
"send_secondary_ip_range_if_empty": {
Type: schema.TypeBool,
Optional: true,
Description: `Controls the removal behavior of secondary_ip_range.
When false, removing secondary_ip_range from config will not produce a diff as
the provider will default to the API's value.
When true, the provider will treat removing secondary_ip_range as sending an
empty list of secondary IP ranges to the API.
Defaults to false.`,
},
"fingerprint": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -572,6 +612,7 @@ func resourceComputeSubnetworkRead(d *schema.ResourceData, meta interface{}) err
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("ComputeSubnetwork %q", d.Id()))
}

// Explicitly set virtual fields to default values if unset
if err := d.Set("project", project); err != nil {
return fmt.Errorf("Error reading Subnetwork: %s", err)
}
Expand Down Expand Up @@ -1040,6 +1081,78 @@ func resourceComputeSubnetworkUpdate(d *schema.ResourceData, meta interface{}) e

d.Partial(false)

if v, ok := d.GetOk("send_secondary_ip_range_if_empty"); ok && v.(bool) {
if sv, ok := d.GetOk("secondary_ip_range"); ok {
configValue := d.GetRawConfig().GetAttr("secondary_ip_range")
stateValue := sv.([]interface{})
if configValue.LengthInt() == 0 && len(stateValue) != 0 {
log.Printf("[DEBUG] Sending empty secondary_ip_range in update")
obj := make(map[string]interface{})
obj["secondaryIpRanges"] = make([]interface{}, 0)

// The rest is the same as the secondary_ip_range generated update code
// without the secondaryIpRangesProp logic

getUrl, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/regions/{{region}}/subnetworks/{{name}}")
if err != nil {
return err
}

// err == nil indicates that the billing_project value was found
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
billingProject = bp
}

getRes, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: billingProject,
RawURL: getUrl,
UserAgent: userAgent,
})
if err != nil {
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("ComputeSubnetwork %q", d.Id()))
}

obj["fingerprint"] = getRes["fingerprint"]

url, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/regions/{{region}}/subnetworks/{{name}}")
if err != nil {
return err
}

headers := make(http.Header)

// err == nil indicates that the billing_project value was found
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
billingProject = bp
}

res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "PATCH",
Project: billingProject,
RawURL: url,
UserAgent: userAgent,
Body: obj,
Timeout: d.Timeout(schema.TimeoutUpdate),
Headers: headers,
})
if err != nil {
return fmt.Errorf("Error updating Subnetwork %q: %s", d.Id(), err)
} else {
log.Printf("[DEBUG] Finished updating Subnetwork %q: %#v", d.Id(), res)
}

err = ComputeOperationWaitTime(
config, res, project, "Updating Subnetwork", userAgent,
d.Timeout(schema.TimeoutUpdate))
if err != nil {
return err
}
}
}
}
return resourceComputeSubnetworkRead(d, meta)
}

Expand Down Expand Up @@ -1117,6 +1230,8 @@ func resourceComputeSubnetworkImport(d *schema.ResourceData, meta interface{}) (
}
d.SetId(id)

// Explicitly set virtual fields to default values on import

return []*schema.ResourceData{d}, nil
}

Expand Down
171 changes: 171 additions & 0 deletions google-beta/services/compute/resource_compute_subnetwork_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,92 @@ func TestAccComputeSubnetwork_secondaryIpRanges(t *testing.T) {
})
}

func TestAccComputeSubnetwork_secondaryIpRanges_sendEmpty(t *testing.T) {
t.Parallel()

var subnetwork compute.Subnetwork

cnName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))
subnetworkName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckComputeSubnetworkDestroyProducer(t),
Steps: []resource.TestStep{
// Start without secondary_ip_range at all
{
Config: testAccComputeSubnetwork_sendEmpty_removed(cnName, subnetworkName, "true"),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
),
},
// Add one secondary_ip_range
{
Config: testAccComputeSubnetwork_sendEmpty_single(cnName, subnetworkName, "true"),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
testAccCheckComputeSubnetworkHasSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"),
),
},
// Remove it with send_secondary_ip_range_if_empty = true
{
Config: testAccComputeSubnetwork_sendEmpty_removed(cnName, subnetworkName, "true"),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
testAccCheckComputeSubnetworkHasNotSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"),
),
},
// Check that empty block secondary_ip_range = [] is not different
{
Config: testAccComputeSubnetwork_sendEmpty_emptyBlock(cnName, subnetworkName, "true"),
PlanOnly: true,
ExpectNonEmptyPlan: false,
},
// Apply two secondary_ip_range
{
Config: testAccComputeSubnetwork_sendEmpty_double(cnName, subnetworkName, "true"),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
testAccCheckComputeSubnetworkHasSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"),
testAccCheckComputeSubnetworkHasSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update2", "192.168.11.0/24"),
),
},
// Remove both with send_secondary_ip_range_if_empty = true
{
Config: testAccComputeSubnetwork_sendEmpty_removed(cnName, subnetworkName, "true"),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
testAccCheckComputeSubnetworkHasNotSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"),
testAccCheckComputeSubnetworkHasNotSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update2", "192.168.11.0/24"),
),
},
// Apply one secondary_ip_range
{
Config: testAccComputeSubnetwork_sendEmpty_single(cnName, subnetworkName, "false"),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
testAccCheckComputeSubnetworkHasSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"),
),
},
// Check removing without send_secondary_ip_range_if_empty produces no diff (normal computed behavior)
{
Config: testAccComputeSubnetwork_sendEmpty_removed(cnName, subnetworkName, "false"),
PlanOnly: true,
ExpectNonEmptyPlan: false,
},
// Remove with empty block []
{
Config: testAccComputeSubnetwork_sendEmpty_emptyBlock(cnName, subnetworkName, "true"),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeSubnetworkExists(t, "google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork),
testAccCheckComputeSubnetworkHasNotSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"),
),
},
},
})
}

func TestAccComputeSubnetwork_flowLogs(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -619,6 +705,91 @@ resource "google_compute_subnetwork" "network-with-private-secondary-ip-ranges"
`, cnName, subnetworkName)
}

func testAccComputeSubnetwork_sendEmpty_removed(cnName, subnetworkName, sendEmpty string) string {
return fmt.Sprintf(`
resource "google_compute_network" "custom-test" {
name = "%s"
auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "network-with-private-secondary-ip-ranges" {
name = "%s"
ip_cidr_range = "10.2.0.0/16"
region = "us-central1"
network = google_compute_network.custom-test.self_link
send_secondary_ip_range_if_empty = "%s"
}
`, cnName, subnetworkName, sendEmpty)
}

func testAccComputeSubnetwork_sendEmpty_emptyBlock(cnName, subnetworkName, sendEmpty string) string {
return fmt.Sprintf(`
resource "google_compute_network" "custom-test" {
name = "%s"
auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "network-with-private-secondary-ip-ranges" {
name = "%s"
ip_cidr_range = "10.2.0.0/16"
region = "us-central1"
network = google_compute_network.custom-test.self_link
secondary_ip_range = []
send_secondary_ip_range_if_empty = "%s"
}
`, cnName, subnetworkName, sendEmpty)
}

func testAccComputeSubnetwork_sendEmpty_single(cnName, subnetworkName, sendEmpty string) string {
return fmt.Sprintf(`
resource "google_compute_network" "custom-test" {
name = "%s"
auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "network-with-private-secondary-ip-ranges" {
name = "%s"
ip_cidr_range = "10.2.0.0/16"
region = "us-central1"
network = google_compute_network.custom-test.self_link
secondary_ip_range {
range_name = "tf-test-secondary-range-update2"
ip_cidr_range = "192.168.11.0/24"
}
secondary_ip_range {
range_name = "tf-test-secondary-range-update1"
ip_cidr_range = "192.168.10.0/24"
}
send_secondary_ip_range_if_empty = "%s"
}
`, cnName, subnetworkName, sendEmpty)
}

func testAccComputeSubnetwork_sendEmpty_double(cnName, subnetworkName, sendEmpty string) string {
return fmt.Sprintf(`
resource "google_compute_network" "custom-test" {
name = "%s"
auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "network-with-private-secondary-ip-ranges" {
name = "%s"
ip_cidr_range = "10.2.0.0/16"
region = "us-central1"
network = google_compute_network.custom-test.self_link
secondary_ip_range {
range_name = "tf-test-secondary-range-update2"
ip_cidr_range = "192.168.11.0/24"
}
secondary_ip_range {
range_name = "tf-test-secondary-range-update1"
ip_cidr_range = "192.168.10.0/24"
}
send_secondary_ip_range_if_empty = "%s"
}
`, cnName, subnetworkName, sendEmpty)
}

func testAccComputeSubnetwork_flowLogs(cnName, subnetworkName string) string {
return fmt.Sprintf(`
resource "google_compute_network" "custom-test" {
Expand Down
10 changes: 10 additions & 0 deletions website/docs/guides/version_6_upgrade.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,16 @@ Previously, `containers.env` was a list, making it order-dependent. It is now a

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`).

## Resource: `google_compute_subnetwork`

### `secondary_ip_range = []` is no longer valid configuration

To explicitly set an empty list of objects, use `send_secondary_ip_range_if_empty = true` and completely remove `secondary_ip_range` from config.

Previously, to explicitly set `secondary_ip_range` as an empty list of objects, the specific configuration `secondary_ip_range = []` was necessary.
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).
This special setting causes other breakages so it is now removed, with `send_secondary_ip_range_if_empty` available instead.

## Resource: `google_compute_backend_service`

## Resource: `google_compute_region_backend_service`
Expand Down
Loading