Skip to content

Commit

Permalink
add external ipv6 support (#5241) (#10189)
Browse files Browse the repository at this point in the history
* add external ipv6 support for subnetworks

* add support for ipv6 to instance network interfaces

* add documentation

* update from review comments

* make stack_type O+C

* make stack_type O+C on subnetwork too. oops.

* update per review comments

Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
modular-magician authored Sep 29, 2021
1 parent 84a726d commit c422deb
Show file tree
Hide file tree
Showing 11 changed files with 504 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .changelog/5241.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
compute: added external IPv6 support on `google_compute_subnetwork` and `google_compute_instance.network_interfaces`
```
43 changes: 37 additions & 6 deletions google/compute_instance_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,17 @@ func flattenAccessConfigs(accessConfigs []*computeBeta.AccessConfig) ([]map[stri
return flattened, natIP
}

func flattenIpv6AccessConfigs(ipv6AccessConfigs []*computeBeta.AccessConfig) []map[string]interface{} {
flattened := make([]map[string]interface{}, len(ipv6AccessConfigs))
for i, ac := range ipv6AccessConfigs {
flattened[i] = map[string]interface{}{
"network_tier": ac.NetworkTier,
}
flattened[i]["public_ptr_domain_name"] = ac.PublicPtrDomainName
}
return flattened
}

func flattenNetworkInterfaces(d *schema.ResourceData, config *Config, networkInterfaces []*computeBeta.NetworkInterface) ([]map[string]interface{}, string, string, string, error) {
flattened := make([]map[string]interface{}, len(networkInterfaces))
var region, internalIP, externalIP string
Expand All @@ -185,6 +196,8 @@ func flattenNetworkInterfaces(d *schema.ResourceData, config *Config, networkInt
"access_config": ac,
"alias_ip_range": flattenAliasIpRange(iface.AliasIpRanges),
"nic_type": iface.NicType,
"stack_type": iface.StackType,
"ipv6_access_config": flattenIpv6AccessConfigs(iface.Ipv6AccessConfigs),
}
// Instance template interfaces never have names, so they're absent
// in the instance template network_interface schema. We want to use the
Expand Down Expand Up @@ -218,6 +231,22 @@ func expandAccessConfigs(configs []interface{}) []*computeBeta.AccessConfig {
return acs
}

func expandIpv6AccessConfigs(configs []interface{}) []*computeBeta.AccessConfig {
iacs := make([]*computeBeta.AccessConfig, len(configs))
for i, raw := range configs {
iacs[i] = &computeBeta.AccessConfig{}
if raw != nil {
data := raw.(map[string]interface{})
iacs[i].NetworkTier = data["network_tier"].(string)
if ptr, ok := data["public_ptr_domain_name"]; ok && ptr != "" {
iacs[i].PublicPtrDomainName = ptr.(string)
}
iacs[i].Type = "DIRECT_IPV6" // Currently only type supported
}
}
return iacs
}

func expandNetworkInterfaces(d TerraformResourceData, config *Config) ([]*computeBeta.NetworkInterface, error) {
configs := d.Get("network_interface").([]interface{})
ifaces := make([]*computeBeta.NetworkInterface, len(configs))
Expand All @@ -242,12 +271,14 @@ func expandNetworkInterfaces(d TerraformResourceData, config *Config) ([]*comput
}

ifaces[i] = &computeBeta.NetworkInterface{
NetworkIP: data["network_ip"].(string),
Network: nf.RelativeLink(),
Subnetwork: sf.RelativeLink(),
AccessConfigs: expandAccessConfigs(data["access_config"].([]interface{})),
AliasIpRanges: expandAliasIpRanges(data["alias_ip_range"].([]interface{})),
NicType: data["nic_type"].(string),
NetworkIP: data["network_ip"].(string),
Network: nf.RelativeLink(),
Subnetwork: sf.RelativeLink(),
AccessConfigs: expandAccessConfigs(data["access_config"].([]interface{})),
AliasIpRanges: expandAliasIpRanges(data["alias_ip_range"].([]interface{})),
NicType: data["nic_type"].(string),
StackType: data["stack_type"].(string),
Ipv6AccessConfigs: expandIpv6AccessConfigs(data["ipv6_access_config"].([]interface{})),
}
}
return ifaces, nil
Expand Down
45 changes: 45 additions & 0 deletions google/resource_compute_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,51 @@ func resourceComputeInstance() *schema.Resource {
},
},
},

"stack_type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice([]string{"IPV4_ONLY", "IPV4_IPV6", ""}, false),
Description: `The stack type for this network interface to identify whether the IPv6 feature is enabled or not. If not specified, IPV4_ONLY will be used.`,
},

"ipv6_access_type": {
Type: schema.TypeString,
Computed: true,
Description: `One of EXTERNAL, INTERNAL to indicate whether the IP can be accessed from the Internet. This field is always inherited from its subnetwork.`,
},

"ipv6_access_config": {
Type: schema.TypeList,
Optional: true,
Description: `An array of IPv6 access configurations for this interface. Currently, only one IPv6 access config, DIRECT_IPV6, is supported. If there is no ipv6AccessConfig specified, then this instance will have no external IPv6 Internet access.`,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"network_tier": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"PREMIUM"}, false),
Description: `The service-level to be provided for IPv6 traffic when the subnet has an external subnet. Only PREMIUM tier is valid for IPv6`,
},
"public_ptr_domain_name": {
Type: schema.TypeString,
Optional: true,
Description: `The domain name to be used when creating DNSv6 records for the external IPv6 ranges.`,
},
"external_ipv6": {
Type: schema.TypeString,
Computed: true,
Description: `The first IPv6 address of the external IPv6 range associated with this instance, prefix length is stored in externalIpv6PrefixLength in ipv6AccessConfig. The field is output only, an IPv6 address from a subnetwork associated with the instance will be allocated dynamically.`,
},
"external_ipv6_prefix_length": {
Type: schema.TypeString,
Computed: true,
Description: `The prefix length of the external IPv6 range.`,
},
},
},
},
},
},
},
Expand Down
47 changes: 47 additions & 0 deletions google/resource_compute_instance_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,53 @@ func resourceComputeInstanceTemplate() *schema.Resource {
},
},
},

"stack_type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice([]string{"IPV4_ONLY", "IPV4_IPV6", ""}, false),
Description: `The stack type for this network interface to identify whether the IPv6 feature is enabled or not. If not specified, IPV4_ONLY will be used.`,
},

"ipv6_access_type": {
Type: schema.TypeString,
Computed: true,
Description: `One of EXTERNAL, INTERNAL to indicate whether the IP can be accessed from the Internet. This field is always inherited from its subnetwork.`,
},

"ipv6_access_config": {
Type: schema.TypeList,
Optional: true,
Description: `An array of IPv6 access configurations for this interface. Currently, only one IPv6 access config, DIRECT_IPV6, is supported. If there is no ipv6AccessConfig specified, then this instance will have no external IPv6 Internet access.`,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"network_tier": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"PREMIUM"}, false),
Description: `The service-level to be provided for IPv6 traffic when the subnet has an external subnet. Only PREMIUM tier is valid for IPv6`,
},
// Possibly configurable- this was added so we don't break if it's inadvertently set
// (assuming the same ass access config)
"public_ptr_domain_name": {
Type: schema.TypeString,
Computed: true,
Description: `The domain name to be used when creating DNSv6 records for the external IPv6 ranges.`,
},
"external_ipv6": {
Type: schema.TypeString,
Computed: true,
Description: `The first IPv6 address of the external IPv6 range associated with this instance, prefix length is stored in externalIpv6PrefixLength in ipv6AccessConfig. The field is output only, an IPv6 address from a subnetwork associated with the instance will be allocated dynamically.`,
},
"external_ipv6_prefix_length": {
Type: schema.TypeString,
Computed: true,
Description: `The prefix length of the external IPv6 range.`,
},
},
},
},
},
},
},
Expand Down
79 changes: 79 additions & 0 deletions google/resource_compute_instance_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,32 @@ func TestAccComputeInstanceTemplate_IP(t *testing.T) {
})
}

func TestAccComputeInstanceTemplate_IPv6(t *testing.T) {
t.Parallel()

var instanceTemplate compute.InstanceTemplate

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeInstanceTemplateDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccComputeInstanceTemplate_ipv6(randString(t, 10)),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeInstanceTemplateExists(
t, "google_compute_instance_template.foobar", &instanceTemplate),
),
},
{
ResourceName: "google_compute_instance_template.foobar",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccComputeInstanceTemplate_networkTier(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -1585,6 +1611,59 @@ resource "google_compute_instance_template" "foobar" {
`, suffix, suffix)
}

func testAccComputeInstanceTemplate_ipv6(suffix string) string {
return fmt.Sprintf(`
resource "google_compute_address" "foo" {
name = "tf-test-instance-template-%s"
}
data "google_compute_image" "my_image" {
family = "debian-9"
project = "debian-cloud"
}
resource "google_compute_network" "foo" {
name = "tf-test-network-%s"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "subnetwork-ipv6" {
name = "tf-test-subnetwork-%s"
ip_cidr_range = "10.0.0.0/22"
region = "us-west2"
stack_type = "IPV4_IPV6"
ipv6_access_type = "EXTERNAL"
network = google_compute_network.foo.id
}
resource "google_compute_instance_template" "foobar" {
name = "tf-test-instance-template-%s"
machine_type = "e2-medium"
region = "us-west2"
tags = ["foo", "bar"]
disk {
source_image = data.google_compute_image.my_image.self_link
}
network_interface {
subnetwork = google_compute_subnetwork.subnetwork-ipv6.name
stack_type = "IPV4_IPV6"
ipv6_access_config {
network_tier = "PREMIUM"
}
}
metadata = {
foo = "bar"
}
}
`, suffix, suffix, suffix, suffix)
}

func testAccComputeInstanceTemplate_networkTier(suffix string) string {
return fmt.Sprintf(`
data "google_compute_image" "my_image" {
Expand Down
85 changes: 85 additions & 0 deletions google/resource_compute_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,35 @@ func TestAccComputeInstance_IP(t *testing.T) {
})
}

func TestAccComputeInstance_IPv6(t *testing.T) {
t.Parallel()

var instance compute.Instance
var ipName = fmt.Sprintf("tf-test-%s", randString(t, 10))
var instanceName = fmt.Sprintf("tf-test-%s", randString(t, 10))
var ptrName = fmt.Sprintf("tf-test-%s", randString(t, 10))

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeInstanceDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccComputeInstance_ipv6(ipName, instanceName, ptrName),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeInstanceExists(
t, "google_compute_instance.foobar", &instance),
),
},
{
ResourceName: "google_compute_instance.foobar",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccComputeInstance_PTRRecord(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -3284,6 +3313,62 @@ resource "google_compute_instance" "foobar" {
`, ip, instance)
}

