From 9bd2a80cdac14343484264a6ea6f05cf683e5d1b Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Mon, 17 Oct 2022 21:52:01 +0000 Subject: [PATCH] Add rules field to google_compute_router_nat (#6643) Signed-off-by: Modular Magician --- .changelog/6643.txt | 3 + google/resource_compute_router_nat.go | 327 +++++++++++++ google/resource_compute_router_nat_test.go | 451 ++++++++++++++++++ .../docs/r/compute_router_nat.html.markdown | 110 +++++ 4 files changed, 891 insertions(+) create mode 100644 .changelog/6643.txt diff --git a/.changelog/6643.txt b/.changelog/6643.txt new file mode 100644 index 0000000000..d32ee3a3f7 --- /dev/null +++ b/.changelog/6643.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +compute: added general field `rules` to `google_compute_router_nat` +``` diff --git a/google/resource_compute_router_nat.go b/google/resource_compute_router_nat.go index e94f6bacc6..c72e9423a7 100644 --- a/google/resource_compute_router_nat.go +++ b/google/resource_compute_router_nat.go @@ -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 { @@ -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, @@ -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, @@ -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) @@ -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 @@ -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) } @@ -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 @@ -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 } @@ -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 } diff --git a/google/resource_compute_router_nat_test.go b/google/resource_compute_router_nat_test.go index 2061c957bc..f63cb15613 100644 --- a/google/resource_compute_router_nat_test.go +++ b/google/resource_compute_router_nat_test.go @@ -222,6 +222,129 @@ func TestAccComputeRouterNat_withPortAllocationMethods(t *testing.T) { }) } +func TestAccComputeRouterNat_withNatRules(t *testing.T) { + t.Parallel() + + testId := randString(t, 10) + routerName := fmt.Sprintf("tf-test-router-nat-%s", testId) + ruleDescription := randString(t, 10) + ruleDescriptionUpdate := randString(t, 10) + match := "inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')" + matchUpdate := "destination.ip == '1.1.0.1' || destination.ip == '8.8.8.8'" + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRouterNatDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeRouterNatRulesBasic_omitRules(routerName), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatRulesBasic(routerName, 0, ruleDescription, match), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatRulesBasic(routerName, 65000, ruleDescription, match), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatRulesBasic(routerName, 100, ruleDescription, match), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatRulesBasic(routerName, 100, ruleDescriptionUpdate, match), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatRulesBasic(routerName, 100, ruleDescriptionUpdate, matchUpdate), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatRulesWithSourceActiveAndDrainIps(routerName, 100, ruleDescriptionUpdate, matchUpdate), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatRulesWithDrainIps(routerName, 100, ruleDescriptionUpdate, matchUpdate), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatMultiRules(routerName), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatRulesBasic_omitAction(routerName, 100, ruleDescriptionUpdate, matchUpdate), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatRulesBasic_omitDescription(routerName, 100, matchUpdate), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatMultiRulesWithIpId(routerName), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatRulesBasic_omitRules(routerName), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckComputeRouterNatDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { config := googleProviderConfig(t) @@ -660,6 +783,334 @@ resource "google_compute_router_nat" "foobar" { `, routerName, routerName, routerName, routerName, routerName, enableEndpointIndependentMapping, enableDynamicPortAllocation, minPortsPerVm, maxPortsPerVm) } +func testAccComputeRouterNatBaseResourcesWithNatIps(routerName string) string { + return fmt.Sprintf(` +resource "google_compute_network" "foobar" { + name = "%s-net" + auto_create_subnetworks = "false" +} + +resource "google_compute_subnetwork" "foobar" { + name = "%s-subnet" + network = google_compute_network.foobar.self_link + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" +} + +resource "google_compute_address" "addr1" { + name = "%s-addr1" + region = google_compute_subnetwork.foobar.region +} + +resource "google_compute_address" "addr2" { + name = "%s-addr2" + region = google_compute_subnetwork.foobar.region +} + +resource "google_compute_address" "addr3" { + name = "%s-addr3" + region = google_compute_subnetwork.foobar.region +} + +resource "google_compute_address" "addr4" { + name = "%s-addr4" + region = google_compute_subnetwork.foobar.region +} + +resource "google_compute_router" "foobar" { + name = "%s" + region = google_compute_subnetwork.foobar.region + network = google_compute_network.foobar.self_link +} +`, routerName, routerName, routerName, routerName, routerName, routerName, routerName) +} + +func testAccComputeRouterNatRulesBasic_omitRules(routerName string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + nat_ip_allocate_option = "MANUAL_ONLY" + nat_ips = [google_compute_address.addr1.self_link] + + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.foobar.id + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } + + enable_endpoint_independent_mapping = false +} +`, testAccComputeRouterNatBaseResourcesWithNatIps(routerName), routerName) +} + +func testAccComputeRouterNatRulesBasic_omitAction(routerName string, ruleNumber int, ruleDescription string, ruleMatch string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + nat_ip_allocate_option = "MANUAL_ONLY" + nat_ips = [google_compute_address.addr1.self_link] + + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.foobar.id + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } + + rules { + rule_number = %d + description = "%s" + match = "%s" + } + + enable_endpoint_independent_mapping = false +} +`, testAccComputeRouterNatBaseResourcesWithNatIps(routerName), routerName, ruleNumber, ruleDescription, ruleMatch) +} + +func testAccComputeRouterNatRulesBasic_omitDescription(routerName string, ruleNumber int, ruleMatch string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + nat_ip_allocate_option = "MANUAL_ONLY" + nat_ips = [google_compute_address.addr1.self_link] + + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.foobar.id + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } + + rules { + rule_number = %d + match = "%s" + action { + source_nat_active_ips = [google_compute_address.addr2.self_link, google_compute_address.addr3.self_link] + } + } + + enable_endpoint_independent_mapping = false +} +`, testAccComputeRouterNatBaseResourcesWithNatIps(routerName), routerName, ruleNumber, ruleMatch) +} + +func testAccComputeRouterNatRulesBasic(routerName string, ruleNumber int, ruleDescription string, ruleMatch string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + nat_ip_allocate_option = "MANUAL_ONLY" + nat_ips = [google_compute_address.addr1.self_link] + + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.foobar.id + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } + + rules { + rule_number = %d + description = "%s" + match = "%s" + action { + source_nat_active_ips = [google_compute_address.addr2.self_link, google_compute_address.addr3.self_link] + } + } + + enable_endpoint_independent_mapping = false +} +`, testAccComputeRouterNatBaseResourcesWithNatIps(routerName), routerName, ruleNumber, ruleDescription, ruleMatch) +} + +func testAccComputeRouterNatRulesWithSourceActiveAndDrainIps(routerName string, ruleNumber int, ruleDescription string, ruleMatch string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + nat_ip_allocate_option = "MANUAL_ONLY" + nat_ips = [google_compute_address.addr1.self_link] + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.foobar.id + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } + + rules { + rule_number = %d + description = "%s" + match = "%s" + action { + source_nat_active_ips = [google_compute_address.addr2.self_link] + source_nat_drain_ips = [google_compute_address.addr3.self_link] + } + } + + enable_endpoint_independent_mapping = false +} +`, testAccComputeRouterNatBaseResourcesWithNatIps(routerName), routerName, ruleNumber, ruleDescription, ruleMatch) +} + +func testAccComputeRouterNatRulesWithDrainIps(routerName string, ruleNumber int, ruleDescription string, ruleMatch string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + nat_ip_allocate_option = "MANUAL_ONLY" + nat_ips = [google_compute_address.addr1.self_link] + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.foobar.id + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } + + rules { + rule_number = %d + description = "%s" + match = "%s" + action { + source_nat_drain_ips = [google_compute_address.addr2.self_link] + } + } + + enable_endpoint_independent_mapping = false +} +`, testAccComputeRouterNatBaseResourcesWithNatIps(routerName), routerName, ruleNumber, ruleDescription, ruleMatch) +} + +func testAccComputeRouterNatMultiRules(routerName string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + nat_ip_allocate_option = "MANUAL_ONLY" + nat_ips = [google_compute_address.addr1.self_link] + + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.foobar.id + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } + + rules { + rule_number = 100 + description = "a" + match = "destination.ip == '1.1.1.1' || destination.ip == '2.2.2.2'" + action { + source_nat_active_ips = [google_compute_address.addr2.self_link] + } + } + + rules { + rule_number = 5000 + description = "b" + match = "destination.ip == '3.3.3.3' || destination.ip == '4.4.4.4'" + action { + source_nat_active_ips = [google_compute_address.addr3.self_link] + } + } + + rules { + rule_number = 300 + description = "c" + match = "destination.ip == '5.5.5.5' || destination.ip == '8.8.8.8'" + action { + source_nat_active_ips = [google_compute_address.addr4.self_link] + } + } + + enable_endpoint_independent_mapping = false +} +`, testAccComputeRouterNatBaseResourcesWithNatIps(routerName), routerName) +} + +func testAccComputeRouterNatMultiRulesWithIpId(routerName string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + nat_ip_allocate_option = "MANUAL_ONLY" + nat_ips = [google_compute_address.addr1.id] + + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.foobar.id + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } + + rules { + rule_number = 100 + description = "a" + match = "destination.ip == '1.1.1.1' || destination.ip == '2.2.2.2'" + action { + source_nat_active_ips = [google_compute_address.addr2.id] + } + } + + rules { + rule_number = 5000 + description = "b" + match = "destination.ip == '3.3.3.3' || destination.ip == '4.4.4.4'" + action { + source_nat_active_ips = [google_compute_address.addr3.id] + } + } + + rules { + rule_number = 300 + description = "c" + match = "destination.ip == '5.5.5.5' || destination.ip == '8.8.8.8'" + action { + source_nat_active_ips = [google_compute_address.addr4.id] + } + } + + enable_endpoint_independent_mapping = false +} +`, testAccComputeRouterNatBaseResourcesWithNatIps(routerName), routerName) +} + func testAccComputeRouterNatKeepRouter(routerName string) string { return fmt.Sprintf(` resource "google_compute_network" "foobar" { diff --git a/website/docs/r/compute_router_nat.html.markdown b/website/docs/r/compute_router_nat.html.markdown index 80c9471d8f..ab408e321a 100644 --- a/website/docs/r/compute_router_nat.html.markdown +++ b/website/docs/r/compute_router_nat.html.markdown @@ -109,6 +109,69 @@ resource "google_compute_router_nat" "nat_manual" { } } ``` +## Example Usage - Router Nat Rules + + +```hcl +resource "google_compute_network" "net" { + name = "my-network" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnet" { + name = "my-subnetwork" + network = google_compute_network.net.id + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" +} + +resource "google_compute_router" "router" { + name = "my-router" + region = google_compute_subnetwork.subnet.region + network = google_compute_network.net.id +} + +resource "google_compute_address" "addr1" { + name = "nat-address1" + region = google_compute_subnetwork.subnet.region +} + +resource "google_compute_address" "addr2" { + name = "nat-address2" + region = google_compute_subnetwork.subnet.region +} + +resource "google_compute_address" "addr3" { + name = "nat-address3" + region = google_compute_subnetwork.subnet.region +} + +resource "google_compute_router_nat" "nat_rules" { + name = "my-router-nat" + router = google_compute_router.router.name + region = google_compute_router.router.region + + nat_ip_allocate_option = "MANUAL_ONLY" + nat_ips = [google_compute_address.addr1.self_link] + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.subnet.id + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } + + rules { + rule_number = 100 + description = "nat rules example" + match = "inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')" + action { + source_nat_active_ips = [google_compute_address.addr2.self_link, google_compute_address.addr3.self_link] + } + } + + enable_endpoint_independent_mapping = false +} +``` ## Argument Reference @@ -206,6 +269,11 @@ The following arguments are supported: Configuration for logging on NAT Structure is [documented below](#nested_log_config). +* `rules` - + (Optional) + A list of rules associated with this NAT. + Structure is [documented below](#nested_rules). + * `enable_endpoint_independent_mapping` - (Optional) Specifies if endpoint independent mapping is enabled. This is enabled by default. For more information @@ -250,6 +318,48 @@ The following arguments are supported: Specifies the desired filtering of logs on this NAT. Possible values are `ERRORS_ONLY`, `TRANSLATIONS_ONLY`, and `ALL`. +The `rules` block supports: + +* `rule_number` - + (Required) + 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. + +* `description` - + (Optional) + An optional description of this rule. + +* `match` - + (Required) + 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'" + +* `action` - + (Optional) + The action to be enforced for traffic that matches this rule. + Structure is [documented below](#nested_action). + + +The `action` block supports: + +* `source_nat_active_ips` - + (Optional) + 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. + +* `source_nat_drain_ips` - + (Optional) + 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. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are exported: