Skip to content

Commit

Permalink
Add support for Cloud NAT
Browse files Browse the repository at this point in the history
  • Loading branch information
cornmander authored and modular-magician committed Nov 30, 2018
1 parent c949f93 commit c756d27
Show file tree
Hide file tree
Showing 5 changed files with 782 additions and 0 deletions.
1 change: 1 addition & 0 deletions google-beta/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,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(),
Expand Down
387 changes: 387 additions & 0 deletions google-beta/resource_compute_router_nat.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit c756d27

Please sign in to comment.