-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add external ipv6 support #5241
Changes from 3 commits
1bbe44f
28002a9
7eae317
ce72927
90fe8a6
78daa78
0eb686f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
resource "google_compute_subnetwork" "subnetwork-ipv6" { | ||
name = "<%= ctx[:vars]['subnetwork_name'] %>" | ||
|
||
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 = "<%= ctx[:vars]['network_name'] %>" | ||
auto_create_subnetworks = false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -351,6 +351,54 @@ func resourceComputeInstance() *schema.Resource { | |
}, | ||
}, | ||
}, | ||
|
||
"stack_type": { | ||
Type: schema.TypeString, | ||
Optional: 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.`, | ||
Default: "IPV4_ONLY", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Conversely, this one does appear to be returned. There's a question of whether it's also returned for old resources created before the field was introduced, and I'm not sure what the answer is. If you happen to have an old GCE instance lying around, that'd be worth checking! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i checked, it is not returned on old instances. thanks for pointing this out! |
||
}, | ||
|
||
"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": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we make one of the subfields of this |
||
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{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: ordering Required -> Optional -> Computed would be nice, that's consistent with most of the code base (although there are certainly exceptions) |
||
"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.`, | ||
}, | ||
|
||
"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.`, | ||
}, | ||
|
||
"network_tier": { | ||
Type: schema.TypeString, | ||
Optional: 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`, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -164,6 +164,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 | ||
|
@@ -179,13 +190,15 @@ func flattenNetworkInterfaces(d *schema.ResourceData, config *Config, networkInt | |
region = subnet.Region | ||
|
||
flattened[i] = map[string]interface{}{ | ||
"network_ip": iface.NetworkIP, | ||
"network": ConvertSelfLinkToV1(iface.Network), | ||
"subnetwork": ConvertSelfLinkToV1(iface.Subnetwork), | ||
"subnetwork_project": subnet.Project, | ||
"access_config": ac, | ||
"alias_ip_range": flattenAliasIpRange(iface.AliasIpRanges), | ||
"nic_type": iface.NicType, | ||
"network_ip": iface.NetworkIP, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instance template shares this code, so this is likely to crash it by setting a field that doesn't exist. Does template support configuring this value? We may need to guard it, otherwise. |
||
"network": ConvertSelfLinkToV1(iface.Network), | ||
"subnetwork": ConvertSelfLinkToV1(iface.Subnetwork), | ||
"subnetwork_project": subnet.Project, | ||
"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 | ||
|
@@ -219,6 +232,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)) | ||
|
@@ -243,12 +272,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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can apply a default here! The API doesn't return the value for older resources, so it wouldn't be safe to apply.
Here's an example from a couple nights ago:
There's a few mitigation options:
default_if_empty: true
interraform.yaml
default_if_empty
rewrites all empty responses into the default, so if empty means something else Terraform'll confuse users.