Skip to content

Commit cb8bba0

Browse files
id of instance templates now relies on the unique id of the resource (#7358) (#14128)
Signed-off-by: Modular Magician <[email protected]>
1 parent a214d6e commit cb8bba0

15 files changed

+446
-28
lines changed

.changelog/7358.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
compute: added output-only attribute `self_link_unique` id for `google_compute_instance_template` to point to the unique id of the resource (instead of its name). It is recommended to use `self_link_unique` for references over `self_link` and `id` for `google_compute_instance_template`
3+
```

google/data_source_google_compute_instance_template.go

+21-4
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,22 @@ func DataSourceGoogleComputeInstanceTemplate() *schema.Resource {
1717
Type: schema.TypeString,
1818
Optional: true,
1919
}
20+
dsSchema["self_link_unique"] = &schema.Schema{
21+
Type: schema.TypeString,
22+
Optional: true,
23+
}
2024
dsSchema["most_recent"] = &schema.Schema{
2125
Type: schema.TypeBool,
2226
Optional: true,
2327
}
2428

2529
// Set 'Optional' schema elements
26-
addOptionalFieldsToSchema(dsSchema, "name", "filter", "most_recent", "project")
30+
addOptionalFieldsToSchema(dsSchema, "name", "filter", "most_recent", "project", "self_link_unique")
2731

28-
dsSchema["name"].ExactlyOneOf = []string{"name", "filter"}
29-
dsSchema["filter"].ExactlyOneOf = []string{"name", "filter"}
32+
mutuallyExclusive := []string{"name", "filter", "self_link_unique"}
33+
for _, n := range mutuallyExclusive {
34+
dsSchema[n].ExactlyOneOf = mutuallyExclusive
35+
}
3036

3137
return &schema.Resource{
3238
Read: datasourceComputeInstanceTemplateRead,
@@ -68,8 +74,11 @@ func datasourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interfac
6874

6975
return fmt.Errorf("your filter has returned %d instance template(s). Please refine your filter or set most_recent to return exactly one instance template", len(templates.Items))
7076
}
77+
if v, ok := d.GetOk("self_link_unique"); ok {
78+
return retrieveInstanceFromUniqueId(d, meta, project, v.(string))
79+
}
7180

72-
return fmt.Errorf("one of name or filters must be set")
81+
return fmt.Errorf("one of name, filters or self_link_unique must be set")
7382
}
7483

7584
func retrieveInstance(d *schema.ResourceData, meta interface{}, project, name string) error {
@@ -78,6 +87,14 @@ func retrieveInstance(d *schema.ResourceData, meta interface{}, project, name st
7887
return resourceComputeInstanceTemplateRead(d, meta)
7988
}
8089

90+
func retrieveInstanceFromUniqueId(d *schema.ResourceData, meta interface{}, project, self_link_unique string) error {
91+
normalId, _ := parseUniqueId(self_link_unique)
92+
d.SetId(normalId)
93+
d.Set("self_link_unique", self_link_unique)
94+
95+
return resourceComputeInstanceTemplateRead(d, meta)
96+
}
97+
8198
// ByCreationTimestamp implements sort.Interface for []*InstanceTemplate based on
8299
// the CreationTimestamp field.
83100
type ByCreationTimestamp []*compute.InstanceTemplate

google/data_source_google_compute_instance_template_test.go

+53
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,31 @@ func TestAccInstanceTemplateDatasource_filter_mostRecent(t *testing.T) {
6969
})
7070
}
7171

72+
func TestAccInstanceTemplateDatasource_self_link_unique(t *testing.T) {
73+
t.Parallel()
74+
75+
VcrTest(t, resource.TestCase{
76+
PreCheck: func() { testAccPreCheck(t) },
77+
Providers: TestAccProviders,
78+
Steps: []resource.TestStep{
79+
{
80+
Config: testAccInstanceTemplate_self_link_unique(GetTestProjectFromEnv(), RandString(t, 10)),
81+
Check: resource.ComposeTestCheckFunc(
82+
checkDataSourceStateMatchesResourceStateWithIgnores(
83+
"data.google_compute_instance_template.default",
84+
"google_compute_instance_template.default",
85+
// we don't compare the id here as we start this test from a self_link_unique url
86+
// and the resource's ID will have the standard format project/projectname/global/instanceTemplates/tf-test-template-random
87+
map[string]struct{}{
88+
"id": {},
89+
},
90+
),
91+
),
92+
},
93+
},
94+
})
95+
}
96+
7297
func testAccInstanceTemplate_name(project, suffix string) string {
7398
return Nprintf(`
7499
resource "google_compute_instance_template" "default" {
@@ -238,3 +263,31 @@ data "google_compute_instance_template" "default" {
238263
}
239264
`, map[string]interface{}{"project": project, "suffix": suffix})
240265
}
266+
267+
func testAccInstanceTemplate_self_link_unique(project, suffix string) string {
268+
return Nprintf(`
269+
resource "google_compute_instance_template" "default" {
270+
name = "tf-test-template-%{suffix}"
271+
description = "Example template."
272+
273+
machine_type = "e2-small"
274+
275+
tags = ["foo", "bar"]
276+
277+
disk {
278+
source_image = "cos-cloud/cos-stable"
279+
auto_delete = true
280+
boot = true
281+
}
282+
283+
network_interface {
284+
network = "default"
285+
}
286+
}
287+
288+
data "google_compute_instance_template" "default" {
289+
project = "%{project}"
290+
self_link_unique = google_compute_instance_template.default.self_link_unique
291+
}
292+
`, map[string]interface{}{"project": project, "suffix": suffix})
293+
}

google/resource_compute_instance_from_template.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,7 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte
118118
return err
119119
}
120120

121-
sourceInstanceTemplate := d.Get("source_instance_template").(string)
122-
121+
sourceInstanceTemplate := ConvertToUniqueIdWhenPresent(d.Get("source_instance_template").(string))
123122
tpl, err := ParseInstanceTemplateFieldValue(sourceInstanceTemplate, d, config)
124123
if err != nil {
125124
return err

google/resource_compute_instance_from_template_test.go

+102
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,34 @@ func TestAccComputeInstanceFromTemplate_basic(t *testing.T) {
3939
})
4040
}
4141

42+
func TestAccComputeInstanceFromTemplate_self_link_unique(t *testing.T) {
43+
t.Parallel()
44+
45+
var instance compute.Instance
46+
instanceName := fmt.Sprintf("tf-test-%s", RandString(t, 10))
47+
templateName := fmt.Sprintf("tf-test-%s", RandString(t, 10))
48+
resourceName := "google_compute_instance_from_template.foobar"
49+
50+
VcrTest(t, resource.TestCase{
51+
PreCheck: func() { testAccPreCheck(t) },
52+
Providers: TestAccProviders,
53+
CheckDestroy: testAccCheckComputeInstanceFromTemplateDestroyProducer(t),
54+
Steps: []resource.TestStep{
55+
{
56+
Config: testAccComputeInstanceFromTemplate_self_link_unique(instanceName, templateName),
57+
Check: resource.ComposeTestCheckFunc(
58+
testAccCheckComputeInstanceExists(t, resourceName, &instance),
59+
60+
// Check that fields were set based on the template
61+
resource.TestCheckResourceAttr(resourceName, "machine_type", "n1-standard-1"),
62+
resource.TestCheckResourceAttr(resourceName, "attached_disk.#", "1"),
63+
resource.TestCheckResourceAttr(resourceName, "scheduling.0.automatic_restart", "false"),
64+
),
65+
},
66+
},
67+
})
68+
}
69+
4270
func TestAccComputeInstanceFromRegionTemplate_basic(t *testing.T) {
4371
t.Parallel()
4472

@@ -413,6 +441,80 @@ resource "google_compute_instance_from_template" "foobar" {
413441
`, template, template, instance)
414442
}
415443

