google_compute_instance can specified the subnetwork using a self_link (#290)

This commit is contained in:
Vincent Roseberry 2017-08-04 11:00:45 -07:00 committed by GitHub
parent b95fb1b6f9
commit 3fde8cf24e
5 changed files with 83 additions and 31 deletions

View File

@ -13,6 +13,7 @@ import (
computeBeta "google.golang.org/api/compute/v0.beta" computeBeta "google.golang.org/api/compute/v0.beta"
"google.golang.org/api/compute/v1" "google.golang.org/api/compute/v1"
"google.golang.org/api/googleapi" "google.golang.org/api/googleapi"
"regexp"
) )
// Global MutexKV // Global MutexKV
@ -273,6 +274,46 @@ func getNetworkLink(d *schema.ResourceData, config *Config, field string) (strin
} }
} }
// Reads the "subnetwork" fields from the given resource data and if the value is:
// - a resource URL, returns the string unchanged
// - a subnetwork name, looks up the resource URL using the google client.
//
// If `subnetworkField` is a resource url, `subnetworkProjectField` cannot be set.
// If `subnetworkField` is a subnetwork name, `subnetworkProjectField` will be used
// as the project if set. If not, we fallback on the default project.
func getSubnetworkLink(d *schema.ResourceData, config *Config, subnetworkField, subnetworkProjectField, zoneField string) (string, error) {
if v, ok := d.GetOk(subnetworkField); ok {
subnetwork := v.(string)
r := regexp.MustCompile(SubnetworkLinkRegex)
if r.MatchString(subnetwork) {
return subnetwork, nil
}
var project string
if subnetworkProject, ok := d.GetOk(subnetworkProjectField); ok {
project = subnetworkProject.(string)
} else {
var err error
project, err = getProject(d, config)
if err != nil {
return "", err
}
}
region := getRegionFromZone(d.Get(zoneField).(string))
subnet, err := config.clientCompute.Subnetworks.Get(project, region, subnetwork).Do()
if err != nil {
return "", fmt.Errorf(
"Error referencing subnetwork '%s' in region '%s': %s",
subnetwork, region, err)
}
return subnet.SelfLink, nil
}
return "", nil
}
// getNetworkName reads the "network" field from the given resource data and if the value: // getNetworkName reads the "network" field from the given resource data and if the value:
// - is a resource URL, extracts the network name from the URL and returns it // - is a resource URL, extracts the network name from the URL and returns it
// - is the network name only (i.e not prefixed with http://www.googleapis.com/compute/...), is returned unchanged // - is the network name only (i.e not prefixed with http://www.googleapis.com/compute/...), is returned unchanged

View File

@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform/helper/validation" "github.com/hashicorp/terraform/helper/validation"
"google.golang.org/api/compute/v1" "google.golang.org/api/compute/v1"
"google.golang.org/api/googleapi" "google.golang.org/api/googleapi"
"regexp"
) )
func stringScopeHashcode(v interface{}) int { func stringScopeHashcode(v interface{}) int {
@ -278,20 +279,26 @@ func resourceComputeInstance() *schema.Resource {
Elem: &schema.Resource{ Elem: &schema.Resource{
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"network": &schema.Schema{ "network": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, Computed: true,
ForceNew: true,
DiffSuppressFunc: linkDiffSuppress,
ConflictsWith: []string{"network_interface.0.subnetwork", "network_interface.0.subnetwork_project"},
}, },
"subnetwork": &schema.Schema{ "subnetwork": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, Computed: true,
ForceNew: true,
DiffSuppressFunc: linkDiffSuppress,
}, },
"subnetwork_project": &schema.Schema{ "subnetwork_project": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true,
ForceNew: true, ForceNew: true,
}, },
@ -704,34 +711,21 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
prefix := fmt.Sprintf("network_interface.%d", i) prefix := fmt.Sprintf("network_interface.%d", i)
// Load up the name of this network_interface // Load up the name of this network_interface
networkName := d.Get(prefix + ".network").(string) networkName := d.Get(prefix + ".network").(string)
subnetworkName := d.Get(prefix + ".subnetwork").(string)
subnetworkProject := d.Get(prefix + ".subnetwork_project").(string)
address := d.Get(prefix + ".address").(string) address := d.Get(prefix + ".address").(string)
var networkLink, subnetworkLink string var networkLink, subnetworkLink string
if networkName != "" && subnetworkName != "" { if networkName != "" {
return fmt.Errorf("Cannot specify both network and subnetwork values.")
} else if networkName != "" {
networkLink, err = getNetworkLink(d, config, prefix+".network") networkLink, err = getNetworkLink(d, config, prefix+".network")
if err != nil { if err != nil {
return fmt.Errorf( return fmt.Errorf(
"Error referencing network '%s': %s", "Error referencing network '%s': %s",
networkName, err) networkName, err)
} }
} else { } else {
region := getRegionFromZone(d.Get("zone").(string)) subnetworkLink, err = getSubnetworkLink(d, config, prefix+".subnetwork", prefix+".subnetwork_project", "zone")
if subnetworkProject == "" {
subnetworkProject = project
}
subnetwork, err := config.clientCompute.Subnetworks.Get(
subnetworkProject, region, subnetworkName).Do()
if err != nil { if err != nil {
return fmt.Errorf( return err
"Error referencing subnetwork '%s' in region '%s': %s",
subnetworkName, region, err)
} }
subnetworkLink = subnetwork.SelfLink
} }
// Build the networkInterface // Build the networkInterface
@ -961,9 +955,9 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
networkInterfaces = append(networkInterfaces, map[string]interface{}{ networkInterfaces = append(networkInterfaces, map[string]interface{}{
"name": iface.Name, "name": iface.Name,
"address": iface.NetworkIP, "address": iface.NetworkIP,
"network": d.Get(fmt.Sprintf("network_interface.%d.network", i)), "network": iface.Network,
"subnetwork": d.Get(fmt.Sprintf("network_interface.%d.subnetwork", i)), "subnetwork": iface.Subnetwork,
"subnetwork_project": d.Get(fmt.Sprintf("network_interface.%d.subnetwork_project", i)), "subnetwork_project": getProjectFromSubnetworkLink(iface.Subnetwork),
"access_config": accessConfigs, "access_config": accessConfigs,
}) })
} }
@ -1452,3 +1446,12 @@ func flattenScratchDisk(disk *compute.AttachedDisk) map[string]interface{} {
} }
return result return result
} }
func getProjectFromSubnetworkLink(subnetwork string) string {
r := regexp.MustCompile(SubnetworkLinkRegex)
if !r.MatchString(subnetwork) {
return ""
}
return r.FindStringSubmatch(subnetwork)[1]
}

View File

@ -1609,7 +1609,7 @@ resource "google_compute_instance" "foobar" {
} }
network_interface { network_interface {
subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.name}" subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.self_link}"
access_config { } access_config { }
} }

View File

@ -6,8 +6,14 @@ import (
"regexp" "regexp"
) )
// Copied from the official Google Cloud auto-generated client. const (
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])?))" // Copied from the official Google Cloud auto-generated client.
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])?))"
RegionRegex = "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?"
SubnetworkRegex = "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?"
SubnetworkLinkRegex = "projects/(" + ProjectRegex + ")/regions/(" + RegionRegex + ")/subnetworks/(" + SubnetworkRegex + ")$"
)
func validateGCPName(v interface{}, k string) (ws []string, errors []error) { func validateGCPName(v interface{}, k string) (ws []string, errors []error) {
re := `^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$` re := `^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$`

View File

@ -210,12 +210,14 @@ The `network_interface` block supports:
* `network` - (Optional) The name or self_link of the network to attach this interface to. * `network` - (Optional) The name or self_link of the network to attach this interface to.
Either `network` or `subnetwork` must be provided. Either `network` or `subnetwork` must be provided.
* `subnetwork` - (Optional) The name of the subnetwork to attach this interface * `subnetwork` - (Optional) The name or self_link of the subnetwork to attach this
to. The subnetwork must exist in the same region this instance will be interface to. The subnetwork must exist in the same region this instance will be
created in. Either `network` or `subnetwork` must be provided. created in. Either `network` or `subnetwork` must be provided.
* `subnetwork_project` - (Optional) The project in which the subnetwork belongs. * `subnetwork_project` - (Optional) The project in which the subnetwork belongs.
If it is not provided, the provider project is used. If the `subnetwork` is a self_link, this field is ignored in favor of the project
defined in the subnetwork self_link. If the `subnetwork` is a name and this
field is not provided, the provider project is used.
* `address` - (Optional) The private IP address to assign to the instance. If * `address` - (Optional) The private IP address to assign to the instance. If
empty, the address will be automatically assigned. empty, the address will be automatically assigned.