diff --git a/builtin/providers/google/resource_compute_instance.go b/builtin/providers/google/resource_compute_instance.go index 28578736d3..4c46321256 100644 --- a/builtin/providers/google/resource_compute_instance.go +++ b/builtin/providers/google/resource_compute_instance.go @@ -684,6 +684,7 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error "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)), "access_config": accessConfigs, }) } diff --git a/builtin/providers/google/resource_compute_instance_template.go b/builtin/providers/google/resource_compute_instance_template.go index f7a0ce8bf1..b0e26d0f97 100644 --- a/builtin/providers/google/resource_compute_instance_template.go +++ b/builtin/providers/google/resource_compute_instance_template.go @@ -137,7 +137,7 @@ func resourceComputeInstanceTemplate() *schema.Resource { Schema: map[string]*schema.Schema{ "network": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, }, @@ -179,6 +179,12 @@ func resourceComputeInstanceTemplate() *schema.Resource { Deprecated: "Please use `scheduling.on_host_maintenance` instead", }, + "region": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "scheduling": &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -333,21 +339,57 @@ func buildDisks(d *schema.ResourceData, meta interface{}) ([]*compute.AttachedDi func buildNetworks(d *schema.ResourceData, meta interface{}) (error, []*compute.NetworkInterface) { // Build up the list of networks + config := meta.(*Config) + networksCount := d.Get("network_interface.#").(int) networkInterfaces := make([]*compute.NetworkInterface, 0, networksCount) for i := 0; i < networksCount; i++ { prefix := fmt.Sprintf("network_interface.%d", i) - source := "global/networks/" + var networkName, subnetworkName string if v, ok := d.GetOk(prefix + ".network"); ok { - source += v.(string) + networkName = v.(string) + } + if v, ok := d.GetOk(prefix + ".subnetwork"); ok { + subnetworkName = v.(string) } - subnetworkLink := d.Get("subnetwork").(string) + if networkName == "" && subnetworkName == "" { + return fmt.Errorf("network or subnetwork must be provided"), nil + } + if networkName != "" && subnetworkName != "" { + return fmt.Errorf("network or subnetwork must not both be provided"), nil + } + + var networkLink, subnetworkLink string + if networkName != "" { + network, err := config.clientCompute.Networks.Get( + config.Project, networkName).Do() + if err != nil { + return fmt.Errorf( + "Error referencing network '%s': %s", + networkName, err), nil + } + networkLink = network.SelfLink + } else { + // lookup subnetwork link using region and subnetwork name + region := d.Get("region").(string) + if region == "" { + region = config.Region + } + subnetwork, err := config.clientCompute.Subnetworks.Get( + config.Project, region, subnetworkName).Do() + if err != nil { + return fmt.Errorf( + "Error referencing subnetwork '%s' in region '%s': %s", + subnetworkName, region, err), nil + } + subnetworkLink = subnetwork.SelfLink + } // Build the networkInterface var iface compute.NetworkInterface - iface.Network = source + iface.Network = networkLink iface.Subnetwork = subnetworkLink accessConfigsCount := d.Get(prefix + ".access_config.#").(int) diff --git a/builtin/providers/google/resource_compute_instance_template_test.go b/builtin/providers/google/resource_compute_instance_template_test.go index a36987b2ca..91c531f650 100644 --- a/builtin/providers/google/resource_compute_instance_template_test.go +++ b/builtin/providers/google/resource_compute_instance_template_test.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" "google.golang.org/api/compute/v1" + "strings" ) func TestAccComputeInstanceTemplate_basic(t *testing.T) { @@ -73,6 +74,47 @@ func TestAccComputeInstanceTemplate_disks(t *testing.T) { }) } +func TestAccComputeInstanceTemplate_subnet_auto(t *testing.T) { + var instanceTemplate compute.InstanceTemplate + network := "network-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_subnet_auto(network), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists( + "google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateNetworkName(&instanceTemplate, network), + ), + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_subnet_custom(t *testing.T) { + var instanceTemplate compute.InstanceTemplate + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_subnet_custom, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists( + "google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateSubnetwork(&instanceTemplate), + ), + }, + }, + }) +} + func testAccCheckComputeInstanceTemplateDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) @@ -158,6 +200,18 @@ func testAccCheckComputeInstanceTemplateNetwork(instanceTemplate *compute.Instan } } +func testAccCheckComputeInstanceTemplateNetworkName(instanceTemplate *compute.InstanceTemplate, network string) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, i := range instanceTemplate.Properties.NetworkInterfaces { + if !strings.Contains(i.Network, network) { + return fmt.Errorf("Network doesn't match expected value, Expected: %s Actual: %s", network, i.Network[strings.LastIndex("/", i.Network)+1:]) + } + } + + return nil + } +} + func testAccCheckComputeInstanceTemplateDisk(instanceTemplate *compute.InstanceTemplate, source string, delete bool, boot bool) resource.TestCheckFunc { return func(s *terraform.State) error { if instanceTemplate.Properties.Disks == nil { @@ -186,6 +240,18 @@ func testAccCheckComputeInstanceTemplateDisk(instanceTemplate *compute.InstanceT } } +func testAccCheckComputeInstanceTemplateSubnetwork(instanceTemplate *compute.InstanceTemplate) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, i := range instanceTemplate.Properties.NetworkInterfaces { + if i.Subnetwork == "" { + return fmt.Errorf("no subnet") + } + } + + return nil + } +} + func testAccCheckComputeInstanceTemplateTag(instanceTemplate *compute.InstanceTemplate, n string) resource.TestCheckFunc { return func(s *terraform.State) error { if instanceTemplate.Properties.Tags == nil { @@ -293,3 +359,65 @@ resource "google_compute_instance_template" "foobar" { foo = "bar" } }`, acctest.RandString(10), acctest.RandString(10)) + +func testAccComputeInstanceTemplate_subnet_auto(network string) string { + return fmt.Sprintf(` + resource "google_compute_network" "auto-network" { + name = "%s" + auto_create_subnetworks = true + } + + resource "google_compute_instance_template" "foobar" { + name = "instance-tpl-%s" + machine_type = "n1-standard-1" + + disk { + source_image = "debian-7-wheezy-v20160211" + auto_delete = true + disk_size_gb = 10 + boot = true + } + + network_interface { + network = "${google_compute_network.auto-network.name}" + } + + metadata { + foo = "bar" + } + }`, network, acctest.RandString(10)) +} + +var testAccComputeInstanceTemplate_subnet_custom = fmt.Sprintf(` +resource "google_compute_network" "network" { + name = "network-%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnetwork" { + name = "subnetwork-%s" + ip_cidr_range = "10.0.0.0/24" + region = "us-central1" + network = "${google_compute_network.network.self_link}" +} + +resource "google_compute_instance_template" "foobar" { + name = "instance-test-%s" + machine_type = "n1-standard-1" + region = "us-central1" + + disk { + source_image = "debian-7-wheezy-v20160211" + auto_delete = true + disk_size_gb = 10 + boot = true + } + + network_interface { + subnetwork = "${google_compute_subnetwork.subnetwork.name}" + } + + metadata { + foo = "bar" + } +}`, acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)) diff --git a/builtin/providers/google/resource_compute_instance_test.go b/builtin/providers/google/resource_compute_instance_test.go index 9a2c3a7879..2ec4c56141 100644 --- a/builtin/providers/google/resource_compute_instance_test.go +++ b/builtin/providers/google/resource_compute_instance_test.go @@ -306,6 +306,48 @@ func TestAccComputeInstance_scheduling(t *testing.T) { }) } +func TestAccComputeInstance_subnet_auto(t *testing.T) { + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_subnet_auto(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasSubnet(&instance), + ), + }, + }, + }) +} + +func TestAccComputeInstance_subnet_custom(t *testing.T) { + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_subnet_custom(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasSubnet(&instance), + ), + }, + }, + }) +} + func testAccCheckComputeInstanceDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) @@ -451,6 +493,18 @@ func testAccCheckComputeInstanceServiceAccount(instance *compute.Instance, scope } } +func testAccCheckComputeInstanceHasSubnet(instance *compute.Instance) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, i := range instance.NetworkInterfaces { + if i.Subnetwork == "" { + return fmt.Errorf("no subnet") + } + } + + return nil + } +} + func testAccComputeInstance_basic_deprecated_network(instance string) string { return fmt.Sprintf(` resource "google_compute_instance" "foobar" { @@ -748,3 +802,58 @@ func testAccComputeInstance_scheduling(instance string) string { } }`, instance) } + +func testAccComputeInstance_subnet_auto(instance string) string { + return fmt.Sprintf(` + resource "google_compute_network" "inst-test-network" { + name = "inst-test-network-%s" + auto_create_subnetworks = true + } + + resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + disk { + image = "debian-7-wheezy-v20140814" + } + + network_interface { + network = "${google_compute_network.inst-test-network.name}" + access_config { } + } + + }`, acctest.RandString(10), instance) +} + +func testAccComputeInstance_subnet_custom(instance string) string { + return fmt.Sprintf(` + resource "google_compute_network" "inst-test-network" { + name = "inst-test-network-%s" + auto_create_subnetworks = false + } + + resource "google_compute_subnetwork" "inst-test-subnetwork" { + name = "inst-test-subnetwork-%s" + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" + network = "${google_compute_network.inst-test-network.self_link}" + } + + resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + disk { + image = "debian-7-wheezy-v20140814" + } + + network_interface { + subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.name}" + access_config { } + } + + }`, acctest.RandString(10), acctest.RandString(10), instance) +} diff --git a/website/source/docs/providers/google/r/compute_instance_template.html.markdown b/website/source/docs/providers/google/r/compute_instance_template.html.markdown index fad62f8a3c..9a51d03bc2 100644 --- a/website/source/docs/providers/google/r/compute_instance_template.html.markdown +++ b/website/source/docs/providers/google/r/compute_instance_template.html.markdown @@ -85,6 +85,12 @@ The following arguments are supported: * `network_interface` - (Required) Networks to attach to instances created from this template. This can be specified multiple times for multiple networks. Structure is documented below. + +* `region` - (Optional) An instance template is a global resource that is not bound to a zone + or a region. However, you can still specify some regional resources in an instance template, + which restricts the template to the region where that resource resides. For example, a + custom `subnetwork` resource is tied to a specific region. + Defaults to the region of the Provider if no value is given. * `automatic_restart` - (Optional, Deprecated - see `scheduling`) Specifies whether the instance should be @@ -137,11 +143,12 @@ The `disk` block supports: The `network_interface` block supports: -* `network` - (Optional) The name of the network to attach this interface to. Only use `network` - attribute for Legacy or Auto subnetted networks, otherwise use `subnetwork` instead. +* `network` - (Optional) The name of the network to attach this interface to. Use `network` + attribute for Legacy or Auto subnetted networks and `subnetwork` for custom subnetted + networks. * `subnetwork` - (Optional) the name of the subnetwork to attach this interface to. The subnetwork - must exist in the same region this instance will be created in. Either `network` + must exist in the same `region` this instance will be created in. Either `network` or `subnetwork` must be provided. * `access_config` - (Optional) Access configurations, i.e. IPs via which this instance can be