func testAccComputeInstance_ipv6(ip, instance, record string) string {
return fmt.Sprintf(`
data "google_compute_image" "my_image" {
family = "debian-9"
project = "debian-cloud"
}
resource "google_compute_subnetwork" "subnetwork-ipv6" {
name = "%s-subnetwork"
ip_cidr_range = "10.0.0.0/22"
region = "us-west2"
stack_type = "IPV4_IPV6"
ipv6_access_type = "EXTERNAL"
network = google_compute_network.custom-test.id
}
resource "google_compute_network" "custom-test" {
name = "%s-network"
auto_create_subnetworks = false
}
resource "google_compute_address" "foo" {
name = "%s"
}
resource "google_compute_instance" "foobar" {
name = "%s"
machine_type = "e2-medium"
zone = "us-west2-a"
tags = ["foo", "bar"]
boot_disk {
initialize_params {
image = data.google_compute_image.my_image.self_link
}
}
network_interface {
subnetwork = google_compute_subnetwork.subnetwork-ipv6.name
stack_type = "IPV4_IPV6"
ipv6_access_config {
network_tier = "PREMIUM"
public_ptr_domain_name = "%s.gcp.tfacc.hashicorptest.com."
}
}
metadata = {
foo = "bar"
}
}
`, instance, instance, ip, instance, record)
}

func testAccComputeInstance_PTRRecord(record, instance string) string {
return fmt.Sprintf(`
data "google_compute_image" "my_image" {
Expand Down
Loading

0 comments on commit c422deb

Please sign in to comment.