mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-10-01 16:21:06 +00:00
Add support for alias_ip_range in google_compute_instance network interface (#375)
This commit is contained in:
parent
dcacc292f2
commit
7ceea51dfd
@ -2,6 +2,8 @@ package google
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ComputeApiVersion uint8
|
type ComputeApiVersion uint8
|
||||||
@ -71,7 +73,16 @@ func getComputeApiVersionUpdate(d TerraformResourceData, resourceVersion Compute
|
|||||||
// A field of a resource and the version of the Compute API required to use it.
|
// A field of a resource and the version of the Compute API required to use it.
|
||||||
type Feature struct {
|
type Feature struct {
|
||||||
Version ComputeApiVersion
|
Version ComputeApiVersion
|
||||||
Item string
|
// Path to the beta field.
|
||||||
|
//
|
||||||
|
// The feature is considered to be in-use if the field referenced by "Item" is set in the state.
|
||||||
|
// The path can reference:
|
||||||
|
// - a beta field at the top-level (e.g. "min_cpu_platform").
|
||||||
|
// - a beta field nested inside a list (e.g. "network_interface.*.alias_ip_range" is considered to be
|
||||||
|
// in-use if the "alias_ip_range" field is set in the state for any of the network interfaces).
|
||||||
|
//
|
||||||
|
// Note: beta field nested inside a SET are NOT supported at the moment.
|
||||||
|
Item string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true when a feature has been modified.
|
// Returns true when a feature has been modified.
|
||||||
@ -84,8 +95,34 @@ func (s Feature) HasChangeBy(d TerraformResourceData) bool {
|
|||||||
|
|
||||||
// Return true when a feature appears in schema or has been modified.
|
// Return true when a feature appears in schema or has been modified.
|
||||||
func (s Feature) InUseBy(d TerraformResourceData) bool {
|
func (s Feature) InUseBy(d TerraformResourceData) bool {
|
||||||
_, ok := d.GetOk(s.Item)
|
return inUseBy(d, s.Item)
|
||||||
return ok || s.HasChangeBy(d)
|
}
|
||||||
|
|
||||||
|
func inUseBy(d TerraformResourceData, path string) bool {
|
||||||
|
pos := strings.Index(path, "*")
|
||||||
|
if pos == -1 {
|
||||||
|
_, ok := d.GetOk(path)
|
||||||
|
return ok || d.HasChange(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := path[0:pos]
|
||||||
|
suffix := path[pos+1:]
|
||||||
|
|
||||||
|
v, ok := d.GetOk(prefix + "#")
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
count := v.(int)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
nestedPath := fmt.Sprintf("%s%d%s", prefix, i, suffix)
|
||||||
|
if inUseBy(d, nestedPath) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func maxVersion(versionsInUse map[ComputeApiVersion]struct{}) ComputeApiVersion {
|
func maxVersion(versionsInUse map[ComputeApiVersion]struct{}) ComputeApiVersion {
|
||||||
|
@ -4,7 +4,9 @@ import "testing"
|
|||||||
|
|
||||||
func TestResourceWithOnlyBaseVersionFields(t *testing.T) {
|
func TestResourceWithOnlyBaseVersionFields(t *testing.T) {
|
||||||
d := &ResourceDataMock{
|
d := &ResourceDataMock{
|
||||||
FieldsInSchema: []string{"normal_field"},
|
FieldsInSchema: map[string]interface{}{
|
||||||
|
"normal_field": "foo",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceVersion := v1
|
resourceVersion := v1
|
||||||
@ -22,7 +24,10 @@ func TestResourceWithOnlyBaseVersionFields(t *testing.T) {
|
|||||||
func TestResourceWithBetaFields(t *testing.T) {
|
func TestResourceWithBetaFields(t *testing.T) {
|
||||||
resourceVersion := v1
|
resourceVersion := v1
|
||||||
d := &ResourceDataMock{
|
d := &ResourceDataMock{
|
||||||
FieldsInSchema: []string{"normal_field", "beta_field"},
|
FieldsInSchema: map[string]interface{}{
|
||||||
|
"normal_field": "foo",
|
||||||
|
"beta_field": "bar",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedVersion := v0beta
|
expectedVersion := v0beta
|
||||||
@ -40,7 +45,9 @@ func TestResourceWithBetaFields(t *testing.T) {
|
|||||||
func TestResourceWithBetaFieldsNotInSchema(t *testing.T) {
|
func TestResourceWithBetaFieldsNotInSchema(t *testing.T) {
|
||||||
resourceVersion := v1
|
resourceVersion := v1
|
||||||
d := &ResourceDataMock{
|
d := &ResourceDataMock{
|
||||||
FieldsInSchema: []string{"normal_field"},
|
FieldsInSchema: map[string]interface{}{
|
||||||
|
"normal_field": "foo",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedVersion := v1
|
expectedVersion := v1
|
||||||
@ -58,7 +65,10 @@ func TestResourceWithBetaFieldsNotInSchema(t *testing.T) {
|
|||||||
func TestResourceWithBetaUpdateFields(t *testing.T) {
|
func TestResourceWithBetaUpdateFields(t *testing.T) {
|
||||||
resourceVersion := v1
|
resourceVersion := v1
|
||||||
d := &ResourceDataMock{
|
d := &ResourceDataMock{
|
||||||
FieldsInSchema: []string{"normal_field", "beta_update_field"},
|
FieldsInSchema: map[string]interface{}{
|
||||||
|
"normal_field": "foo",
|
||||||
|
"beta_field": "bar",
|
||||||
|
},
|
||||||
FieldsWithHasChange: []string{"beta_update_field"},
|
FieldsWithHasChange: []string{"beta_update_field"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,11 +83,76 @@ func TestResourceWithBetaUpdateFields(t *testing.T) {
|
|||||||
if computeApiVersion != expectedVersion {
|
if computeApiVersion != expectedVersion {
|
||||||
t.Errorf("Expected to see version: %v. Saw version: %v.", expectedVersion, computeApiVersion)
|
t.Errorf("Expected to see version: %v. Saw version: %v.", expectedVersion, computeApiVersion)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceWithOnlyBaseNestedFields(t *testing.T) {
|
||||||
|
resourceVersion := v1
|
||||||
|
d := &ResourceDataMock{
|
||||||
|
FieldsInSchema: map[string]interface{}{
|
||||||
|
"list_field.#": 2,
|
||||||
|
"list_field.0.normal_field": "foo",
|
||||||
|
"list_field.1.normal_field": "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
computeApiVersion := getComputeApiVersion(d, resourceVersion, []Feature{})
|
||||||
|
if computeApiVersion != resourceVersion {
|
||||||
|
t.Errorf("Expected to see version: %v. Saw version: %v.", resourceVersion, computeApiVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
computeApiVersion = getComputeApiVersionUpdate(d, resourceVersion, []Feature{}, []Feature{{Version: resourceVersion, Item: "list_field.*.beta_nested_field"}})
|
||||||
|
if computeApiVersion != resourceVersion {
|
||||||
|
t.Errorf("Expected to see version: %v. Saw version: %v.", resourceVersion, computeApiVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceWithBetaNestedFields(t *testing.T) {
|
||||||
|
resourceVersion := v1
|
||||||
|
d := &ResourceDataMock{
|
||||||
|
FieldsInSchema: map[string]interface{}{
|
||||||
|
"list_field.#": 2,
|
||||||
|
"list_field.0.normal_field": "foo",
|
||||||
|
"list_field.1.normal_field": "bar",
|
||||||
|
"list_field.1.beta_nested_field": "baz",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedVersion := v0beta
|
||||||
|
computeApiVersion := getComputeApiVersion(d, resourceVersion, []Feature{{Version: expectedVersion, Item: "list_field.*.beta_nested_field"}})
|
||||||
|
if computeApiVersion != expectedVersion {
|
||||||
|
t.Errorf("Expected to see version: %v. Saw version: %v.", expectedVersion, computeApiVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
computeApiVersion = getComputeApiVersionUpdate(d, resourceVersion, []Feature{{Version: expectedVersion, Item: "list_field.*.beta_nested_field"}}, []Feature{})
|
||||||
|
if computeApiVersion != expectedVersion {
|
||||||
|
t.Errorf("Expected to see version: %v. Saw version: %v.", expectedVersion, computeApiVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceWithBetaDoubleNestedFields(t *testing.T) {
|
||||||
|
resourceVersion := v1
|
||||||
|
d := &ResourceDataMock{
|
||||||
|
FieldsInSchema: map[string]interface{}{
|
||||||
|
"list_field.#": 1,
|
||||||
|
"list_field.0.nested_list_field.#": 1,
|
||||||
|
"list_field.0.nested_list_field.0.beta_nested_field": "foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedVersion := v0beta
|
||||||
|
computeApiVersion := getComputeApiVersion(d, resourceVersion, []Feature{{Version: expectedVersion, Item: "list_field.*.nested_list_field.*.beta_nested_field"}})
|
||||||
|
if computeApiVersion != expectedVersion {
|
||||||
|
t.Errorf("Expected to see version: %v. Saw version: %v.", expectedVersion, computeApiVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
computeApiVersion = getComputeApiVersionUpdate(d, resourceVersion, []Feature{{Version: expectedVersion, Item: "list_field.*.nested_list_field.*.beta_nested_field"}}, []Feature{})
|
||||||
|
if computeApiVersion != expectedVersion {
|
||||||
|
t.Errorf("Expected to see version: %v. Saw version: %v.", expectedVersion, computeApiVersion)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceDataMock struct {
|
type ResourceDataMock struct {
|
||||||
FieldsInSchema []string
|
FieldsInSchema map[string]interface{}
|
||||||
FieldsWithHasChange []string
|
FieldsWithHasChange []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,13 +168,11 @@ func (d *ResourceDataMock) HasChange(key string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *ResourceDataMock) GetOk(key string) (interface{}, bool) {
|
func (d *ResourceDataMock) GetOk(key string) (interface{}, bool) {
|
||||||
exists := false
|
for k, v := range d.FieldsInSchema {
|
||||||
for _, val := range d.FieldsInSchema {
|
if key == k {
|
||||||
if key == val {
|
return v, true
|
||||||
exists = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, exists
|
return nil, false
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,10 @@ var InstanceVersionedFeatures = []Feature{
|
|||||||
Version: v0beta,
|
Version: v0beta,
|
||||||
Item: "min_cpu_platform",
|
Item: "min_cpu_platform",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Version: v0beta,
|
||||||
|
Item: "network_interface.*.alias_ip_range",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringScopeHashcode(v interface{}) int {
|
func stringScopeHashcode(v interface{}) int {
|
||||||
@ -293,7 +297,7 @@ func resourceComputeInstance() *schema.Resource {
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
DiffSuppressFunc: linkDiffSuppress,
|
DiffSuppressFunc: compareSelfLinkOrResourceName,
|
||||||
},
|
},
|
||||||
|
|
||||||
"subnetwork": &schema.Schema{
|
"subnetwork": &schema.Schema{
|
||||||
@ -301,7 +305,7 @@ func resourceComputeInstance() *schema.Resource {
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
DiffSuppressFunc: linkDiffSuppress,
|
DiffSuppressFunc: compareSelfLinkOrResourceName,
|
||||||
},
|
},
|
||||||
|
|
||||||
"subnetwork_project": &schema.Schema{
|
"subnetwork_project": &schema.Schema{
|
||||||
@ -340,6 +344,27 @@ func resourceComputeInstance() *schema.Resource {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"alias_ip_range": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"ip_cidr_range": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DiffSuppressFunc: ipCidrRangeDiffSuppress,
|
||||||
|
},
|
||||||
|
"subnetwork_range_name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -796,6 +821,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
|
|||||||
iface.Network = networkLink
|
iface.Network = networkLink
|
||||||
iface.Subnetwork = subnetworkLink
|
iface.Subnetwork = subnetworkLink
|
||||||
iface.NetworkIP = address
|
iface.NetworkIP = address
|
||||||
|
iface.AliasIpRanges = expandAliasIpRanges(d.Get(prefix + ".alias_ip_range").([]interface{}))
|
||||||
|
|
||||||
// Handle access_config structs
|
// Handle access_config structs
|
||||||
accessConfigsCount := d.Get(prefix + ".access_config.#").(int)
|
accessConfigsCount := d.Get(prefix + ".access_config.#").(int)
|
||||||
@ -1038,6 +1064,7 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
|
|||||||
"subnetwork": iface.Subnetwork,
|
"subnetwork": iface.Subnetwork,
|
||||||
"subnetwork_project": getProjectFromSubnetworkLink(iface.Subnetwork),
|
"subnetwork_project": getProjectFromSubnetworkLink(iface.Subnetwork),
|
||||||
"access_config": accessConfigs,
|
"access_config": accessConfigs,
|
||||||
|
"alias_ip_range": flattenAliasIpRange(iface.AliasIpRanges),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1580,6 +1607,18 @@ func expandGuestAccelerators(zone string, configs []interface{}) []*computeBeta.
|
|||||||
return guestAccelerators
|
return guestAccelerators
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func expandAliasIpRanges(ranges []interface{}) []*computeBeta.AliasIpRange {
|
||||||
|
ipRanges := make([]*computeBeta.AliasIpRange, 0, len(ranges))
|
||||||
|
for _, raw := range ranges {
|
||||||
|
data := raw.(map[string]interface{})
|
||||||
|
ipRanges = append(ipRanges, &computeBeta.AliasIpRange{
|
||||||
|
IpCidrRange: data["ip_cidr_range"].(string),
|
||||||
|
SubnetworkRangeName: data["subnetwork_range_name"].(string),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return ipRanges
|
||||||
|
}
|
||||||
|
|
||||||
func flattenGuestAccelerators(zone string, accelerators []*computeBeta.AcceleratorConfig) []map[string]interface{} {
|
func flattenGuestAccelerators(zone string, accelerators []*computeBeta.AcceleratorConfig) []map[string]interface{} {
|
||||||
acceleratorsSchema := make([]map[string]interface{}, 0, len(accelerators))
|
acceleratorsSchema := make([]map[string]interface{}, 0, len(accelerators))
|
||||||
for _, accelerator := range accelerators {
|
for _, accelerator := range accelerators {
|
||||||
@ -1612,6 +1651,17 @@ func flattenBetaScheduling(scheduling *computeBeta.Scheduling) []map[string]inte
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func flattenAliasIpRange(ranges []*computeBeta.AliasIpRange) []map[string]interface{} {
|
||||||
|
rangesSchema := make([]map[string]interface{}, 0, len(ranges))
|
||||||
|
for _, ipRange := range ranges {
|
||||||
|
rangesSchema = append(rangesSchema, map[string]interface{}{
|
||||||
|
"ip_cidr_range": ipRange.IpCidrRange,
|
||||||
|
"subnetwork_range_name": ipRange.SubnetworkRangeName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return rangesSchema
|
||||||
|
}
|
||||||
|
|
||||||
func getProjectFromSubnetworkLink(subnetwork string) string {
|
func getProjectFromSubnetworkLink(subnetwork string) string {
|
||||||
r := regexp.MustCompile(SubnetworkLinkRegex)
|
r := regexp.MustCompile(SubnetworkLinkRegex)
|
||||||
if !r.MatchString(subnetwork) {
|
if !r.MatchString(subnetwork) {
|
||||||
|
@ -749,7 +749,46 @@ func TestAccComputeInstance_minCpuPlatform(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccComputeInstance_primaryAliasIpRange(t *testing.T) {
|
||||||
|
var instance computeBeta.Instance
|
||||||
|
instanceName := fmt.Sprintf("terraform-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_primaryAliasIpRange(instanceName),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeBetaInstanceExists("google_compute_instance.foobar", &instance),
|
||||||
|
testAccCheckComputeInstanceHasAliasIpRange(&instance, "", "/24"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccComputeInstance_secondaryAliasIpRange(t *testing.T) {
|
||||||
|
var instance computeBeta.Instance
|
||||||
|
instanceName := fmt.Sprintf("terraform-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_secondaryAliasIpRange(instanceName),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeBetaInstanceExists("google_compute_instance.foobar", &instance),
|
||||||
|
testAccCheckComputeInstanceHasAliasIpRange(&instance, "inst-test-secondary", "172.16.0.0/24"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckComputeInstanceUpdateMachineType(n string) resource.TestCheckFunc {
|
func testAccCheckComputeInstanceUpdateMachineType(n string) resource.TestCheckFunc {
|
||||||
@ -1186,6 +1225,20 @@ func testAccCheckComputeInstanceHasMinCpuPlatform(instance *computeBeta.Instance
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeInstanceHasAliasIpRange(instance *computeBeta.Instance, subnetworkRangeName, iPCidrRange string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
for _, networkInterface := range instance.NetworkInterfaces {
|
||||||
|
for _, aliasIpRange := range networkInterface.AliasIpRanges {
|
||||||
|
if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || ipCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Alias ip range with name %s and cidr %s not present", subnetworkRangeName, iPCidrRange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testAccComputeInstance_basic_deprecated_network(instance string) string {
|
func testAccComputeInstance_basic_deprecated_network(instance string) string {
|
||||||
return fmt.Sprintf(`
|
return fmt.Sprintf(`
|
||||||
resource "google_compute_instance" "foobar" {
|
resource "google_compute_instance" "foobar" {
|
||||||
@ -2109,3 +2162,63 @@ resource "google_compute_instance" "foobar" {
|
|||||||
min_cpu_platform = "Intel Haswell"
|
min_cpu_platform = "Intel Haswell"
|
||||||
}`, instance)
|
}`, instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testAccComputeInstance_primaryAliasIpRange(instance string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "google_compute_instance" "foobar" {
|
||||||
|
name = "%s"
|
||||||
|
machine_type = "n1-standard-1"
|
||||||
|
zone = "us-east1-d"
|
||||||
|
|
||||||
|
boot_disk {
|
||||||
|
initialize_params {
|
||||||
|
image = "debian-8-jessie-v20160803"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
network_interface {
|
||||||
|
network = "default"
|
||||||
|
|
||||||
|
alias_ip_range {
|
||||||
|
ip_cidr_range = "/24"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`, instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccComputeInstance_secondaryAliasIpRange(instance string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "google_compute_network" "inst-test-network" {
|
||||||
|
name = "inst-test-network-%s"
|
||||||
|
}
|
||||||
|
resource "google_compute_subnetwork" "inst-test-subnetwork" {
|
||||||
|
name = "inst-test-subnetwork-%s"
|
||||||
|
ip_cidr_range = "10.0.0.0/16"
|
||||||
|
region = "us-east1"
|
||||||
|
network = "${google_compute_network.inst-test-network.self_link}"
|
||||||
|
secondary_ip_range {
|
||||||
|
range_name = "inst-test-secondary"
|
||||||
|
ip_cidr_range = "172.16.0.0/20"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resource "google_compute_instance" "foobar" {
|
||||||
|
name = "%s"
|
||||||
|
machine_type = "n1-standard-1"
|
||||||
|
zone = "us-east1-d"
|
||||||
|
|
||||||
|
boot_disk {
|
||||||
|
initialize_params {
|
||||||
|
image = "debian-8-jessie-v20160803"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
network_interface {
|
||||||
|
subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.self_link}"
|
||||||
|
|
||||||
|
alias_ip_range {
|
||||||
|
subnetwork_range_name = "${google_compute_subnetwork.inst-test-subnetwork.secondary_ip_range.0.range_name}"
|
||||||
|
ip_cidr_range = "172.16.0.0/24"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`, acctest.RandString(10), acctest.RandString(10), instance)
|
||||||
|
}
|
||||||
|
@ -46,7 +46,7 @@ func resourceComputeSubnetwork() *schema.Resource {
|
|||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
DiffSuppressFunc: compareGlobalSelfLinkOrResourceName,
|
DiffSuppressFunc: compareSelfLinkOrResourceName,
|
||||||
},
|
},
|
||||||
|
|
||||||
"description": &schema.Schema{
|
"description": &schema.Schema{
|
||||||
|
@ -28,15 +28,21 @@ func compareSelfLinkRelativePaths(k, old, new string, d *schema.ResourceData) bo
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use this method when the field accepts either a name or a self_link referencing a global resource.
|
// Use this method when the field accepts either a name or a self_link referencing a resource.
|
||||||
func compareGlobalSelfLinkOrResourceName(k, old, new string, d *schema.ResourceData) bool {
|
// The value we store (i.e. `old` in this method), must be a self_link.
|
||||||
oldParts := strings.Split(old, "/")
|
func compareSelfLinkOrResourceName(k, old, new string, d *schema.ResourceData) bool {
|
||||||
|
oldParts := strings.Split(old, "/") // always a self_link
|
||||||
newParts := strings.Split(new, "/")
|
newParts := strings.Split(new, "/")
|
||||||
|
|
||||||
if oldParts[len(oldParts)-1] == newParts[len(newParts)-1] {
|
if len(newParts) == 1 {
|
||||||
return true
|
// The `new` string is a name
|
||||||
|
if oldParts[len(oldParts)-1] == newParts[0] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
// The `new` string is a self_link
|
||||||
|
return compareSelfLinkRelativePaths(k, old, new, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash the relative path of a self link.
|
// Hash the relative path of a self link.
|
||||||
|
@ -258,6 +258,30 @@ func linkDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ipCidrRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
|
||||||
|
// The range may be a:
|
||||||
|
// A) single IP address (e.g. 10.2.3.4)
|
||||||
|
// B) CIDR format string (e.g. 10.1.2.0/24)
|
||||||
|
// C) netmask (e.g. /24)
|
||||||
|
//
|
||||||
|
// For A) and B), no diff to suppress, they have to match completely.
|
||||||
|
// For C), The API picks a network IP address and this creates a diff of the form:
|
||||||
|
// network_interface.0.alias_ip_range.0.ip_cidr_range: "10.128.1.0/24" => "/24"
|
||||||
|
// We should only compare the mask portion for this case.
|
||||||
|
if len(new) > 0 && new[0] == '/' {
|
||||||
|
oldNetmaskStartPos := strings.LastIndex(old, "/")
|
||||||
|
|
||||||
|
if oldNetmaskStartPos != -1 {
|
||||||
|
oldNetmask := old[strings.LastIndex(old, "/"):]
|
||||||
|
if oldNetmask == new {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// expandLabels pulls the value of "labels" out of a schema.ResourceData as a map[string]string.
|
// expandLabels pulls the value of "labels" out of a schema.ResourceData as a map[string]string.
|
||||||
func expandLabels(d *schema.ResourceData) map[string]string {
|
func expandLabels(d *schema.ResourceData) map[string]string {
|
||||||
return expandStringMap(d, "labels")
|
return expandStringMap(d, "labels")
|
||||||
|
47
google/utils_test.go
Normal file
47
google/utils_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package google
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIpCidrRangeDiffSuppress(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Old, New string
|
||||||
|
ExpectDiffSupress bool
|
||||||
|
}{
|
||||||
|
"single ip address": {
|
||||||
|
Old: "10.2.3.4",
|
||||||
|
New: "10.2.3.5",
|
||||||
|
ExpectDiffSupress: false,
|
||||||
|
},
|
||||||
|
"cidr format string": {
|
||||||
|
Old: "10.1.2.0/24",
|
||||||
|
New: "10.1.3.0/24",
|
||||||
|
ExpectDiffSupress: false,
|
||||||
|
},
|
||||||
|
"netmask same mask": {
|
||||||
|
Old: "10.1.2.0/24",
|
||||||
|
New: "/24",
|
||||||
|
ExpectDiffSupress: true,
|
||||||
|
},
|
||||||
|
"netmask different mask": {
|
||||||
|
Old: "10.1.2.0/24",
|
||||||
|
New: "/32",
|
||||||
|
ExpectDiffSupress: false,
|
||||||
|
},
|
||||||
|
"add netmask": {
|
||||||
|
Old: "",
|
||||||
|
New: "/24",
|
||||||
|
ExpectDiffSupress: false,
|
||||||
|
},
|
||||||
|
"remove netmask": {
|
||||||
|
Old: "/24",
|
||||||
|
New: "",
|
||||||
|
ExpectDiffSupress: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for tn, tc := range cases {
|
||||||
|
if ipCidrRangeDiffSuppress("ip_cidr_range", tc.Old, tc.New, nil) != tc.ExpectDiffSupress {
|
||||||
|
t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSupress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -229,11 +229,26 @@ The `network_interface` block supports:
|
|||||||
on that network). This block can be repeated multiple times. Structure
|
on that network). This block can be repeated multiple times. Structure
|
||||||
documented below.
|
documented below.
|
||||||
|
|
||||||
|
* `alias_ip_range` - (Optional, [Beta](/docs/providers/google/index.html#beta-features)) An
|
||||||
|
array of alias IP ranges for this network interface. Can only be specified for network
|
||||||
|
interfaces on subnet-mode networks. Structure documented below.
|
||||||
|
|
||||||
The `access_config` block supports:
|
The `access_config` block supports:
|
||||||
|
|
||||||
* `nat_ip` - (Optional) The IP address that will be 1:1 mapped to the instance's
|
* `nat_ip` - (Optional) The IP address that will be 1:1 mapped to the instance's
|
||||||
network ip. If not given, one will be generated.
|
network ip. If not given, one will be generated.
|
||||||
|
|
||||||
|
The `alias_ip_range` block supports:
|
||||||
|
|
||||||
|
* `ip_cidr_range` - The IP CIDR range represented by this alias IP range. This IP CIDR range
|
||||||
|
must belong to the specified subnetwork and cannot contain IP addresses reserved by
|
||||||
|
system or used by other network interfaces. This range may be a single IP address
|
||||||
|
(e.g. 10.2.3.4), a netmask (e.g. /24) or a CIDR format string (e.g. 10.1.2.0/24).
|
||||||
|
|
||||||
|
* `subnetwork_range_name` - (Optional) The subnetwork secondary range name specifying
|
||||||
|
the secondary range from which to allocate the IP CIDR range for this alias IP
|
||||||
|
range. If left unspecified, the primary range of the subnetwork will be used.
|
||||||
|
|
||||||
The `service_account` block supports:
|
The `service_account` block supports:
|
||||||
|
|
||||||
* `email` - (Optional) The service account e-mail address. If not given, the
|
* `email` - (Optional) The service account e-mail address. If not given, the
|
||||||
|
Loading…
Reference in New Issue
Block a user