444+
func testAccComputeInstanceFromTemplate_self_link_unique(instance, template string) string {
445+
return fmt.Sprintf(`
446+
data "google_compute_image" "my_image" {
447+
family = "debian-11"
448+
project = "debian-cloud"
449+
}
450+
451+
resource "google_compute_disk" "foobar" {
452+
name = "%s"
453+
image = data.google_compute_image.my_image.self_link
454+
size = 10
455+
type = "pd-ssd"
456+
zone = "us-central1-a"
457+
}
458+
459+
resource "google_compute_instance_template" "foobar" {
460+
name = "%s"
461+
machine_type = "n1-standard-1" // can't be e2 because of local-ssd
462+
463+
disk {
464+
source = google_compute_disk.foobar.name
465+
auto_delete = false
466+
boot = true
467+
}
468+
469+
disk {
470+
disk_type = "local-ssd"
471+
type = "SCRATCH"
472+
interface = "NVME"
473+
disk_size_gb = 375
474+
}
475+
476+
disk {
477+
source_image = data.google_compute_image.my_image.self_link
478+
auto_delete = true
479+
disk_size_gb = 100
480+
boot = false
481+
disk_type = "pd-ssd"
482+
type = "PERSISTENT"
483+
}
484+
485+
network_interface {
486+
network = "default"
487+
}
488+
489+
metadata = {
490+
foo = "bar"
491+
}
492+
493+
scheduling {
494+
automatic_restart = true
495+
}
496+
497+
can_ip_forward = true
498+
}
499+
500+
resource "google_compute_instance_from_template" "foobar" {
501+
name = "%s"
502+
zone = "us-central1-a"
503+
504+
source_instance_template = google_compute_instance_template.foobar.self_link_unique
505+
506+
// Overrides
507+
can_ip_forward = false
508+
labels = {
509+
my_key = "my_value"
510+
}
511+
scheduling {
512+
automatic_restart = false
513+
}
514+
}
515+
`, template, template, instance)
516+
}
517+
416518
func testAccComputeInstanceFromTemplate_overrideBootDisk(templateDisk, overrideDisk, template, instance string) string {
417519
return fmt.Sprintf(`
418520
data "google_compute_image" "my_image" {

google/resource_compute_instance_group_manager.go

+29-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func ResourceComputeInstanceGroupManager() *schema.Resource {
5050
"instance_template": {
5151
Type: schema.TypeString,
5252
Required: true,
53-
DiffSuppressFunc: compareSelfLinkRelativePaths,
53+
DiffSuppressFunc: compareSelfLinkRelativePathsIgnoreParams,
5454
Description: `The full URL to an instance template from which all new instances of this version will be created.`,
5555
},
5656

@@ -371,6 +371,33 @@ func ResourceComputeInstanceGroupManager() *schema.Resource {
371371
}
372372
}
373373

374+
func parseUniqueId(s string) (string, string) {
375+
splits := strings.SplitN(s, "?uniqueId=", 2)
376+
if len(splits) == 2 {
377+
return splits[0], splits[1]
378+
}
379+
return s, ""
380+
}
381+
382+
func compareSelfLinkRelativePathsIgnoreParams(_unused1, old, new string, _unused2 *schema.ResourceData) bool {
383+
oldName, oldUniqueId := parseUniqueId(old)
384+
newName, newUniqueId := parseUniqueId(new)
385+
if oldUniqueId != "" && newUniqueId != "" && oldUniqueId != newUniqueId {
386+
return false
387+
}
388+
return compareSelfLinkRelativePaths(_unused1, oldName, newName, _unused2)
389+
}
390+
391+
func ConvertToUniqueIdWhenPresent(s string) string {
392+
original, uniqueId := parseUniqueId(s)
393+
if uniqueId != "" {
394+
splits := strings.Split(original, "/")
395+
splits[len(splits)-1] = uniqueId
396+
return strings.Join(splits, "/")
397+
}
398+
return s
399+
}
400+
374401
func getNamedPorts(nps []interface{}) []*compute.NamedPort {
375402
namedPorts := make([]*compute.NamedPort, 0, len(nps))
376403
for _, v := range nps {
@@ -935,7 +962,7 @@ func expandVersions(configured []interface{}) []*compute.InstanceGroupManagerVer
935962

936963
version := compute.InstanceGroupManagerVersion{
937964
Name: data["name"].(string),
938-
InstanceTemplate: data["instance_template"].(string),
965+
InstanceTemplate: ConvertToUniqueIdWhenPresent(data["instance_template"].(string)),
939966
TargetSize: expandFixedOrPercent(data["target_size"].([]interface{})),
940967
}
941968

0 commit comments

Comments
 (0)