Skip to content

Commit 043e816

Browse files
rosboDmitry Vlasov
authored and
Dmitry Vlasov
committed
google_compute_instance can specified the subnetwork using a self_link (hashicorp#290)
1 parent dfe02ef commit 043e816

File tree

5 files changed

+83
-31
lines changed

5 files changed

+83
-31
lines changed

google/provider.go

+41
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
computeBeta "google.golang.org/api/compute/v0.beta"
1414
"google.golang.org/api/compute/v1"
1515
"google.golang.org/api/googleapi"
16+
"regexp"
1617
)
1718

1819
// Global MutexKV
@@ -273,6 +274,46 @@ func getNetworkLink(d *schema.ResourceData, config *Config, field string) (strin
273274
}
274275
}
275276

277+
// Reads the "subnetwork" fields from the given resource data and if the value is:
278+
// - a resource URL, returns the string unchanged
279+
// - a subnetwork name, looks up the resource URL using the google client.
280+
//
281+
// If `subnetworkField` is a resource url, `subnetworkProjectField` cannot be set.
282+
// If `subnetworkField` is a subnetwork name, `subnetworkProjectField` will be used
283+
// as the project if set. If not, we fallback on the default project.
284+
func getSubnetworkLink(d *schema.ResourceData, config *Config, subnetworkField, subnetworkProjectField, zoneField string) (string, error) {
285+
if v, ok := d.GetOk(subnetworkField); ok {
286+
subnetwork := v.(string)
287+
r := regexp.MustCompile(SubnetworkLinkRegex)
288+
if r.MatchString(subnetwork) {
289+
return subnetwork, nil
290+
}
291+
292+
var project string
293+
if subnetworkProject, ok := d.GetOk(subnetworkProjectField); ok {
294+
project = subnetworkProject.(string)
295+
} else {
296+
var err error
297+
project, err = getProject(d, config)
298+
if err != nil {
299+
return "", err
300+
}
301+
}
302+
303+
region := getRegionFromZone(d.Get(zoneField).(string))
304+
305+
subnet, err := config.clientCompute.Subnetworks.Get(project, region, subnetwork).Do()
306+
if err != nil {
307+
return "", fmt.Errorf(
308+
"Error referencing subnetwork '%s' in region '%s': %s",
309+
subnetwork, region, err)
310+
}
311+
312+
return subnet.SelfLink, nil
313+
}
314+
return "", nil
315+
}
316+
276317
// getNetworkName reads the "network" field from the given resource data and if the value:
277318
// - is a resource URL, extracts the network name from the URL and returns it
278319
// - is the network name only (i.e not prefixed with http://www.googleapis.com/compute/...), is returned unchanged

google/resource_compute_instance.go

+28-25
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/hashicorp/terraform/helper/validation"
1010
"google.golang.org/api/compute/v1"
1111
"google.golang.org/api/googleapi"
12+
"regexp"
1213
)
1314

