diff --git a/provider.go b/provider.go index 2c295010..c7d345b8 100644 --- a/provider.go +++ b/provider.go @@ -59,6 +59,7 @@ func Provider() terraform.ResourceProvider { "google_compute_project_metadata": resourceComputeProjectMetadata(), "google_compute_route": resourceComputeRoute(), "google_compute_ssl_certificate": resourceComputeSslCertificate(), + "google_compute_subnetwork": resourceComputeSubnetwork(), "google_compute_target_http_proxy": resourceComputeTargetHttpProxy(), "google_compute_target_https_proxy": resourceComputeTargetHttpsProxy(), "google_compute_target_pool": resourceComputeTargetPool(), diff --git a/resource_compute_network.go b/resource_compute_network.go index a3c72aa1..3f853636 100644 --- a/resource_compute_network.go +++ b/resource_compute_network.go @@ -14,6 +14,7 @@ func resourceComputeNetwork() *schema.Resource { Create: resourceComputeNetworkCreate, Read: resourceComputeNetworkRead, Delete: resourceComputeNetworkDelete, + Update: resourceComputeNetworkUpdate, Schema: map[string]*schema.Schema{ "name": &schema.Schema{ @@ -24,7 +25,7 @@ func resourceComputeNetwork() *schema.Resource { "ipv4_range": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, }, @@ -33,6 +34,17 @@ func resourceComputeNetwork() *schema.Resource { Computed: true, }, + "auto_create_subnetworks": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, // copy behaviour of Google Cloud GUI and gcloud tool + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "self_link": &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -44,11 +56,36 @@ func resourceComputeNetwork() *schema.Resource { func resourceComputeNetworkCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) + // + // Possible modes: + // -1- Legacy mode - Create a network in the legacy mode. ipv4_range is set. auto_create_subnetworks must be false + // and not sent in reqest, and subnetworks empty + // -2- Distributed Mode - Create a new generation network that supports subnetworks: + // 2.a - Auto subnet mode - auto_create_subnetworks = true, Google will generate 1 subnetwork per region + // 2.b - Custom subnet mode - auto_create_subnetworks = false & ipv4_range not set, + // + ipv4range := d.Get("ipv4_range").(string) + autoCreateSubnetworks := d.Get("auto_create_subnetworks").(bool) + + if ipv4range != "" && autoCreateSubnetworks { + return fmt.Errorf("Error: cannot define ipv4_range with auto_create_subnetworks = true.") + } + // Build the network parameter network := &compute.Network{ - Name: d.Get("name").(string), - IPv4Range: d.Get("ipv4_range").(string), + Name: d.Get("name").(string), + AutoCreateSubnetworks: autoCreateSubnetworks, + Description: d.Get("description").(string), } + + if v, ok := d.GetOk("ipv4_range"); ok { + log.Printf("[DEBUG] Setting IPv4Range (%#V) for legacy network mode", v.(string)) + network.IPv4Range = v.(string) + } else { + // custom subnet mode, so make sure AutoCreateSubnetworks field is included in request + network.ForceSendFields = []string{"AutoCreateSubnetworks"} + } + log.Printf("[DEBUG] Network insert request: %#v", network) op, err := config.clientCompute.Networks.Insert( config.Project, network).Do() @@ -64,7 +101,7 @@ func resourceComputeNetworkCreate(d *schema.ResourceData, meta interface{}) erro return err } - return resourceComputeNetworkRead(d, meta) + return nil } func resourceComputeNetworkRead(d *schema.ResourceData, meta interface{}) error { @@ -86,6 +123,10 @@ func resourceComputeNetworkRead(d *schema.ResourceData, meta interface{}) error d.Set("gateway_ipv4", network.GatewayIPv4) d.Set("self_link", network.SelfLink) + for i, v := range network.Subnetworks { + prefix := fmt.Sprintf("subnetwork_links.%d", i) + d.Set(prefix, v) + } return nil } @@ -108,3 +149,8 @@ func resourceComputeNetworkDelete(d *schema.ResourceData, meta interface{}) erro d.SetId("") return nil } + +func resourceComputeNetworkUpdate(d *schema.ResourceData, meta interface{}) error { + //config := meta.(*Config) + return nil +} diff --git a/resource_compute_network_test.go b/resource_compute_network_test.go index 4337bf7f..a364c9c9 100644 --- a/resource_compute_network_test.go +++ b/resource_compute_network_test.go @@ -29,6 +29,46 @@ func TestAccComputeNetwork_basic(t *testing.T) { }) } +func TestAccComputeNetwork_auto_subnet(t *testing.T) { + var network compute.Network + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeNetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeNetwork_auto_subnet, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeNetworkExists( + "google_compute_network.bar", &network), + ), + }, + }, + }) +} + +func TestAccComputeNetwork_custom_subnet(t *testing.T) { + var network compute.Network + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeNetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeNetwork_custom_subnet, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeNetworkExists( + "google_compute_network.baz", &network), + testAccCheckComputeNetworkIsCustomSubnet( + "google_compute_network.baz", &network), + ), + }, + }, + }) +} + func testAccCheckComputeNetworkDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) @@ -76,8 +116,42 @@ func testAccCheckComputeNetworkExists(n string, network *compute.Network) resour } } +func testAccCheckComputeNetworkIsCustomSubnet(n string, network *compute.Network) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + found, err := config.clientCompute.Networks.Get( + config.Project, network.Name).Do() + if err != nil { + return err + } + + if found.AutoCreateSubnetworks { + return fmt.Errorf("should have AutoCreateSubnetworks = false") + } + + if found.IPv4Range != "" { + return fmt.Errorf("should not have IPv4Range") + } + + return nil + } +} + var testAccComputeNetwork_basic = fmt.Sprintf(` resource "google_compute_network" "foobar" { name = "network-test-%s" ipv4_range = "10.0.0.0/16" }`, acctest.RandString(10)) + +var testAccComputeNetwork_auto_subnet = fmt.Sprintf(` +resource "google_compute_network" "bar" { + name = "network-test-%s" + auto_create_subnetworks = true +}`, acctest.RandString(10)) + +var testAccComputeNetwork_custom_subnet = fmt.Sprintf(` +resource "google_compute_network" "baz" { + name = "network-test-%s" + auto_create_subnetworks = false +}`, acctest.RandString(10)) diff --git a/resource_compute_subnetwork.go b/resource_compute_subnetwork.go new file mode 100644 index 00000000..23905cd5 --- /dev/null +++ b/resource_compute_subnetwork.go @@ -0,0 +1,142 @@ +package google + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" +) + +func resourceComputeSubnetwork() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeSubnetworkCreate, + Read: resourceComputeSubnetworkRead, + Delete: resourceComputeSubnetworkDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "network": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "ipCidrRange": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "gateway_address": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "self_link": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func createSubnetID(s *compute.Subnetwork) string { + return fmt.Sprintf("%s/%s", s.Region, s.Name) +} + +func resourceComputeSubnetworkCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + // Build the subnetwork parameters + subnetwork := &compute.Subnetwork{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + IpCidrRange: d.Get("ipCidrRange").(string), + Network: d.Get("network").(string), + } + region := d.Get("region").(string) + + log.Printf("[DEBUG] Subnetwork insert request: %#v", subnetwork) + op, err := config.clientCompute.Subnetworks.Insert( + config.Project, region, subnetwork).Do() + + if err != nil { + return fmt.Errorf("Error creating subnetwork: %s", err) + } + + // It probably maybe worked, so store the ID now + // Subnetwork name is not guaranteed to be unique in a project, but must be unique within a region + subnetwork.Region = region + d.SetId(createSubnetID(subnetwork)) + + err = computeOperationWaitRegion(config, op, region, "Creating Subnetwork") + if err != nil { + return err + } + + return resourceComputeSubnetworkRead(d, meta) +} + +func resourceComputeSubnetworkRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + name := d.Get("name").(string) + region := d.Get("region").(string) + + subnetwork, err := config.clientCompute.Subnetworks.Get( + config.Project, region, name).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { + log.Printf("[WARN] Removing Subnetwork %q because it's gone", name) + // The resource doesn't exist anymore + d.SetId("") + + return nil + } + + return fmt.Errorf("Error reading subnetwork: %s", err) + } + + d.Set("gateway_address", subnetwork.GatewayAddress) + d.Set("self_link", subnetwork.SelfLink) + + return nil +} + +func resourceComputeSubnetworkDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + region := d.Get("region").(string) + + // Delete the network + op, err := config.clientCompute.Subnetworks.Delete( + config.Project, region, d.Get("name").(string)).Do() + if err != nil { + return fmt.Errorf("Error deleting network: %s", err) + } + + err = computeOperationWaitRegion(config, op, region, "Deleting Network") + if err != nil { + return err + } + + d.SetId("") + return nil +}