diff --git a/google/compute_operation.go b/google/compute_operation.go index bce57bb6..459ba336 100644 --- a/google/compute_operation.go +++ b/google/compute_operation.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" + computeBeta "google.golang.org/api/compute/v0.beta" "google.golang.org/api/compute/v1" ) @@ -91,3 +92,13 @@ func computeOperationWaitTime(config *Config, op *compute.Operation, project, ac return nil } + +func computeBetaOperationWaitTime(config *Config, op *computeBeta.Operation, project, activity string, timeoutMin int) error { + opV1 := &compute.Operation{} + err := Convert(op, opV1) + if err != nil { + return err + } + + return computeOperationWaitTime(config, opV1, project, activity, 4) +} diff --git a/google/compute_shared_operation.go b/google/compute_shared_operation.go index 775e6680..9be0e110 100644 --- a/google/compute_shared_operation.go +++ b/google/compute_shared_operation.go @@ -18,7 +18,7 @@ func computeSharedOperationWaitTime(config *Config, op interface{}, project stri case *compute.Operation: return computeOperationWaitTime(config, op.(*compute.Operation), project, activity, minutes) case *computeBeta.Operation: - return computeBetaOperationWaitGlobalTime(config, op.(*computeBeta.Operation), project, activity, minutes) + return computeBetaOperationWaitTime(config, op.(*computeBeta.Operation), project, activity, minutes) default: panic("Attempted to wait on an Operation of unknown type.") } diff --git a/google/resource_compute_subnetwork.go b/google/resource_compute_subnetwork.go index c078168c..b4fcbbce 100644 --- a/google/resource_compute_subnetwork.go +++ b/google/resource_compute_subnetwork.go @@ -7,9 +7,18 @@ import ( "strings" "github.com/hashicorp/terraform/helper/schema" + computeBeta "google.golang.org/api/compute/v0.beta" "google.golang.org/api/compute/v1" ) +var SubnetworkBaseApiVersion = v1 +var SubnetworkVersionedFeatures = []Feature{ + { + Version: v0beta, + Item: "secondary_ip_range", + }, +} + func resourceComputeSubnetwork() *schema.Resource { return &schema.Resource{ Create: resourceComputeSubnetworkCreate, @@ -37,7 +46,7 @@ func resourceComputeSubnetwork() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - DiffSuppressFunc: linkDiffSuppress, + DiffSuppressFunc: compareGlobalSelfLinkOrResourceName, }, "description": &schema.Schema{ @@ -68,6 +77,27 @@ func resourceComputeSubnetwork() *schema.Resource { Optional: true, }, + "secondary_ip_range": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "range_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateGCPName, + }, + "ip_cidr_range": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + }, + "self_link": &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -76,18 +106,8 @@ func resourceComputeSubnetwork() *schema.Resource { } } -func createSubnetID(s *compute.Subnetwork) string { - return fmt.Sprintf("%s/%s", s.Region, s.Name) -} - -func splitSubnetID(id string) (region string, name string) { - parts := strings.Split(id, "/") - region = parts[0] - name = parts[1] - return -} - func resourceComputeSubnetworkCreate(d *schema.ResourceData, meta interface{}) error { + computeApiVersion := getComputeApiVersion(d, SubnetworkBaseApiVersion, SubnetworkVersionedFeatures) config := meta.(*Config) region, err := getRegion(d, config) @@ -106,17 +126,32 @@ func resourceComputeSubnetworkCreate(d *schema.ResourceData, meta interface{}) e } // Build the subnetwork parameters - subnetwork := &compute.Subnetwork{ + subnetwork := &computeBeta.Subnetwork{ Name: d.Get("name").(string), Description: d.Get("description").(string), IpCidrRange: d.Get("ip_cidr_range").(string), PrivateIpGoogleAccess: d.Get("private_ip_google_access").(bool), + SecondaryIpRanges: expandSecondaryRanges(d.Get("secondary_ip_range").([]interface{})), Network: network, } log.Printf("[DEBUG] Subnetwork insert request: %#v", subnetwork) - op, err := config.clientCompute.Subnetworks.Insert( - project, region, subnetwork).Do() + + var op interface{} + switch computeApiVersion { + case v1: + subnetworkV1 := &compute.Subnetwork{} + err := Convert(subnetwork, subnetworkV1) + if err != nil { + return err + } + + op, err = config.clientCompute.Subnetworks.Insert( + project, region, subnetworkV1).Do() + case v0beta: + op, err = config.clientComputeBeta.Subnetworks.Insert( + project, region, subnetwork).Do() + } if err != nil { return fmt.Errorf("Error creating subnetwork: %s", err) @@ -128,9 +163,9 @@ func resourceComputeSubnetworkCreate(d *schema.ResourceData, meta interface{}) e // The same name can appear twice in a project, as long as each one is in a different region." // https://cloud.google.com/compute/docs/subnetworks subnetwork.Region = region - d.SetId(createSubnetID(subnetwork)) + d.SetId(createBetaSubnetID(subnetwork)) - err = computeOperationWait(config, op, project, "Creating Subnetwork") + err = computeSharedOperationWait(config, op, project, "Creating Subnetwork") if err != nil { return err } @@ -139,6 +174,7 @@ func resourceComputeSubnetworkCreate(d *schema.ResourceData, meta interface{}) e } func resourceComputeSubnetworkRead(d *schema.ResourceData, meta interface{}) error { + computeApiVersion := getComputeApiVersion(d, SubnetworkBaseApiVersion, SubnetworkVersionedFeatures) config := meta.(*Config) region, err := getRegion(d, config) @@ -153,10 +189,25 @@ func resourceComputeSubnetworkRead(d *schema.ResourceData, meta interface{}) err name := d.Get("name").(string) - subnetwork, err := config.clientCompute.Subnetworks.Get( - project, region, name).Do() - if err != nil { - return handleNotFoundError(err, d, fmt.Sprintf("Subnetwork %q", name)) + subnetwork := &computeBeta.Subnetwork{} + switch computeApiVersion { + case v1: + subnetworkV1, err := config.clientCompute.Subnetworks.Get( + project, region, name).Do() + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("Subnetwork %q", name)) + } + + err = Convert(subnetworkV1, subnetwork) + if err != nil { + return err + } + case v0beta: + var err error + subnetwork, err = config.clientComputeBeta.Subnetworks.Get(project, region, name).Do() + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("Subnetwork %q", name)) + } } d.Set("name", subnetwork.Name) @@ -165,12 +216,14 @@ func resourceComputeSubnetworkRead(d *schema.ResourceData, meta interface{}) err d.Set("description", subnetwork.Description) d.Set("private_ip_google_access", subnetwork.PrivateIpGoogleAccess) d.Set("gateway_address", subnetwork.GatewayAddress) - d.Set("self_link", subnetwork.SelfLink) + d.Set("secondary_ip_range", flattenSecondaryRanges(subnetwork.SecondaryIpRanges)) + d.Set("self_link", ConvertSelfLinkToV1(subnetwork.SelfLink)) return nil } func resourceComputeSubnetworkUpdate(d *schema.ResourceData, meta interface{}) error { + computeApiVersion := getComputeApiVersionUpdate(d, SubnetworkBaseApiVersion, SubnetworkVersionedFeatures, []Feature{}) config := meta.(*Config) region, err := getRegion(d, config) @@ -186,18 +239,34 @@ func resourceComputeSubnetworkUpdate(d *schema.ResourceData, meta interface{}) e d.Partial(true) if d.HasChange("private_ip_google_access") { - subnetworksSetPrivateIpGoogleAccessRequest := &compute.SubnetworksSetPrivateIpGoogleAccessRequest{ + subnetworksSetPrivateIpGoogleAccessRequest := &computeBeta.SubnetworksSetPrivateIpGoogleAccessRequest{ PrivateIpGoogleAccess: d.Get("private_ip_google_access").(bool), } log.Printf("[DEBUG] Updating Subnetwork PrivateIpGoogleAccess %q: %#v", d.Id(), subnetworksSetPrivateIpGoogleAccessRequest) - op, err := config.clientCompute.Subnetworks.SetPrivateIpGoogleAccess( - project, region, d.Get("name").(string), subnetworksSetPrivateIpGoogleAccessRequest).Do() + + var op interface{} + switch computeApiVersion { + case v1: + subnetworksSetPrivateIpGoogleAccessRequestV1 := &compute.SubnetworksSetPrivateIpGoogleAccessRequest{} + err := Convert(subnetworksSetPrivateIpGoogleAccessRequest, subnetworksSetPrivateIpGoogleAccessRequestV1) + if err != nil { + return err + } + + op, err = config.clientCompute.Subnetworks.SetPrivateIpGoogleAccess( + project, region, d.Get("name").(string), subnetworksSetPrivateIpGoogleAccessRequestV1).Do() + case v0beta: + op, err = config.clientComputeBeta.Subnetworks.SetPrivateIpGoogleAccess( + project, region, d.Get("name").(string), subnetworksSetPrivateIpGoogleAccessRequest).Do() + + } + if err != nil { return fmt.Errorf("Error updating subnetwork PrivateIpGoogleAccess: %s", err) } - err = computeOperationWait(config, op, project, "Updating Subnetwork PrivateIpGoogleAccess") + err = computeSharedOperationWait(config, op, project, "Updating Subnetwork PrivateIpGoogleAccess") if err != nil { return err } @@ -211,6 +280,7 @@ func resourceComputeSubnetworkUpdate(d *schema.ResourceData, meta interface{}) e } func resourceComputeSubnetworkDelete(d *schema.ResourceData, meta interface{}) error { + computeApiVersion := getComputeApiVersion(d, SubnetworkBaseApiVersion, SubnetworkVersionedFeatures) config := meta.(*Config) region, err := getRegion(d, config) @@ -224,13 +294,20 @@ func resourceComputeSubnetworkDelete(d *schema.ResourceData, meta interface{}) e } // Delete the subnetwork - op, err := config.clientCompute.Subnetworks.Delete( - project, region, d.Get("name").(string)).Do() + var op interface{} + switch computeApiVersion { + case v1: + op, err = config.clientCompute.Subnetworks.Delete( + project, region, d.Get("name").(string)).Do() + case v0beta: + op, err = config.clientComputeBeta.Subnetworks.Delete( + project, region, d.Get("name").(string)).Do() + } if err != nil { return fmt.Errorf("Error deleting subnetwork: %s", err) } - err = computeOperationWait(config, op, project, "Deleting Subnetwork") + err = computeSharedOperationWait(config, op, project, "Deleting Subnetwork") if err != nil { return err } @@ -256,3 +333,45 @@ func resourceComputeSubnetworkImportState(d *schema.ResourceData, meta interface return []*schema.ResourceData{d}, nil } + +func createBetaSubnetID(s *computeBeta.Subnetwork) string { + return fmt.Sprintf("%s/%s", s.Region, s.Name) +} + +func createSubnetID(s *compute.Subnetwork) string { + return fmt.Sprintf("%s/%s", s.Region, s.Name) +} + +func splitSubnetID(id string) (region string, name string) { + parts := strings.Split(id, "/") + region = parts[0] + name = parts[1] + return +} + +func expandSecondaryRanges(configured []interface{}) []*computeBeta.SubnetworkSecondaryRange { + secondaryRanges := make([]*computeBeta.SubnetworkSecondaryRange, 0, len(configured)) + for _, raw := range configured { + data := raw.(map[string]interface{}) + secondaryRange := computeBeta.SubnetworkSecondaryRange{ + RangeName: data["range_name"].(string), + IpCidrRange: data["ip_cidr_range"].(string), + } + + secondaryRanges = append(secondaryRanges, &secondaryRange) + } + return secondaryRanges +} + +func flattenSecondaryRanges(secondaryRanges []*computeBeta.SubnetworkSecondaryRange) []map[string]interface{} { + secondaryRangesSchema := make([]map[string]interface{}, 0, len(secondaryRanges)) + for _, secondaryRange := range secondaryRanges { + data := map[string]interface{}{ + "range_name": secondaryRange.RangeName, + "ip_cidr_range": secondaryRange.IpCidrRange, + } + + secondaryRangesSchema = append(secondaryRangesSchema, data) + } + return secondaryRangesSchema +} diff --git a/google/resource_compute_subnetwork_test.go b/google/resource_compute_subnetwork_test.go index 7ad178a4..d6c67dd8 100644 --- a/google/resource_compute_subnetwork_test.go +++ b/google/resource_compute_subnetwork_test.go @@ -8,6 +8,8 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" "google.golang.org/api/compute/v1" + + computeBeta "google.golang.org/api/compute/v0.beta" ) func TestAccComputeSubnetwork_basic(t *testing.T) { @@ -70,6 +72,28 @@ func TestAccComputeSubnetwork_update(t *testing.T) { } } +func TestAccComputeSubnetwork_secondaryIpRanges(t *testing.T) { + var subnetwork computeBeta.Subnetwork + + cnName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + subnetworkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSubnetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSubnetwork_secondaryIpRanges(cnName, subnetworkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBetaSubnetworkExists("google_compute_subnetwork.network-with-private-secondary-ip-range", &subnetwork), + testAccCheckComputeSubnetworkHasSecondaryIpRange(&subnetwork, "tf-test-secondary-range", "192.168.1.0/24"), + ), + }, + }, + }) +} + func testAccCheckComputeSubnetworkDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) @@ -119,6 +143,51 @@ func testAccCheckComputeSubnetworkExists(n string, subnetwork *compute.Subnetwor } } +func testAccCheckComputeBetaSubnetworkExists(n string, subnetwork *computeBeta.Subnetwork) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + + region, subnet_name := splitSubnetID(rs.Primary.ID) + found, err := config.clientComputeBeta.Subnetworks.Get( + config.Project, region, subnet_name).Do() + if err != nil { + return err + } + + if found.Name != subnet_name { + return fmt.Errorf("Subnetwork not found") + } + + *subnetwork = *found + + return nil + } +} + +func testAccCheckComputeSubnetworkHasSecondaryIpRange(subnetwork *computeBeta.Subnetwork, rangeName, ipCidrRange string) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, secondaryRange := range subnetwork.SecondaryIpRanges { + if secondaryRange.RangeName == rangeName { + if secondaryRange.IpCidrRange == ipCidrRange { + return nil + } + return fmt.Errorf("Secondary range %s has the wrong ip_cidr_range. Expected %s, got %s", rangeName, ipCidrRange, secondaryRange.IpCidrRange) + } + } + + return fmt.Errorf("Secondary range %s not found", rangeName) + } +} + func testAccComputeSubnetwork_basic(cnName, subnetwork1Name, subnetwork2Name, subnetwork3Name string) string { return fmt.Sprintf(` resource "google_compute_network" "custom-test" { @@ -183,3 +252,23 @@ resource "google_compute_subnetwork" "network-with-private-google-access" { } `, cnName, subnetworkName) } + +func testAccComputeSubnetwork_secondaryIpRanges(cnName, subnetworkName string) string { + return fmt.Sprintf(` +resource "google_compute_network" "custom-test" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "network-with-private-secondary-ip-range" { + name = "%s" + ip_cidr_range = "10.2.0.0/16" + region = "us-central1" + network = "${google_compute_network.custom-test.self_link}" + secondary_ip_range { + range_name = "tf-test-secondary-range" + ip_cidr_range = "192.168.1.0/24" + } +} +`, cnName, subnetworkName) +} diff --git a/google/self_link_helpers.go b/google/self_link_helpers.go index bda74d4b..75e246f6 100644 --- a/google/self_link_helpers.go +++ b/google/self_link_helpers.go @@ -28,6 +28,17 @@ func compareSelfLinkRelativePaths(k, old, new string, d *schema.ResourceData) bo return false } +// Use this method when the field accepts either a name or a self_link referencing a global resource. +func compareGlobalSelfLinkOrResourceName(k, old, new string, d *schema.ResourceData) bool { + oldParts := strings.Split(old, "/") + newParts := strings.Split(new, "/") + + if oldParts[len(oldParts)-1] == newParts[len(newParts)-1] { + return true + } + return false +} + // Hash the relative path of a self link. func selfLinkRelativePathHash(selfLink interface{}) int { path, _ := getRelativePath(selfLink.(string)) diff --git a/website/docs/r/compute_subnetwork.html.markdown b/website/docs/r/compute_subnetwork.html.markdown index 55eed2b5..b0fd3aae 100644 --- a/website/docs/r/compute_subnetwork.html.markdown +++ b/website/docs/r/compute_subnetwork.html.markdown @@ -56,6 +56,16 @@ The following arguments are supported: can access Google services without assigned external IP addresses. +- - - + +* `secondary_ip_range` - (Optional, Beta) An array of configurations for secondary IP ranges for VM instances contained in this subnetwork. Structure is documented below. + +The `secondary_ip_range` block supports: + +* `range_name` - (Required) The name associated with this subnetwork secondary range, used when adding an alias IP range to a VM instance. + +* `ip_cidr_range` - (Required) The range of IP addresses belonging to this subnetwork secondary range. Ranges must be unique and non-overlapping with all primary and secondary IP ranges within a network. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are