Skip to content
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 rules field to google_compute_router_nat #12815

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/6643.txt
@@ -0,0 +1,3 @@
```release-note:enhancement
compute: added general field `rules` to `google_compute_router_nat`
```
327 changes: 327 additions & 0 deletions google/resource_compute_router_nat.go
Expand Up @@ -23,6 +23,7 @@ import (
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func resourceNameSetFromSelfLinkSet(v interface{}) *schema.Set {
Expand Down Expand Up @@ -111,6 +112,48 @@ func computeRouterNatIPsHash(v interface{}) int {
return schema.HashString(GetResourceNameFromSelfLink(val))
}

func computeRouterNatRulesHash(v interface{}) int {
obj := v.(map[string]interface{})
ruleNumber := obj["rule_number"].(int)

description := obj["description"]
descriptionHash := 0
if description != nil {
descriptionHash = schema.HashString(description.(string))
}

match := obj["match"].(string)

sourceNatActiveIpHash := 0
sourceNatDrainIpHash := 0
if obj["action"] != nil {
actions := obj["action"].([]interface{})
if len(actions) != 0 && actions[0] != nil {
action := actions[0].(map[string]interface{})

sourceNatActiveIps := action["source_nat_active_ips"]
if sourceNatActiveIps != nil {
sourceNatActiveIpSet := sourceNatActiveIps.(*schema.Set)
for _, sourceNatActiveIp := range sourceNatActiveIpSet.List() {
sourceNatActiveIpStr := fmt.Sprintf("source_nat_active_ips-%d", computeRouterNatIPsHash(sourceNatActiveIp.(string)))
sourceNatActiveIpHash += schema.HashString(sourceNatActiveIpStr)
}
}

soureNatDrainIps := action["source_nat_drain_ips"]
if soureNatDrainIps != nil {
soureNatDrainIpSet := soureNatDrainIps.(*schema.Set)
for _, soureNatDrainIp := range soureNatDrainIpSet.List() {
sourceNatDrainIpStr := fmt.Sprintf("source_nat_drain_ips-%d", computeRouterNatIPsHash(soureNatDrainIp.(string)))
sourceNatDrainIpHash += schema.HashString(sourceNatDrainIpStr)
}
}
}
}

return ruleNumber + descriptionHash + schema.HashString(match) + sourceNatActiveIpHash + sourceNatDrainIpHash
}

func resourceComputeRouterNat() *schema.Resource {
return &schema.Resource{
Create: resourceComputeRouterNatCreate,
Expand Down Expand Up @@ -256,6 +299,13 @@ is set to MANUAL_ONLY.`,
DiffSuppressFunc: compareSelfLinkOrResourceName,
Description: `Region where the router and NAT reside.`,
},
"rules": {
Type: schema.TypeSet,
Optional: true,
Description: `A list of rules associated with this NAT.`,
Elem: computeRouterNatRulesSchema(),
Set: computeRouterNatRulesHash,
},
"subnetwork": {
Type: schema.TypeSet,
Optional: true,
Expand Down Expand Up @@ -333,6 +383,77 @@ sourceIpRangesToNat`,
}
}

func computeRouterNatRulesSchema() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"match": {
Type: schema.TypeString,
Required: true,
Description: `CEL expression that specifies the match condition that egress traffic from a VM is evaluated against.
If it evaluates to true, the corresponding action is enforced.

The following examples are valid match expressions for public NAT:

"inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')"

"destination.ip == '1.1.0.1' || destination.ip == '8.8.8.8'"

The following example is a valid match expression for private NAT:

"nexthop.hub == 'https://networkconnectivity.googleapis.com/v1alpha1/projects/my-project/global/hub/hub-1'"`,
},
"rule_number": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(0, 65000),
Description: `An integer uniquely identifying a rule in the list.
The rule number must be a positive value between 0 and 65000, and must be unique among rules within a NAT.`,
},
"action": {
Type: schema.TypeList,
Computed: true,
Optional: true,
Description: `The action to be enforced for traffic that matches this rule.`,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"source_nat_active_ips": {
Type: schema.TypeSet,
Optional: true,
Description: `A list of URLs of the IP resources used for this NAT rule.
These IP addresses must be valid static external IP addresses assigned to the project.
This field is used for public NAT.`,
Elem: &schema.Schema{
Type: schema.TypeString,
DiffSuppressFunc: compareSelfLinkOrResourceName,
},
Set: computeRouterNatIPsHash,
},
"source_nat_drain_ips": {
Type: schema.TypeSet,
Optional: true,
Description: `A list of URLs of the IP resources to be drained.
These IPs must be valid static external IPs that have been assigned to the NAT.
These IPs should be used for updating/patching a NAT rule only.
This field is used for public NAT.`,
Elem: &schema.Schema{
Type: schema.TypeString,
DiffSuppressFunc: compareSelfLinkOrResourceName,
},
Set: computeRouterNatIPsHash,
},
},
},
},
"description": {
Type: schema.TypeString,
Optional: true,
Description: `An optional description of this rule.`,
},
},
}
}

func resourceComputeRouterNatCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
userAgent, err := generateUserAgentString(d, config.userAgent)
Expand Down Expand Up @@ -425,6 +546,12 @@ func resourceComputeRouterNatCreate(d *schema.ResourceData, meta interface{}) er
} else if v, ok := d.GetOkExists("log_config"); ok || !reflect.DeepEqual(v, logConfigProp) {
obj["logConfig"] = logConfigProp
}
rulesProp, err := expandNestedComputeRouterNatRules(d.Get("rules"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("rules"); ok || !reflect.DeepEqual(v, rulesProp) {
obj["rules"] = rulesProp
}
enableEndpointIndependentMappingProp, err := expandNestedComputeRouterNatEnableEndpointIndependentMapping(d.Get("enable_endpoint_independent_mapping"), d, config)
if err != nil {
return err
Expand Down Expand Up @@ -578,6 +705,9 @@ func resourceComputeRouterNatRead(d *schema.ResourceData, meta interface{}) erro
if err := d.Set("log_config", flattenNestedComputeRouterNatLogConfig(res["logConfig"], d, config)); err != nil {
return fmt.Errorf("Error reading RouterNat: %s", err)
}
if err := d.Set("rules", flattenNestedComputeRouterNatRules(res["rules"], d, config)); err != nil {
return fmt.Errorf("Error reading RouterNat: %s", err)
}
if err := d.Set("enable_endpoint_independent_mapping", flattenNestedComputeRouterNatEnableEndpointIndependentMapping(res["enableEndpointIndependentMapping"], d, config)); err != nil {
return fmt.Errorf("Error reading RouterNat: %s", err)
}
Expand Down Expand Up @@ -679,6 +809,12 @@ func resourceComputeRouterNatUpdate(d *schema.ResourceData, meta interface{}) er
} else if v, ok := d.GetOkExists("log_config"); ok || !reflect.DeepEqual(v, logConfigProp) {
obj["logConfig"] = logConfigProp
}
rulesProp, err := expandNestedComputeRouterNatRules(d.Get("rules"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("rules"); ok || !reflect.DeepEqual(v, rulesProp) {
obj["rules"] = rulesProp
}
enableEndpointIndependentMappingProp, err := expandNestedComputeRouterNatEnableEndpointIndependentMapping(d.Get("enable_endpoint_independent_mapping"), d, config)
if err != nil {
return err
Expand Down Expand Up @@ -991,6 +1127,81 @@ func flattenNestedComputeRouterNatLogConfigFilter(v interface{}, d *schema.Resou
return v
}

func flattenNestedComputeRouterNatRules(v interface{}, d *schema.ResourceData, config *Config) interface{} {
if v == nil {
return v
}
l := v.([]interface{})
transformed := schema.NewSet(computeRouterNatRulesHash, []interface{}{})
for _, raw := range l {
original := raw.(map[string]interface{})
if len(original) < 1 {
// Do not include empty json objects coming back from the api
continue
}
transformed.Add(map[string]interface{}{
"rule_number": flattenNestedComputeRouterNatRulesRuleNumber(original["ruleNumber"], d, config),
"description": flattenNestedComputeRouterNatRulesDescription(original["description"], d, config),
"match": flattenNestedComputeRouterNatRulesMatch(original["match"], d, config),
"action": flattenNestedComputeRouterNatRulesAction(original["action"], d, config),
})
}
return transformed
}
func flattenNestedComputeRouterNatRulesRuleNumber(v interface{}, d *schema.ResourceData, config *Config) interface{} {
// Handles the string fixed64 format
if strVal, ok := v.(string); ok {
if intVal, err := stringToFixed64(strVal); err == nil {
return intVal
}
}

// number values are represented as float64
if floatVal, ok := v.(float64); ok {
intVal := int(floatVal)
return intVal
}

return v // let terraform core handle it otherwise
}

func flattenNestedComputeRouterNatRulesDescription(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func flattenNestedComputeRouterNatRulesMatch(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func flattenNestedComputeRouterNatRulesAction(v interface{}, d *schema.ResourceData, config *Config) interface{} {
if v == nil {
return nil
}
original := v.(map[string]interface{})
if len(original) == 0 {
return nil
}
transformed := make(map[string]interface{})
transformed["source_nat_active_ips"] =
flattenNestedComputeRouterNatRulesActionSourceNatActiveIps(original["sourceNatActiveIps"], d, config)
transformed["source_nat_drain_ips"] =
flattenNestedComputeRouterNatRulesActionSourceNatDrainIps(original["sourceNatDrainIps"], d, config)
return []interface{}{transformed}
}
func flattenNestedComputeRouterNatRulesActionSourceNatActiveIps(v interface{}, d *schema.ResourceData, config *Config) interface{} {
if v == nil {
return v
}
return schema.NewSet(computeRouterNatIPsHash, convertStringArrToInterface(convertAndMapStringArr(v.([]interface{}), ConvertSelfLinkToV1)))
}

func flattenNestedComputeRouterNatRulesActionSourceNatDrainIps(v interface{}, d *schema.ResourceData, config *Config) interface{} {
if v == nil {
return v
}
return schema.NewSet(computeRouterNatIPsHash, convertStringArrToInterface(convertAndMapStringArr(v.([]interface{}), ConvertSelfLinkToV1)))
}

func flattenNestedComputeRouterNatEnableEndpointIndependentMapping(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}
Expand Down Expand Up @@ -1158,6 +1369,122 @@ func expandNestedComputeRouterNatLogConfigFilter(v interface{}, d TerraformResou
return v, nil
}

func expandNestedComputeRouterNatRules(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
v = v.(*schema.Set).List()
l := v.([]interface{})
req := make([]interface{}, 0, len(l))
for _, raw := range l {
if raw == nil {
continue
}
original := raw.(map[string]interface{})
transformed := make(map[string]interface{})

transformedRuleNumber, err := expandNestedComputeRouterNatRulesRuleNumber(original["rule_number"], d, config)
if err != nil {
return nil, err
} else {
transformed["ruleNumber"] = transformedRuleNumber
}

transformedDescription, err := expandNestedComputeRouterNatRulesDescription(original["description"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedDescription); val.IsValid() && !isEmptyValue(val) {
transformed["description"] = transformedDescription
}

transformedMatch, err := expandNestedComputeRouterNatRulesMatch(original["match"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedMatch); val.IsValid() && !isEmptyValue(val) {
transformed["match"] = transformedMatch
}

transformedAction, err := expandNestedComputeRouterNatRulesAction(original["action"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedAction); val.IsValid() && !isEmptyValue(val) {
transformed["action"] = transformedAction
}

req = append(req, transformed)
}
return req, nil
}

func expandNestedComputeRouterNatRulesRuleNumber(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandNestedComputeRouterNatRulesDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandNestedComputeRouterNatRulesMatch(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandNestedComputeRouterNatRulesAction(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
l := v.([]interface{})
if len(l) == 0 || l[0] == nil {
return nil, nil
}
raw := l[0]
original := raw.(map[string]interface{})
transformed := make(map[string]interface{})

transformedSourceNatActiveIps, err := expandNestedComputeRouterNatRulesActionSourceNatActiveIps(original["source_nat_active_ips"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedSourceNatActiveIps); val.IsValid() && !isEmptyValue(val) {
transformed["sourceNatActiveIps"] = transformedSourceNatActiveIps
}

transformedSourceNatDrainIps, err := expandNestedComputeRouterNatRulesActionSourceNatDrainIps(original["source_nat_drain_ips"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedSourceNatDrainIps); val.IsValid() && !isEmptyValue(val) {
transformed["sourceNatDrainIps"] = transformedSourceNatDrainIps
}

return transformed, nil
}

func expandNestedComputeRouterNatRulesActionSourceNatActiveIps(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
v = v.(*schema.Set).List()
l := v.([]interface{})
req := make([]interface{}, 0, len(l))
for _, raw := range l {
if raw == nil {
return nil, fmt.Errorf("Invalid value for source_nat_active_ips: nil")
}
f, err := parseRegionalFieldValue("addresses", raw.(string), "project", "region", "zone", d, config, true)
if err != nil {
return nil, fmt.Errorf("Invalid value for source_nat_active_ips: %s", err)
}
req = append(req, f.RelativeLink())
}
return req, nil
}

func expandNestedComputeRouterNatRulesActionSourceNatDrainIps(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
v = v.(*schema.Set).List()
l := v.([]interface{})
req := make([]interface{}, 0, len(l))
for _, raw := range l {
if raw == nil {
return nil, fmt.Errorf("Invalid value for source_nat_drain_ips: nil")
}
f, err := parseRegionalFieldValue("addresses", raw.(string), "project", "region", "zone", d, config, true)
if err != nil {
return nil, fmt.Errorf("Invalid value for source_nat_drain_ips: %s", err)
}
req = append(req, f.RelativeLink())
}
return req, nil
}

func expandNestedComputeRouterNatEnableEndpointIndependentMapping(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}
Expand Down