1415
func stringScopeHashcode(v interface{}) int {
@@ -278,20 +279,26 @@ func resourceComputeInstance() *schema.Resource {
278279
Elem: &schema.Resource{
279280
Schema: map[string]*schema.Schema{
280281
"network": &schema.Schema{
281-
Type: schema.TypeString,
282-
Optional: true,
283-
ForceNew: true,
282+
Type: schema.TypeString,
283+
Optional: true,
284+
Computed: true,
285+
ForceNew: true,
286+
DiffSuppressFunc: linkDiffSuppress,
287+
ConflictsWith: []string{"network_interface.0.subnetwork", "network_interface.0.subnetwork_project"},
284288
},
285289

286290
"subnetwork": &schema.Schema{
287-
Type: schema.TypeString,
288-
Optional: true,
289-
ForceNew: true,
291+
Type: schema.TypeString,
292+
Optional: true,
293+
Computed: true,
294+
ForceNew: true,
295+
DiffSuppressFunc: linkDiffSuppress,
290296
},
291297

292298
"subnetwork_project": &schema.Schema{
293299
Type: schema.TypeString,
294300
Optional: true,
301+
Computed: true,
295302
ForceNew: true,
296303
},
297304

@@ -704,34 +711,21 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
704711
prefix := fmt.Sprintf("network_interface.%d", i)
705712
// Load up the name of this network_interface
706713
networkName := d.Get(prefix + ".network").(string)
707-
subnetworkName := d.Get(prefix + ".subnetwork").(string)
708-
subnetworkProject := d.Get(prefix + ".subnetwork_project").(string)
709714
address := d.Get(prefix + ".address").(string)
710715
var networkLink, subnetworkLink string
711716

712-
if networkName != "" && subnetworkName != "" {
713-
return fmt.Errorf("Cannot specify both network and subnetwork values.")
714-
} else if networkName != "" {
717+
if networkName != "" {
715718
networkLink, err = getNetworkLink(d, config, prefix+".network")
716719
if err != nil {
717720
return fmt.Errorf(
718721
"Error referencing network '%s': %s",
719722
networkName, err)
720723
}
721-
722724
} else {
723-
region := getRegionFromZone(d.Get("zone").(string))
724-
if subnetworkProject == "" {
725-
subnetworkProject = project
726-
}
727-
subnetwork, err := config.clientCompute.Subnetworks.Get(
728-
subnetworkProject, region, subnetworkName).Do()
725+
subnetworkLink, err = getSubnetworkLink(d, config, prefix+".subnetwork", prefix+".subnetwork_project", "zone")
729726
if err != nil {
730-
return fmt.Errorf(
731-
"Error referencing subnetwork '%s' in region '%s': %s",
732-
subnetworkName, region, err)
727+
return err
733728
}
734-
subnetworkLink = subnetwork.SelfLink
735729
}
736730

737731
// Build the networkInterface
@@ -961,9 +955,9 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
961955
networkInterfaces = append(networkInterfaces, map[string]interface{}{
962956
"name": iface.Name,
963957
"address": iface.NetworkIP,
964-
"network": d.Get(fmt.Sprintf("network_interface.%d.network", i)),
965-
"subnetwork": d.Get(fmt.Sprintf("network_interface.%d.subnetwork", i)),
966-
"subnetwork_project": d.Get(fmt.Sprintf("network_interface.%d.subnetwork_project", i)),
958+
"network": iface.Network,
959+
"subnetwork": iface.Subnetwork,
960+
"subnetwork_project": getProjectFromSubnetworkLink(iface.Subnetwork),
967961
"access_config": accessConfigs,
968962
})
969963
}
@@ -1452,3 +1446,12 @@ func flattenScratchDisk(disk *compute.AttachedDisk) map[string]interface{} {
14521446
}
14531447
return result
14541448
}
1449+
1450+
func getProjectFromSubnetworkLink(subnetwork string) string {
1451+
r := regexp.MustCompile(SubnetworkLinkRegex)
1452+
if !r.MatchString(subnetwork) {
1453+
return ""
1454+
}
1455+
1456+
return r.FindStringSubmatch(subnetwork)[1]
1457+
}

google/resource_compute_instance_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1609,7 +1609,7 @@ resource "google_compute_instance" "foobar" {
16091609
}
16101610
16111611
network_interface {
1612-
subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.name}"
1612+
subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.self_link}"
16131613
access_config { }
16141614
}
16151615

google/validation.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ import (
66
"regexp"
77
)
88

9-
// Copied from the official Google Cloud auto-generated client.
10-
const ProjectRegex = "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))"
9+
const (
10+
// Copied from the official Google Cloud auto-generated client.
11+
ProjectRegex = "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))"
12+
RegionRegex = "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?"
13+
SubnetworkRegex = "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?"
14+
15+
SubnetworkLinkRegex = "projects/(" + ProjectRegex + ")/regions/(" + RegionRegex + ")/subnetworks/(" + SubnetworkRegex + ")$"
16+
)
1117

1218
func validateGCPName(v interface{}, k string) (ws []string, errors []error) {
1319
re := `^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$`

website/docs/r/compute_instance.html.markdown

+5-3
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,14 @@ The `network_interface` block supports:
210210
* `network` - (Optional) The name or self_link of the network to attach this interface to.
211211
Either `network` or `subnetwork` must be provided.
212212

213-
* `subnetwork` - (Optional) The name of the subnetwork to attach this interface
214-
to. The subnetwork must exist in the same region this instance will be
213+
* `subnetwork` - (Optional) The name or self_link of the subnetwork to attach this
214+
interface to. The subnetwork must exist in the same region this instance will be
215215
created in. Either `network` or `subnetwork` must be provided.
216216

217217
* `subnetwork_project` - (Optional) The project in which the subnetwork belongs.
218-
If it is not provided, the provider project is used.
218+
If the `subnetwork` is a self_link, this field is ignored in favor of the project
219+
defined in the subnetwork self_link. If the `subnetwork` is a name and this
220+
field is not provided, the provider project is used.
219221

220222
* `address` - (Optional) The private IP address to assign to the instance. If
221223
empty, the address will be automatically assigned.

0 commit comments

Comments
 (0)