Add router nat resource. For TPG #2249. (#2576)

This commit is contained in:
The Magician 2018-12-05 09:21:13 -08:00 committed by Nathan McKinley
parent 979e846f4e
commit e07e1ba4e8
3 changed files with 612 additions and 0 deletions

View File

@ -147,6 +147,7 @@ func Provider() terraform.ResourceProvider {
"google_compute_route": resourceComputeRoute(),
"google_compute_router": resourceComputeRouter(),
"google_compute_router_interface": resourceComputeRouterInterface(),
"google_compute_router_nat": resourceComputeRouterNat(),
"google_compute_router_peer": resourceComputeRouterPeer(),
"google_compute_security_policy": resourceComputeSecurityPolicy(),
"google_compute_shared_vpc_host_project": resourceComputeSharedVpcHostProject(),

View File

@ -0,0 +1,387 @@
package google
import (
"fmt"
"log"
"time"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
computeBeta "google.golang.org/api/compute/v0.beta"
"google.golang.org/api/googleapi"
)
var (
routerNatSubnetworkConfig = &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"source_ip_ranges_to_nat": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"secondary_ip_range_names": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
)
func resourceComputeRouterNat() *schema.Resource {
return &schema.Resource{
// TODO(https://github.com/GoogleCloudPlatform/magic-modules/issues/963): Implement Update
Create: resourceComputeRouterNatCreate,
Read: resourceComputeRouterNatRead,
Delete: resourceComputeRouterNatDelete,
Importer: &schema.ResourceImporter{
State: resourceComputeRouterNatImportState,
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(10 * time.Minute),
Delete: schema.DefaultTimeout(10 * time.Minute),
},
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateRFC1035Name(2, 63),
},
"router": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"nat_ip_allocate_option": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{"MANUAL_ONLY", "AUTO_ONLY"}, false),
},
"nat_ips": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"source_subnetwork_ip_ranges_to_nat": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{"ALL_SUBNETWORKS_ALL_IP_RANGES", "ALL_SUBNETWORKS_ALL_PRIMARY_IP_RANGES", "LIST_OF_SUBNETWORKS"}, false),
},
"subnetwork": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Elem: routerNatSubnetworkConfig,
},
"min_ports_per_vm": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
},
"udp_idle_timeout_sec": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
},
"icmp_idle_timeout_sec": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
},
"tcp_established_idle_timeout_sec": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
},
"tcp_transitory_idle_timeout_sec": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
},
"project": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"region": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
},
}
}
func resourceComputeRouterNatCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
region, err := getRegion(d, config)
if err != nil {
return err
}
project, err := getProject(d, config)
if err != nil {
return err
}
routerName := d.Get("router").(string)
natName := d.Get("name").(string)
routerLock := getRouterLockName(region, routerName)
mutexKV.Lock(routerLock)
defer mutexKV.Unlock(routerLock)
routersService := config.clientComputeBeta.Routers
router, err := routersService.Get(project, region, routerName).Do()
if err != nil {
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
return fmt.Errorf("Router %s/%s not found", region, routerName)
}
return fmt.Errorf("Error Reading router %s/%s: %s", region, routerName, err)
}
nats := router.Nats
for _, nat := range nats {
if nat.Name == natName {
return fmt.Errorf("Router %s has nat %s already", routerName, natName)
}
}
nat := &computeBeta.RouterNat{
Name: natName,
NatIpAllocateOption: d.Get("nat_ip_allocate_option").(string),
NatIps: convertStringArr(d.Get("nat_ips").(*schema.Set).List()),
SourceSubnetworkIpRangesToNat: d.Get("source_subnetwork_ip_ranges_to_nat").(string),
MinPortsPerVm: int64(d.Get("min_ports_per_vm").(int)),
UdpIdleTimeoutSec: int64(d.Get("udp_idle_timeout_sec").(int)),
IcmpIdleTimeoutSec: int64(d.Get("icmp_idle_timeout_sec").(int)),
TcpEstablishedIdleTimeoutSec: int64(d.Get("tcp_established_idle_timeout_sec").(int)),
TcpTransitoryIdleTimeoutSec: int64(d.Get("tcp_transitory_idle_timeout_sec").(int)),
}
if v, ok := d.GetOk("subnetwork"); ok {
nat.Subnetworks = expandSubnetworks(v.(*schema.Set).List())
}
log.Printf("[INFO] Adding nat %s", natName)
nats = append(nats, nat)
patchRouter := &computeBeta.Router{
Nats: nats,
}
log.Printf("[DEBUG] Updating router %s/%s with nats: %+v", region, routerName, nats)
op, err := routersService.Patch(project, region, router.Name, patchRouter).Do()
if err != nil {
return fmt.Errorf("Error patching router %s/%s: %s", region, routerName, err)
}
d.SetId(fmt.Sprintf("%s/%s/%s", region, routerName, natName))
err = computeBetaOperationWaitTime(config.clientCompute, op, project, "Patching router", int(d.Timeout(schema.TimeoutCreate).Minutes()))
if err != nil {
d.SetId("")
return fmt.Errorf("Error waiting to patch router %s/%s: %s", region, routerName, err)
}
return resourceComputeRouterNatRead(d, meta)
}
func resourceComputeRouterNatRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
region, err := getRegion(d, config)
if err != nil {
return err
}
project, err := getProject(d, config)
if err != nil {
return err
}
routerName := d.Get("router").(string)
natName := d.Get("name").(string)
routersService := config.clientComputeBeta.Routers
router, err := routersService.Get(project, region, routerName).Do()
if err != nil {
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
log.Printf("[WARN] Removing router nat %s because its router %s/%s is gone", natName, region, routerName)
d.SetId("")
return nil
}
return fmt.Errorf("Error Reading router %s/%s: %s", region, routerName, err)
}
for _, nat := range router.Nats {
if nat.Name == natName {
d.SetId(fmt.Sprintf("%s/%s/%s", region, routerName, natName))
d.Set("nat_ip_allocate_option", nat.NatIpAllocateOption)
d.Set("nat_ips", schema.NewSet(schema.HashString, convertStringArrToInterface(convertSelfLinksToV1(nat.NatIps))))
d.Set("source_subnetwork_ip_ranges_to_nat", nat.SourceSubnetworkIpRangesToNat)
d.Set("min_ports_per_vm", nat.MinPortsPerVm)
d.Set("udp_idle_timeout_sec", nat.UdpIdleTimeoutSec)
d.Set("icmp_idle_timeout_sec", nat.IcmpIdleTimeoutSec)
d.Set("tcp_established_idle_timeout_sec", nat.TcpEstablishedIdleTimeoutSec)
d.Set("tcp_transitory_idle_timeout_sec", nat.TcpTransitoryIdleTimeoutSec)
d.Set("region", region)
d.Set("project", project)
if err := d.Set("subnetwork", flattenRouterNatSubnetworkToNatBeta(nat.Subnetworks)); err != nil {
return fmt.Errorf("Error reading router nat: %s", err)
}
return nil
}
}
log.Printf("[WARN] Removing router nat %s/%s/%s because it is gone", region, routerName, natName)
d.SetId("")
return nil
}
func resourceComputeRouterNatDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
region, err := getRegion(d, config)
if err != nil {
return err
}
project, err := getProject(d, config)
if err != nil {
return err
}
routerName := d.Get("router").(string)
natName := d.Get("name").(string)
routerLock := getRouterLockName(region, routerName)
mutexKV.Lock(routerLock)
defer mutexKV.Unlock(routerLock)
routersService := config.clientComputeBeta.Routers
router, err := routersService.Get(project, region, routerName).Do()
if err != nil {
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
log.Printf("[WARN] Removing router nat %s because its router %s/%s is gone", natName, region, routerName)
return nil
}
return fmt.Errorf("Error Reading Router %s: %s", routerName, err)
}
var newNats []*computeBeta.RouterNat = make([]*computeBeta.RouterNat, 0, len(router.Nats))
for _, nat := range router.Nats {
if nat.Name == natName {
continue
} else {
newNats = append(newNats, nat)
}
}
if len(newNats) == len(router.Nats) {
log.Printf("[DEBUG] Router %s/%s had no nat %s already", region, routerName, natName)
d.SetId("")
return nil
}
log.Printf("[INFO] Removing nat %s from router %s/%s", natName, region, routerName)
patchRouter := &computeBeta.Router{
Nats: newNats,
}
if len(newNats) == 0 {
patchRouter.ForceSendFields = append(patchRouter.ForceSendFields, "Nats")
}
log.Printf("[DEBUG] Updating router %s/%s with nats: %+v", region, routerName, newNats)
op, err := routersService.Patch(project, region, router.Name, patchRouter).Do()
if err != nil {
return fmt.Errorf("Error patching router %s/%s: %s", region, routerName, err)
}
err = computeBetaOperationWaitTime(config.clientCompute, op, project, "Patching router", int(d.Timeout(schema.TimeoutDelete).Minutes()))
if err != nil {
return fmt.Errorf("Error waiting to patch router %s/%s: %s", region, routerName, err)
}
d.SetId("")
return nil
}
func resourceComputeRouterNatImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
parts := strings.Split(d.Id(), "/")
if len(parts) != 3 {
return nil, fmt.Errorf("Invalid router nat specifier. Expecting {region}/{router}/{nat}")
}
d.Set("region", parts[0])
d.Set("router", parts[1])
d.Set("name", parts[2])
return []*schema.ResourceData{d}, nil
}
func expandSubnetworks(subnetworks []interface{}) []*computeBeta.RouterNatSubnetworkToNat {
result := make([]*computeBeta.RouterNatSubnetworkToNat, 0, len(subnetworks))
for _, subnetwork := range subnetworks {
snm := subnetwork.(map[string]interface{})
subnetworkToNat := computeBeta.RouterNatSubnetworkToNat{
Name: snm["name"].(string),
SourceIpRangesToNat: convertStringSet(snm["source_ip_ranges_to_nat"].(*schema.Set)),
}
if v, ok := snm["secondary_ip_range_names"]; ok {
subnetworkToNat.SecondaryIpRangeNames = convertStringSet(v.(*schema.Set))
}
result = append(result, &subnetworkToNat)
}
return result
}
func flattenRouterNatSubnetworkToNatBeta(subnetworksToNat []*computeBeta.RouterNatSubnetworkToNat) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(subnetworksToNat))
for _, subnetworkToNat := range subnetworksToNat {
stnMap := make(map[string]interface{})
stnMap["name"] = ConvertSelfLinkToV1(subnetworkToNat.Name)
stnMap["source_ip_ranges_to_nat"] = schema.NewSet(schema.HashString, convertStringArrToInterface(subnetworkToNat.SourceIpRangesToNat))
stnMap["secondary_ip_range_names"] = schema.NewSet(schema.HashString, convertStringArrToInterface(subnetworkToNat.SecondaryIpRangeNames))
result = append(result, stnMap)
}
return result
}
func convertSelfLinksToV1(selfLinks []string) []string {
result := make([]string, 0, len(selfLinks))
for _, selfLink := range selfLinks {
result = append(result, ConvertSelfLinkToV1(selfLink))
}
return result
}

View File

@ -0,0 +1,224 @@
package google
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccComputeRouterNat_basic(t *testing.T) {
t.Parallel()
testId := acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeRouterNatDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeRouterNatBasic(testId),
},
resource.TestStep{
ResourceName: "google_compute_router_nat.foobar",
ImportState: true,
ImportStateVerify: true,
},
resource.TestStep{
Config: testAccComputeRouterNatKeepRouter(testId),
Check: testAccCheckComputeRouterNatDelete(
"google_compute_router_nat.foobar"),
},
},
})
}
func TestAccComputeRouterNat_withManualIpAndSubnetConfiguration(t *testing.T) {
t.Parallel()
testId := acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeRouterNatDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeRouterNatWithManualIpAndSubnetConfiguration(testId),
},
resource.TestStep{
ResourceName: "google_compute_router_nat.foobar",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func testAccCheckComputeRouterNatDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
routersService := config.clientCompute.Routers
for _, rs := range s.RootModule().Resources {
if rs.Type != "google_compute_router" {
continue
}
project, err := getTestProject(rs.Primary, config)
if err != nil {
return err
}
region, err := getTestRegion(rs.Primary, config)
if err != nil {
return err
}
routerName := rs.Primary.Attributes["router"]
_, err = routersService.Get(project, region, routerName).Do()
if err == nil {
return fmt.Errorf("Error, Router %s in region %s still exists",
routerName, region)
}
}
return nil
}
func testAccCheckComputeRouterNatDelete(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
routersService := config.clientComputeBeta.Routers
for _, rs := range s.RootModule().Resources {
if rs.Type != "google_compute_router_nat" {
continue
}
project, err := getTestProject(rs.Primary, config)
if err != nil {
return err
}
region, err := getTestRegion(rs.Primary, config)
if err != nil {
return err
}
name := rs.Primary.Attributes["name"]
routerName := rs.Primary.Attributes["router"]
router, err := routersService.Get(project, region, routerName).Do()
if err != nil {
return fmt.Errorf("Error Reading Router %s: %s", routerName, err)
}
nats := router.Nats
for _, nat := range nats {
if nat.Name == name {
return fmt.Errorf("Nat %s still exists on router %s/%s", name, region, router.Name)
}
}
}
return nil
}
}
func testAccComputeRouterNatBasic(testId string) string {
return fmt.Sprintf(`
resource "google_compute_network" "foobar" {
name = "router-nat-test-%s"
}
resource "google_compute_subnetwork" "foobar" {
name = "router-nat-test-subnetwork-%s"
network = "${google_compute_network.foobar.self_link}"
ip_cidr_range = "10.0.0.0/16"
region = "us-central1"
}
resource "google_compute_router" "foobar"{
name = "router-nat-test-%s"
region = "${google_compute_subnetwork.foobar.region}"
network = "${google_compute_network.foobar.self_link}"
bgp {
asn = 64514
}
}
resource "google_compute_router_nat" "foobar" {
name = "router-nat-test-%s"
router = "${google_compute_router.foobar.name}"
region = "${google_compute_router.foobar.region}"
nat_ip_allocate_option = "AUTO_ONLY"
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
}
`, testId, testId, testId, testId)
}
func testAccComputeRouterNatWithManualIpAndSubnetConfiguration(testId string) string {
return fmt.Sprintf(`
resource "google_compute_network" "foobar" {
name = "router-nat-test-%s"
auto_create_subnetworks = "false"
}
resource "google_compute_subnetwork" "foobar" {
name = "router-nat-test-subnetwork-%s"
network = "${google_compute_network.foobar.self_link}"
ip_cidr_range = "10.0.0.0/16"
region = "us-central1"
}
resource "google_compute_address" "foobar" {
name = "router-nat-test-%s"
region = "${google_compute_subnetwork.foobar.region}"
}
resource "google_compute_router" "foobar"{
name = "router-nat-test-%s"
region = "${google_compute_subnetwork.foobar.region}"
network = "${google_compute_network.foobar.self_link}"
bgp {
asn = 64514
}
}
resource "google_compute_router_nat" "foobar" {
name = "router-nat-test-%s"
router = "${google_compute_router.foobar.name}"
region = "${google_compute_router.foobar.region}"
nat_ip_allocate_option = "MANUAL_ONLY"
nat_ips = ["${google_compute_address.foobar.self_link}"]
source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS"
subnetwork {
name = "${google_compute_subnetwork.foobar.self_link}"
source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
}
}
`, testId, testId, testId, testId, testId)
}
func testAccComputeRouterNatKeepRouter(testId string) string {
return fmt.Sprintf(`
resource "google_compute_network" "foobar" {
name = "router-nat-test-%s"
auto_create_subnetworks = "false"
}
resource "google_compute_subnetwork" "foobar" {
name = "router-nat-test-subnetwork-%s"
network = "${google_compute_network.foobar.self_link}"
ip_cidr_range = "10.0.0.0/16"
region = "us-central1"
}
resource "google_compute_router" "foobar"{
name = "router-nat-test-%s"
region = "${google_compute_subnetwork.foobar.region}"
network = "${google_compute_network.foobar.self_link}"
bgp {
asn = 64514
}
}
`, testId, testId, testId)
}