Add subnetwork secondary ip ranges beta feature (#310)

This commit is contained in:
Vincent Roseberry 2017-08-09 15:02:54 -07:00 committed by GitHub
parent f49e29411c
commit 52daf4097a
6 changed files with 270 additions and 30 deletions

View File

@ -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)
}

View File

@ -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.")
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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))

View File

@ -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