diff --git a/.changelog/6643.txt b/.changelog/6643.txt
new file mode 100644
index 00000000000..d32ee3a3f72
--- /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 e94f6bacc63..c72e9423a70 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 2061c957bc0..f63cb156131 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 80c9471d8fc..ab408e321a7 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: