Skip to content

google_compute_instance can specified the subnetwork using a self_link #290

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
merged 2 commits into from
Aug 4, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
41 changes: 41 additions & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
computeBeta "google.golang.org/api/compute/v0.beta"
"google.golang.org/api/compute/v1"
"google.golang.org/api/googleapi"
"regexp"
)

// Global MutexKV
Expand Down Expand Up @@ -272,6 +273,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:
// - 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
Expand Down
46 changes: 26 additions & 20 deletions google/resource_compute_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform/helper/validation"
"google.golang.org/api/compute/v1"
"google.golang.org/api/googleapi"
"regexp"
)

func stringScopeHashcode(v interface{}) int {
Expand Down Expand Up @@ -278,20 +279,25 @@ func resourceComputeInstance() *schema.Resource {
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"network": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
DiffSuppressFunc: linkDiffSuppress,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While you're here, want to add a ConflictsWith for subnetwork? Then we can get rid of an if statement below

},

"subnetwork": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
DiffSuppressFunc: linkDiffSuppress,
},

"subnetwork_project": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},

Expand Down Expand Up @@ -705,7 +711,6 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
// Load up the name of this network_interface
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)
var networkLink, subnetworkLink string

Expand All @@ -720,18 +725,10 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
}

} else {
region := getRegionFromZone(d.Get("zone").(string))
if subnetworkProject == "" {
subnetworkProject = project
}
subnetwork, err := config.clientCompute.Subnetworks.Get(
subnetworkProject, region, subnetworkName).Do()
subnetworkLink, err = getSubnetworkLink(d, config, prefix+".subnetwork", prefix+".subnetwork_project", "zone")
if err != nil {
return fmt.Errorf(
"Error referencing subnetwork '%s' in region '%s': %s",
subnetworkName, region, err)
return err
}
subnetworkLink = subnetwork.SelfLink
}

// Build the networkInterface
Expand Down Expand Up @@ -961,9 +958,9 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
networkInterfaces = append(networkInterfaces, map[string]interface{}{
"name": iface.Name,
"address": iface.NetworkIP,
"network": d.Get(fmt.Sprintf("network_interface.%d.network", i)),
"subnetwork": d.Get(fmt.Sprintf("network_interface.%d.subnetwork", i)),
"subnetwork_project": d.Get(fmt.Sprintf("network_interface.%d.subnetwork_project", i)),
"network": iface.Network,
"subnetwork": iface.Subnetwork,
"subnetwork_project": getProjectFromSubnetworkLink(iface.Subnetwork),
"access_config": accessConfigs,
})
}
Expand Down Expand Up @@ -1452,3 +1449,12 @@ func flattenScratchDisk(disk *compute.AttachedDisk) map[string]interface{} {
}
return result
}

func getProjectFromSubnetworkLink(subnetwork string) string {
r := regexp.MustCompile(SubnetworkLinkRegex)
if !r.MatchString(subnetwork) {
return ""
}

return r.FindStringSubmatch(subnetwork)[1]
}
2 changes: 1 addition & 1 deletion google/resource_compute_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1609,7 +1609,7 @@ resource "google_compute_instance" "foobar" {
}

network_interface {
subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.name}"
subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.self_link}"
access_config { }
}

Expand Down
10 changes: 8 additions & 2 deletions google/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ import (
"regexp"
)

// Copied from the official Google Cloud auto-generated client.
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])?))"
const (
// 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) {
re := `^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$`
Expand Down
8 changes: 5 additions & 3 deletions website/docs/r/compute_instance.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,14 @@ The `network_interface` block supports:
* `network` - (Optional) The name or self_link of the network to attach this interface to.
Either `network` or `subnetwork` must be provided.

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

* `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
empty, the address will be automatically assigned.
Expand Down