Skip to content

Commit baf6942

Browse files
fix error with missing zonal value for compute attached disk (#9371) (#16484)
[upstream:4eb33c5ac7d09b08667a54985fdfd015a8ba1b20] Signed-off-by: Modular Magician <[email protected]>
1 parent adad008 commit baf6942

File tree

4 files changed

+109
-1
lines changed

4 files changed

+109
-1
lines changed

.changelog/9371.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
compute: fixed issue where `google_compute_attached_disk` would produce an error for certain zone configs
3+
```

google/services/compute/resource_compute_attached_disk.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package compute
44

55
import (
6+
"context"
67
"fmt"
78
"log"
89
"strings"
@@ -11,6 +12,7 @@ import (
1112
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
1213
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
1314

15+
"github.com/hashicorp/go-cty/cty"
1416
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
1517
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1618
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
@@ -35,7 +37,7 @@ func ResourceComputeAttachedDisk() *schema.Resource {
3537

3638
CustomizeDiff: customdiff.All(
3739
tpgresource.DefaultProviderProject,
38-
tpgresource.DefaultProviderZone,
40+
computeAttachedDiskDefaultProviderZone,
3941
),
4042

4143
Schema: map[string]*schema.Schema{
@@ -263,3 +265,19 @@ func FindDiskByName(disks []*compute.AttachedDisk, id string) *compute.AttachedD
263265

264266
return nil
265267
}
268+
269+
func computeAttachedDiskDefaultProviderZone(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error {
270+
if diff.GetRawConfig().GetAttr("instance") == cty.UnknownVal(cty.String) {
271+
return nil
272+
}
273+
config := meta.(*transport_tpg.Config)
274+
zv, err := tpgresource.ParseZonalFieldValueDiff("instances", diff.Get("instance").(string), "project", "zone", diff, config, false)
275+
if err != nil {
276+
return err
277+
}
278+
if err := diff.SetNew("zone", zv.Zone); err != nil {
279+
return fmt.Errorf("Failed to retrieve zone: %s", err)
280+
}
281+
282+
return nil
283+
}

google/services/compute/resource_compute_attached_disk_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,37 @@ func TestAccComputeAttachedDisk_count(t *testing.T) {
122122

123123
}
124124

125+
func TestAccComputeAttachedDisk_zoneless(t *testing.T) {
126+
t.Setenv("GOOGLE_ZONE", "")
127+
128+
diskName := fmt.Sprintf("tf-test-disk-%d", acctest.RandInt(t))
129+
instanceName := fmt.Sprintf("tf-test-inst-%d", acctest.RandInt(t))
130+
importID := fmt.Sprintf("%s/us-central1-a/%s/%s", envvar.GetTestProjectFromEnv(), instanceName, diskName)
131+
132+
acctest.VcrTest(t, resource.TestCase{
133+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
134+
// Check destroy isn't a good test here, see comment on testCheckAttachedDiskIsNowDetached
135+
CheckDestroy: nil,
136+
Steps: []resource.TestStep{
137+
{
138+
Config: testAttachedDiskResource(diskName, instanceName) + testAttachedDiskResourceAttachment(),
139+
},
140+
{
141+
ResourceName: "google_compute_attached_disk.test",
142+
ImportStateId: importID,
143+
ImportState: true,
144+
ImportStateVerify: true,
145+
},
146+
{
147+
Config: testAttachedDiskResource(diskName, instanceName),
148+
Check: resource.ComposeTestCheckFunc(
149+
testCheckAttachedDiskIsNowDetached(t, instanceName, diskName),
150+
),
151+
},
152+
},
153+
})
154+
}
155+
125156
// testCheckAttachedDiskIsNowDetached queries a compute instance and iterates through the attached
126157
// disks to confirm that a specific disk is no longer attached to the instance
127158
//

google/tpgresource/field_helpers.go

+56
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"regexp"
88

9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
910
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
1011
)
1112

@@ -236,6 +237,61 @@ func ParseZonalFieldValue(resourceType, fieldValue, projectSchemaField, zoneSche
236237
}, nil
237238
}
238239

240+
// Parses a zonal field supporting 5 different formats:
241+
// - https://www.googleapis.com/compute/ANY_VERSION/projects/{my_project}/zones/{zone}/{resource_type}/{resource_name}
242+
// - projects/{my_project}/zones/{zone}/{resource_type}/{resource_name}
243+
// - zones/{zone}/{resource_type}/{resource_name}
244+
// - resource_name
245+
// - "" (empty string). RelativeLink() returns empty if isEmptyValid is true.
246+
//
247+
// If the project is not specified, it first tries to get the project from the `projectSchemaField` and then fallback on the default project.
248+
// If the zone is not specified, it takes the value of `zoneSchemaField`.
249+
func ParseZonalFieldValueDiff(resourceType, fieldValue, projectSchemaField, zoneSchemaField string, d *schema.ResourceDiff, config *transport_tpg.Config, isEmptyValid bool) (*ZonalFieldValue, error) {
250+
r := regexp.MustCompile(fmt.Sprintf(ZonalLinkBasePattern, resourceType))
251+
if parts := r.FindStringSubmatch(fieldValue); parts != nil {
252+
return &ZonalFieldValue{
253+
Project: parts[1],
254+
Zone: parts[2],
255+
Name: parts[3],
256+
ResourceType: resourceType,
257+
}, nil
258+
}
259+
260+
project, err := GetProjectFromDiff(d, config)
261+
if err != nil {
262+
return nil, err
263+
}
264+
265+
r = regexp.MustCompile(fmt.Sprintf(ZonalPartialLinkBasePattern, resourceType))
266+
if parts := r.FindStringSubmatch(fieldValue); parts != nil {
267+
return &ZonalFieldValue{
268+
Project: project,
269+
Zone: parts[1],
270+
Name: parts[2],
271+
ResourceType: resourceType,
272+
}, nil
273+
}
274+
275+
if len(zoneSchemaField) == 0 {
276+
return nil, fmt.Errorf("Invalid field format. Got '%s', expected format '%s'", fieldValue, fmt.Sprintf(GlobalLinkTemplate, "{project}", resourceType, "{name}"))
277+
}
278+
279+
zone, ok := d.GetOk(zoneSchemaField)
280+
if !ok {
281+
zone = config.Zone
282+
if zone == "" {
283+
return nil, fmt.Errorf("A zone must be specified")
284+
}
285+
}
286+
287+
return &ZonalFieldValue{
288+
Project: project,
289+
Zone: zone.(string),
290+
Name: GetResourceNameFromSelfLink(fieldValue),
291+
ResourceType: resourceType,
292+
}, nil
293+
}
294+
239295
func GetProjectFromSchema(projectSchemaField string, d TerraformResourceData, config *transport_tpg.Config) (string, error) {
240296
res, ok := d.GetOk(projectSchemaField)
241297
if ok && projectSchemaField != "" {

0 commit comments

Comments
 (0)