diff --git a/CHANGELOG.md b/CHANGELOG.md index 357912f73..eaedc0844 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ FEATURES: * **New resource: `google_compute_project_metadata`** [GH-3065] * **New resources: `aws_ami`, `aws_ami_copy`, `aws_ami_from_instance`** [GH-2874] * **New resource: `google_storage_bucket_object`** [GH-3192] + * **New resources: `google_compute_vpn_gateway`, `google_compute_vpn_tunnel`** [GH-3213] IMPROVEMENTS: diff --git a/builtin/providers/google/provider.go b/builtin/providers/google/provider.go index 2248227f2..a023b81c9 100644 --- a/builtin/providers/google/provider.go +++ b/builtin/providers/google/provider.go @@ -47,6 +47,8 @@ func Provider() terraform.ResourceProvider { "google_compute_project_metadata": resourceComputeProjectMetadata(), "google_compute_route": resourceComputeRoute(), "google_compute_target_pool": resourceComputeTargetPool(), + "google_compute_vpn_gateway": resourceComputeVpnGateway(), + "google_compute_vpn_tunnel": resourceComputeVpnTunnel(), "google_container_cluster": resourceContainerCluster(), "google_dns_managed_zone": resourceDnsManagedZone(), "google_dns_record_set": resourceDnsRecordSet(), diff --git a/builtin/providers/google/resource_compute_address.go b/builtin/providers/google/resource_compute_address.go index 9bb9547fe..721d67d14 100644 --- a/builtin/providers/google/resource_compute_address.go +++ b/builtin/providers/google/resource_compute_address.go @@ -32,18 +32,32 @@ func resourceComputeAddress() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + "region": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, }, } } +func getOptionalRegion(d *schema.ResourceData, config *Config) string { + if res, ok := d.GetOk("region"); !ok { + return config.Region + } else { + return res.(string) + } +} + func resourceComputeAddressCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) + region := getOptionalRegion(d, config) // Build the address parameter addr := &compute.Address{Name: d.Get("name").(string)} - log.Printf("[DEBUG] Address insert request: %#v", addr) op, err := config.clientCompute.Addresses.Insert( - config.Project, config.Region, addr).Do() + config.Project, region, addr).Do() if err != nil { return fmt.Errorf("Error creating address: %s", err) } @@ -56,7 +70,7 @@ func resourceComputeAddressCreate(d *schema.ResourceData, meta interface{}) erro Service: config.clientCompute, Op: op, Project: config.Project, - Region: config.Region, + Region: region, Type: OperationWaitRegion, } state := w.Conf() @@ -81,8 +95,10 @@ func resourceComputeAddressCreate(d *schema.ResourceData, meta interface{}) erro func resourceComputeAddressRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) + region := getOptionalRegion(d, config) + addr, err := config.clientCompute.Addresses.Get( - config.Project, config.Region, d.Id()).Do() + config.Project, region, d.Id()).Do() if err != nil { if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { // The resource doesn't exist anymore @@ -103,10 +119,11 @@ func resourceComputeAddressRead(d *schema.ResourceData, meta interface{}) error func resourceComputeAddressDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) + region := getOptionalRegion(d, config) // Delete the address log.Printf("[DEBUG] address delete request") op, err := config.clientCompute.Addresses.Delete( - config.Project, config.Region, d.Id()).Do() + config.Project, region, d.Id()).Do() if err != nil { return fmt.Errorf("Error deleting address: %s", err) } @@ -116,7 +133,7 @@ func resourceComputeAddressDelete(d *schema.ResourceData, meta interface{}) erro Service: config.clientCompute, Op: op, Project: config.Project, - Region: config.Region, + Region: region, Type: OperationWaitRegion, } state := w.Conf() diff --git a/builtin/providers/google/resource_compute_forwarding_rule.go b/builtin/providers/google/resource_compute_forwarding_rule.go index 8138ead83..0c905ead7 100644 --- a/builtin/providers/google/resource_compute_forwarding_rule.go +++ b/builtin/providers/google/resource_compute_forwarding_rule.go @@ -50,6 +50,12 @@ func resourceComputeForwardingRule() *schema.Resource { ForceNew: true, }, + "region": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "self_link": &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -67,6 +73,8 @@ func resourceComputeForwardingRule() *schema.Resource { func resourceComputeForwardingRuleCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) + region := getOptionalRegion(d, config) + frule := &compute.ForwardingRule{ IPAddress: d.Get("ip_address").(string), IPProtocol: d.Get("ip_protocol").(string), @@ -78,7 +86,7 @@ func resourceComputeForwardingRuleCreate(d *schema.ResourceData, meta interface{ log.Printf("[DEBUG] ForwardingRule insert request: %#v", frule) op, err := config.clientCompute.ForwardingRules.Insert( - config.Project, config.Region, frule).Do() + config.Project, region, frule).Do() if err != nil { return fmt.Errorf("Error creating ForwardingRule: %s", err) } @@ -90,7 +98,7 @@ func resourceComputeForwardingRuleCreate(d *schema.ResourceData, meta interface{ w := &OperationWaiter{ Service: config.clientCompute, Op: op, - Region: config.Region, + Region: region, Project: config.Project, Type: OperationWaitRegion, } @@ -116,13 +124,15 @@ func resourceComputeForwardingRuleCreate(d *schema.ResourceData, meta interface{ func resourceComputeForwardingRuleUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) + region := getOptionalRegion(d, config) + d.Partial(true) if d.HasChange("target") { target_name := d.Get("target").(string) target_ref := &compute.TargetReference{Target: target_name} op, err := config.clientCompute.ForwardingRules.SetTarget( - config.Project, config.Region, d.Id(), target_ref).Do() + config.Project, region, d.Id(), target_ref).Do() if err != nil { return fmt.Errorf("Error updating target: %s", err) } @@ -131,7 +141,7 @@ func resourceComputeForwardingRuleUpdate(d *schema.ResourceData, meta interface{ w := &OperationWaiter{ Service: config.clientCompute, Op: op, - Region: config.Region, + Region: region, Project: config.Project, Type: OperationWaitRegion, } @@ -161,8 +171,10 @@ func resourceComputeForwardingRuleUpdate(d *schema.ResourceData, meta interface{ func resourceComputeForwardingRuleRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) + region := getOptionalRegion(d, config) + frule, err := config.clientCompute.ForwardingRules.Get( - config.Project, config.Region, d.Id()).Do() + config.Project, region, d.Id()).Do() if err != nil { if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { // The resource doesn't exist anymore @@ -184,10 +196,12 @@ func resourceComputeForwardingRuleRead(d *schema.ResourceData, meta interface{}) func resourceComputeForwardingRuleDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) + region := getOptionalRegion(d, config) + // Delete the ForwardingRule log.Printf("[DEBUG] ForwardingRule delete request") op, err := config.clientCompute.ForwardingRules.Delete( - config.Project, config.Region, d.Id()).Do() + config.Project, region, d.Id()).Do() if err != nil { return fmt.Errorf("Error deleting ForwardingRule: %s", err) } @@ -196,7 +210,7 @@ func resourceComputeForwardingRuleDelete(d *schema.ResourceData, meta interface{ w := &OperationWaiter{ Service: config.clientCompute, Op: op, - Region: config.Region, + Region: region, Project: config.Project, Type: OperationWaitRegion, } diff --git a/builtin/providers/google/resource_compute_route.go b/builtin/providers/google/resource_compute_route.go index 1f52a2807..53176c871 100644 --- a/builtin/providers/google/resource_compute_route.go +++ b/builtin/providers/google/resource_compute_route.go @@ -66,6 +66,12 @@ func resourceComputeRoute() *schema.Resource { ForceNew: true, }, + "next_hop_vpn_tunnel": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "priority": &schema.Schema{ Type: schema.TypeInt, Required: true, @@ -101,13 +107,17 @@ func resourceComputeRouteCreate(d *schema.ResourceData, meta interface{}) error } // Next hop data - var nextHopInstance, nextHopIp, nextHopNetwork, nextHopGateway string + var nextHopInstance, nextHopIp, nextHopNetwork, nextHopGateway, + nextHopVpnTunnel string if v, ok := d.GetOk("next_hop_ip"); ok { nextHopIp = v.(string) } if v, ok := d.GetOk("next_hop_gateway"); ok { nextHopGateway = v.(string) } + if v, ok := d.GetOk("next_hop_vpn_tunnel"); ok { + nextHopVpnTunnel = v.(string) + } if v, ok := d.GetOk("next_hop_instance"); ok { nextInstance, err := config.clientCompute.Instances.Get( config.Project, @@ -140,15 +150,16 @@ func resourceComputeRouteCreate(d *schema.ResourceData, meta interface{}) error // Build the route parameter route := &compute.Route{ - Name: d.Get("name").(string), - DestRange: d.Get("dest_range").(string), - Network: network.SelfLink, - NextHopInstance: nextHopInstance, - NextHopIp: nextHopIp, - NextHopNetwork: nextHopNetwork, - NextHopGateway: nextHopGateway, - Priority: int64(d.Get("priority").(int)), - Tags: tags, + Name: d.Get("name").(string), + DestRange: d.Get("dest_range").(string), + Network: network.SelfLink, + NextHopInstance: nextHopInstance, + NextHopVpnTunnel: nextHopVpnTunnel, + NextHopIp: nextHopIp, + NextHopNetwork: nextHopNetwork, + NextHopGateway: nextHopGateway, + Priority: int64(d.Get("priority").(int)), + Tags: tags, } log.Printf("[DEBUG] Route insert request: %#v", route) op, err := config.clientCompute.Routes.Insert( diff --git a/builtin/providers/google/resource_compute_vpn_gateway.go b/builtin/providers/google/resource_compute_vpn_gateway.go new file mode 100644 index 000000000..01a6c4b94 --- /dev/null +++ b/builtin/providers/google/resource_compute_vpn_gateway.go @@ -0,0 +1,120 @@ +package google + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + + "google.golang.org/api/compute/v1" +) + +func resourceComputeVpnGateway() *schema.Resource { + return &schema.Resource{ + // Unfortunately, the VPNGatewayService does not support update + // operations. This is why everything is marked forcenew + Create: resourceComputeVpnGatewayCreate, + Read: resourceComputeVpnGatewayRead, + Delete: resourceComputeVpnGatewayDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "network": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "region": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "self_link": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceComputeVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + name := d.Get("name").(string) + network := d.Get("network").(string) + region := getOptionalRegion(d, config) + project := config.Project + + vpnGatewaysService := compute.NewTargetVpnGatewaysService(config.clientCompute) + + vpnGateway := &compute.TargetVpnGateway{ + Name: name, + Network: network, + } + + if v, ok := d.GetOk("description"); ok { + vpnGateway.Description = v.(string) + } + + op, err := vpnGatewaysService.Insert(project, region, vpnGateway).Do() + if err != nil { + return fmt.Errorf("Error Inserting VPN Gateway %s into network %s: %s", name, network, err) + } + + err = resourceOperationWaitRegion(config, op, region, "Inserting VPN Gateway") + if err != nil { + return fmt.Errorf("Error Waiting to Insert VPN Gateway %s into network %s: %s", name, network, err) + } + + return resourceComputeVpnGatewayRead(d, meta) +} + +func resourceComputeVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + name := d.Get("name").(string) + region := d.Get("region").(string) + project := config.Project + + vpnGatewaysService := compute.NewTargetVpnGatewaysService(config.clientCompute) + vpnGateway, err := vpnGatewaysService.Get(project, region, name).Do() + + if err != nil { + return fmt.Errorf("Error Reading VPN Gateway %s: %s", name, err) + } + + d.Set("self_link", vpnGateway.SelfLink) + d.SetId(name) + + return nil +} + +func resourceComputeVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + name := d.Get("name").(string) + region := d.Get("region").(string) + project := config.Project + + vpnGatewaysService := compute.NewTargetVpnGatewaysService(config.clientCompute) + + op, err := vpnGatewaysService.Delete(project, region, name).Do() + if err != nil { + return fmt.Errorf("Error Reading VPN Gateway %s: %s", name, err) + } + + err = resourceOperationWaitRegion(config, op, region, "Deleting VPN Gateway") + if err != nil { + return fmt.Errorf("Error Waiting to Delete VPN Gateway %s: %s", name, err) + } + + return nil +} diff --git a/builtin/providers/google/resource_compute_vpn_gateway_test.go b/builtin/providers/google/resource_compute_vpn_gateway_test.go new file mode 100644 index 000000000..1d6270423 --- /dev/null +++ b/builtin/providers/google/resource_compute_vpn_gateway_test.go @@ -0,0 +1,91 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "google.golang.org/api/compute/v1" +) + +func TestAccComputeVpnGateway_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeVpnGatewayDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeVpnGateway_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeVpnGatewayExists( + "google_compute_vpn_gateway.foobar"), + ), + }, + }, + }) +} + +func testAccCheckComputeVpnGatewayDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + project := config.Project + + vpnGatewaysService := compute.NewTargetVpnGatewaysService(config.clientCompute) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_network" { + continue + } + + region := rs.Primary.Attributes["region"] + name := rs.Primary.Attributes["name"] + + _, err := vpnGatewaysService.Get(project, region, name).Do() + + if err == nil { + return fmt.Errorf("Error, VPN Gateway %s in region %s still exists", + name, region) + } + } + + return nil +} + +func testAccCheckComputeVpnGatewayExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + name := rs.Primary.Attributes["name"] + region := rs.Primary.Attributes["region"] + project := config.Project + + vpnGatewaysService := compute.NewTargetVpnGatewaysService(config.clientCompute) + _, err := vpnGatewaysService.Get(project, region, name).Do() + + if err != nil { + return fmt.Errorf("Error Reading VPN Gateway %s: %s", name, err) + } + + return nil + } +} + +const testAccComputeVpnGateway_basic = ` +resource "google_compute_network" "foobar" { + name = "tf-test-network" + ipv4_range = "10.0.0.0/16" +} +resource "google_compute_vpn_gateway" "foobar" { + name = "tf-test-vpn-gateway" + network = "${google_compute_network.foobar.self_link}" + region = "us-central1" +} ` diff --git a/builtin/providers/google/resource_compute_vpn_tunnel.go b/builtin/providers/google/resource_compute_vpn_tunnel.go new file mode 100644 index 000000000..55848d546 --- /dev/null +++ b/builtin/providers/google/resource_compute_vpn_tunnel.go @@ -0,0 +1,178 @@ +package google + +import ( + "fmt" + "time" + + "github.com/hashicorp/terraform/helper/schema" + + "google.golang.org/api/compute/v1" +) + +func resourceComputeVpnTunnel() *schema.Resource { + return &schema.Resource{ + // Unfortunately, the VPNTunnelService does not support update + // operations. This is why everything is marked forcenew + Create: resourceComputeVpnTunnelCreate, + Read: resourceComputeVpnTunnelRead, + Delete: resourceComputeVpnTunnelDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "region": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "peer_ip": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "shared_secret": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "target_vpn_gateway": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "ike_version": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 2, + ForceNew: true, + }, + "detailed_status": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "self_link": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceOperationWaitRegion(config *Config, op *compute.Operation, region, activity string) error { + w := &OperationWaiter{ + Service: config.clientCompute, + Op: op, + Project: config.Project, + Type: OperationWaitRegion, + Region: region, + } + + state := w.Conf() + state.Timeout = 2 * time.Minute + state.MinTimeout = 1 * time.Second + opRaw, err := state.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for %s: %s", activity, err) + } + + op = opRaw.(*compute.Operation) + if op.Error != nil { + return OperationError(*op.Error) + } + + return nil +} + +func resourceComputeVpnTunnelCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + name := d.Get("name").(string) + region := getOptionalRegion(d, config) + peerIp := d.Get("peer_ip").(string) + sharedSecret := d.Get("shared_secret").(string) + targetVpnGateway := d.Get("target_vpn_gateway").(string) + ikeVersion := d.Get("ike_version").(int) + project := config.Project + + if ikeVersion < 1 || ikeVersion > 2 { + return fmt.Errorf("Only IKE version 1 or 2 supported, not %d", ikeVersion) + } + + vpnTunnelsService := compute.NewVpnTunnelsService(config.clientCompute) + + vpnTunnel := &compute.VpnTunnel{ + Name: name, + PeerIp: peerIp, + SharedSecret: sharedSecret, + TargetVpnGateway: targetVpnGateway, + IkeVersion: int64(ikeVersion), + } + + if v, ok := d.GetOk("description"); ok { + vpnTunnel.Description = v.(string) + } + + op, err := vpnTunnelsService.Insert(project, region, vpnTunnel).Do() + if err != nil { + return fmt.Errorf("Error Inserting VPN Tunnel %s : %s", name, err) + } + + err = resourceOperationWaitRegion(config, op, region, "Inserting VPN Tunnel") + if err != nil { + return fmt.Errorf("Error Waiting to Insert VPN Tunnel %s: %s", name, err) + } + + return resourceComputeVpnTunnelRead(d, meta) +} + +func resourceComputeVpnTunnelRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + name := d.Get("name").(string) + region := d.Get("region").(string) + project := config.Project + + vpnTunnelsService := compute.NewVpnTunnelsService(config.clientCompute) + + vpnTunnel, err := vpnTunnelsService.Get(project, region, name).Do() + if err != nil { + return fmt.Errorf("Error Reading VPN Tunnel %s: %s", name, err) + } + + d.Set("detailed_status", vpnTunnel.DetailedStatus) + d.Set("self_link", vpnTunnel.SelfLink) + + d.SetId(name) + + return nil +} + +func resourceComputeVpnTunnelDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + name := d.Get("name").(string) + region := d.Get("region").(string) + project := config.Project + + vpnTunnelsService := compute.NewVpnTunnelsService(config.clientCompute) + + op, err := vpnTunnelsService.Delete(project, region, name).Do() + if err != nil { + return fmt.Errorf("Error Reading VPN Tunnel %s: %s", name, err) + } + + err = resourceOperationWaitRegion(config, op, region, "Deleting VPN Tunnel") + if err != nil { + return fmt.Errorf("Error Waiting to Delete VPN Tunnel %s: %s", name, err) + } + + return nil +} diff --git a/builtin/providers/google/resource_compute_vpn_tunnel_test.go b/builtin/providers/google/resource_compute_vpn_tunnel_test.go new file mode 100644 index 000000000..4bb666879 --- /dev/null +++ b/builtin/providers/google/resource_compute_vpn_tunnel_test.go @@ -0,0 +1,125 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "google.golang.org/api/compute/v1" +) + +func TestAccComputeVpnTunnel_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeVpnTunnelDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeVpnTunnel_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeVpnTunnelExists( + "google_compute_vpn_tunnel.foobar"), + ), + }, + }, + }) +} + +func testAccCheckComputeVpnTunnelDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + project := config.Project + + vpnTunnelsService := compute.NewVpnTunnelsService(config.clientCompute) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_network" { + continue + } + + region := rs.Primary.Attributes["region"] + name := rs.Primary.Attributes["name"] + + _, err := vpnTunnelsService.Get(project, region, name).Do() + + if err == nil { + return fmt.Errorf("Error, VPN Tunnel %s in region %s still exists", + name, region) + } + } + + return nil +} + +func testAccCheckComputeVpnTunnelExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + name := rs.Primary.Attributes["name"] + region := rs.Primary.Attributes["region"] + project := config.Project + + vpnTunnelsService := compute.NewVpnTunnelsService(config.clientCompute) + _, err := vpnTunnelsService.Get(project, region, name).Do() + + if err != nil { + return fmt.Errorf("Error Reading VPN Tunnel %s: %s", name, err) + } + + return nil + } +} + +const testAccComputeVpnTunnel_basic = ` +resource "google_compute_network" "foobar" { + name = "tf-test-network" + ipv4_range = "10.0.0.0/16" +} +resource "google_compute_address" "foobar" { + name = "tf-test-static-ip" + region = "us-central1" +} +resource "google_compute_vpn_gateway" "foobar" { + name = "tf-test-vpn-gateway" + network = "${google_compute_network.foobar.self_link}" + region = "${google_compute_address.foobar.region}" +} +resource "google_compute_forwarding_rule" "foobar_esp" { + name = "tf-test-fr-esp" + region = "${google_compute_vpn_gateway.foobar.region}" + ip_protocol = "ESP" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" +} +resource "google_compute_forwarding_rule" "foobar_udp500" { + name = "tf-test-fr-udp500" + region = "${google_compute_forwarding_rule.foobar_esp.region}" + ip_protocol = "UDP" + port_range = "500" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" +} +resource "google_compute_forwarding_rule" "foobar_udp4500" { + name = "tf-test-fr-udp4500" + region = "${google_compute_forwarding_rule.foobar_udp500.region}" + ip_protocol = "UDP" + port_range = "4500" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" +} +resource "google_compute_vpn_tunnel" "foobar" { + name = "tf-test-vpn-tunnel" + region = "${google_compute_forwarding_rule.foobar_udp4500.region}" + target_vpn_gateway = "${google_compute_vpn_gateway.foobar.self_link}" + shared_secret = "unguessable" + peer_ip = "0.0.0.0" +}` diff --git a/examples/gce-vpn/README.md b/examples/gce-vpn/README.md new file mode 100644 index 000000000..db7c6beb0 --- /dev/null +++ b/examples/gce-vpn/README.md @@ -0,0 +1,17 @@ +# Google Compute Engine VPN Example + +This example joins two GCE networks via VPN. The firewall rules have been set up +so that you can create an instance in each network and have them communicate +using their internal IP addresses. + +See this [example](https://cloud.google.com/compute/docs/vpn) for more +information. + +Run this example using + +``` +terraform apply \ + -var="region1=us-central1" \ + -var="region2=europe-west1" \ + -var="project=my-project-id-123" +``` diff --git a/examples/gce-vpn/variables.tf b/examples/gce-vpn/variables.tf new file mode 100644 index 000000000..20ada06bb --- /dev/null +++ b/examples/gce-vpn/variables.tf @@ -0,0 +1,11 @@ +variable "project" { + description = "Your project name" +} + +variable "region1" { + description = "The desired region for the first network & VPN and project" +} + +variable "region2" { + description = "The desired region for the second network & VPN" +} diff --git a/examples/gce-vpn/vpn.tf b/examples/gce-vpn/vpn.tf new file mode 100644 index 000000000..2693c1001 --- /dev/null +++ b/examples/gce-vpn/vpn.tf @@ -0,0 +1,172 @@ +# An example of how to connect two GCE networks with a VPN +provider "google" { + account_file = "${file("~/gce/account.json")}" + project = "${var.project}" + region = "${var.region1}" +} + +# Create the two networks we want to join. They must have seperate, internal +# ranges. +resource "google_compute_network" "network1" { + name = "network1" + ipv4_range = "10.120.0.0/16" +} + +resource "google_compute_network" "network2" { + name = "network2" + ipv4_range = "10.121.0.0/16" +} + +# Attach a VPN gateway to each network. +resource "google_compute_vpn_gateway" "target_gateway1" { + name = "vpn1" + network = "${google_compute_network.network1.self_link}" + region = "${var.region1}" +} + +resource "google_compute_vpn_gateway" "target_gateway2" { + name = "vpn2" + network = "${google_compute_network.network2.self_link}" + region = "${var.region2}" +} + +# Create an outward facing static IP for each VPN that will be used by the +# other VPN to connect. +resource "google_compute_address" "vpn_static_ip1" { + name = "vpn-static-ip1" + region = "${var.region1}" +} + +resource "google_compute_address" "vpn_static_ip2" { + name = "vpn-static-ip2" + region = "${var.region2}" +} + +# Forward IPSec traffic coming into our static IP to our VPN gateway. +resource "google_compute_forwarding_rule" "fr1_esp" { + name = "fr1-esp" + region = "${var.region1}" + ip_protocol = "ESP" + ip_address = "${google_compute_address.vpn_static_ip1.address}" + target = "${google_compute_vpn_gateway.target_gateway1.self_link}" +} + +resource "google_compute_forwarding_rule" "fr2_esp" { + name = "fr2-esp" + region = "${var.region2}" + ip_protocol = "ESP" + ip_address = "${google_compute_address.vpn_static_ip2.address}" + target = "${google_compute_vpn_gateway.target_gateway2.self_link}" +} + +# The following two sets of forwarding rules are used as a part of the IPSec +# protocol +resource "google_compute_forwarding_rule" "fr1_udp500" { + name = "fr1-udp500" + region = "${var.region1}" + ip_protocol = "UDP" + port_range = "500" + ip_address = "${google_compute_address.vpn_static_ip1.address}" + target = "${google_compute_vpn_gateway.target_gateway1.self_link}" +} + +resource "google_compute_forwarding_rule" "fr2_udp500" { + name = "fr2-udp500" + region = "${var.region2}" + ip_protocol = "UDP" + port_range = "500" + ip_address = "${google_compute_address.vpn_static_ip2.address}" + target = "${google_compute_vpn_gateway.target_gateway2.self_link}" +} + +resource "google_compute_forwarding_rule" "fr1_udp4500" { + name = "fr1-udp4500" + region = "${var.region1}" + ip_protocol = "UDP" + port_range = "4500" + ip_address = "${google_compute_address.vpn_static_ip1.address}" + target = "${google_compute_vpn_gateway.target_gateway1.self_link}" +} + +resource "google_compute_forwarding_rule" "fr2_udp4500" { + name = "fr2-udp4500" + region = "${var.region2}" + ip_protocol = "UDP" + port_range = "4500" + ip_address = "${google_compute_address.vpn_static_ip2.address}" + target = "${google_compute_vpn_gateway.target_gateway2.self_link}" +} + +# Each tunnel is responsible for encrypting and decrypting traffic exiting +# and leaving it's associated gateway +resource "google_compute_vpn_tunnel" "tunnel1" { + name = "tunnel1" + region = "${var.region1}" + peer_ip = "${google_compute_address.vpn_static_ip2.address}" + shared_secret = "a secret message" + target_vpn_gateway = "${google_compute_vpn_gateway.target_gateway1.self_link}" + depends_on = ["google_compute_forwarding_rule.fr1_udp500", + "google_compute_forwarding_rule.fr1_udp4500", + "google_compute_forwarding_rule.fr1_esp"] +} + +resource "google_compute_vpn_tunnel" "tunnel2" { + name = "tunnel2" + region = "${var.region2}" + peer_ip = "${google_compute_address.vpn_static_ip1.address}" + shared_secret = "a secret message" + target_vpn_gateway = "${google_compute_vpn_gateway.target_gateway2.self_link}" + depends_on = ["google_compute_forwarding_rule.fr2_udp500", + "google_compute_forwarding_rule.fr2_udp4500", + "google_compute_forwarding_rule.fr2_esp"] +} + +# Each route tells the associated network to send all traffic in the dest_range +# through the VPN tunnel +resource "google_compute_route" "route1" { + name = "route1" + network = "${google_compute_network.network1.name}" + next_hop_vpn_tunnel = "${google_compute_vpn_tunnel.tunnel1.self_link}" + dest_range = "${google_compute_network.network2.ipv4_range}" + priority = 1000 +} + +resource "google_compute_route" "route2" { + name = "route2" + network = "${google_compute_network.network2.name}" + next_hop_vpn_tunnel = "${google_compute_vpn_tunnel.tunnel2.self_link}" + dest_range = "${google_compute_network.network1.ipv4_range}" + priority = 1000 +} + +# We want to allow the two networks to communicate, so we need to unblock +# them in the firewall +resource "google_compute_firewall" "network1-allow-network1" { + name = "network1-allow-network1" + network = "${google_compute_network.network1.name}" + source_ranges = ["${google_compute_network.network1.ipv4_range}"] + allow { + protocol = "tcp" + } + allow { + protocol = "udp" + } + allow { + protocol = "icmp" + } +} + +resource "google_compute_firewall" "network1-allow-network2" { + name = "network1-allow-network2" + network = "${google_compute_network.network1.name}" + source_ranges = ["${google_compute_network.network2.ipv4_range}"] + allow { + protocol = "tcp" + } + allow { + protocol = "udp" + } + allow { + protocol = "icmp" + } +} diff --git a/website/source/docs/providers/google/r/compute_address.html.markdown b/website/source/docs/providers/google/r/compute_address.html.markdown index c0551c11f..2ffd62262 100644 --- a/website/source/docs/providers/google/r/compute_address.html.markdown +++ b/website/source/docs/providers/google/r/compute_address.html.markdown @@ -27,6 +27,8 @@ The following arguments are supported: * `name` - (Required) A unique name for the resource, required by GCE. Changing this forces a new resource to be created. +* `region` - (Optional) The Region in which the created address should reside. + If it is not provided, the provider region is used. ## Attributes Reference @@ -35,3 +37,4 @@ The following attributes are exported: * `name` - The name of the resource. * `address` - The IP address that was allocated. * `self_link` - The URI of the created resource. +* `region` - The Region in which the created address does reside. diff --git a/website/source/docs/providers/google/r/compute_route.html.markdown b/website/source/docs/providers/google/r/compute_route.html.markdown index 318494b83..946886371 100644 --- a/website/source/docs/providers/google/r/compute_route.html.markdown +++ b/website/source/docs/providers/google/r/compute_route.html.markdown @@ -54,6 +54,9 @@ The following arguments are supported: * `next_hop_network` - (Optional) The name of the network to route to if this route is matched. +* `next_hop_vpn_gateway` - (Optional) The name of the VPN to route to if this + route is matched. + * `priority` - (Required) The priority of this route, used to break ties. * `tags` - (Optional) The tags that this route applies to. diff --git a/website/source/docs/providers/google/r/compute_vpn_gateway.html.markdown b/website/source/docs/providers/google/r/compute_vpn_gateway.html.markdown new file mode 100644 index 000000000..0073ecb72 --- /dev/null +++ b/website/source/docs/providers/google/r/compute_vpn_gateway.html.markdown @@ -0,0 +1,103 @@ +--- +layout: "google" +page_title: "Google: google_compute_vpn_gateway" +sidebar_current: "docs-google-resource-vpn-gateway" +description: |- + Manages a VPN Gateway in the GCE network +--- + +# google\_compute\_vpn\_gateway + +Manages a VPN Gateway in the GCE network. For more info, read the +[documentation](https://cloud.google.com/compute/docs/vpn). + + +## Example Usage + +``` +resource "google_compute_network" "network1" { + name = "network1" + ipv4_range = "10.120.0.0/16" +} + +resource "google_compute_vpn_gateway" "target_gateway" { + name = "vpn1" + network = "${google_compute_network.network1.self_link}" + region = "${var.region}" +} + +resource "google_compute_address" "vpn_static_ip" { + name = "vpn-static-ip" + region = "${var.region}" +} + +resource "google_compute_forwarding_rule" "fr_esp" { + name = "fr-esp" + region = "${var.region}" + ip_protocol = "ESP" + ip_address = "${google_compute_address.vpn_static_ip.address}" + target = "${google_compute_vpn_gateway.target_gateway.self_link}" +} + +resource "google_compute_forwarding_rule" "fr_udp500" { + name = "fr-udp500" + region = "${var.region}" + ip_protocol = "UDP" + port_range = "500" + ip_address = "${google_compute_address.vpn_static_ip.address}" + target = "${google_compute_vpn_gateway.target_gateway.self_link}" +} + +resource "google_compute_forwarding_rule" "fr_udp4500" { + name = "fr-udp4500" + region = "${var.region}" + ip_protocol = "UDP" + port_range = "4500" + ip_address = "${google_compute_address.vpn_static_ip.address}" + target = "${google_compute_vpn_gateway.target_gateway.self_link}" +} + +resource "google_compute_vpn_tunnel" "tunnel1" { + name = "tunnel1" + region = "${var.region}" + peer_ip = "15.0.0.120" + shared_secret = "a secret message" + target_vpn_gateway = "${google_compute_vpn_gateway.target_gateway.self_link}" + depends_on = ["google_compute_forwarding_rule.fr_esp", + "google_compute_forwarding_rule.fr_udp500", + "google_compute_forwarding_rule.fr_udp4500"] +} + +resource "google_compute_route" "route1" { + name = "route1" + network = "${google_compute_network.network1.name}" + next_hop_vpn_tunnel = "${google_compute_vpn_tunnel.tunnel1.self_link}" + dest_range = "15.0.0.0/24" + priority = 1000 +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) A unique name for the resource, required by GCE. + Changing this forces a new resource to be created. + +* `description` - (Optional) A description of the resource. + Changing this forces a new resource to be created. + +* `network` - (Required) A link to the network this VPN gateway is accepting + traffic for. + Changing this forces a new resource to be created. + +* `region` - (Optional) The region this gateway should sit in. If not specified, + the project region will be used. Changing this forces a new resource to be + created. + +## Attributes Reference + +The following attributes are exported: + +* `self_link` - A GCE server assigned link to this resource. diff --git a/website/source/docs/providers/google/r/compute_vpn_tunnel.html.markdown b/website/source/docs/providers/google/r/compute_vpn_tunnel.html.markdown new file mode 100644 index 000000000..0df012bcd --- /dev/null +++ b/website/source/docs/providers/google/r/compute_vpn_tunnel.html.markdown @@ -0,0 +1,112 @@ +--- +layout: "google" +page_title: "Google: google_compute_vpn_tunnel" +sidebar_current: "docs-google-resource-vpn-tunnel" +description: |- + Manages a VPN Tunnel to the GCE network +--- + +# google\_compute\_vpn\_tunnel + +Manages a VPN Tunnel to the GCE network. For more info, read the +[documentation](https://cloud.google.com/compute/docs/vpn). + +## Example Usage + +``` +resource "google_compute_network" "network1" { + name = "network1" + ipv4_range = "10.120.0.0/16" +} + +resource "google_compute_vpn_gateway" "target_gateway" { + name = "vpn1" + network = "${google_compute_network.network1.self_link}" + region = "${var.region}" +} + +resource "google_compute_address" "vpn_static_ip" { + name = "vpn-static-ip" + region = "${var.region}" +} + +resource "google_compute_forwarding_rule" "fr_esp" { + name = "fr-esp" + region = "${var.region}" + ip_protocol = "ESP" + ip_address = "${google_compute_address.vpn_static_ip.address}" + target = "${google_compute_vpn_gateway.target_gateway.self_link}" +} + +resource "google_compute_forwarding_rule" "fr_udp500" { + name = "fr-udp500" + region = "${var.region}" + ip_protocol = "UDP" + port_range = "500" + ip_address = "${google_compute_address.vpn_static_ip.address}" + target = "${google_compute_vpn_gateway.target_gateway.self_link}" +} + +resource "google_compute_forwarding_rule" "fr_udp4500" { + name = "fr-udp4500" + region = "${var.region}" + ip_protocol = "UDP" + port_range = "4500" + ip_address = "${google_compute_address.vpn_static_ip.address}" + target = "${google_compute_vpn_gateway.target_gateway.self_link}" +} + +resource "google_compute_vpn_tunnel" "tunnel1" { + name = "tunnel1" + region = "${var.region}" + peer_ip = "15.0.0.120" + shared_secret = "a secret message" + target_vpn_gateway = "${google_compute_vpn_gateway.target_gateway.self_link}" + depends_on = ["google_compute_forwarding_rule.fr_esp", + "google_compute_forwarding_rule.fr_udp500", + "google_compute_forwarding_rule.fr_udp4500"] +} + +resource "google_compute_route" "route1" { + name = "route1" + network = "${google_compute_network.network1.name}" + next_hop_vpn_tunnel = "${google_compute_vpn_tunnel.tunnel1.self_link}" + dest_range = "15.0.0.0/24" + priority = 1000 +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) A unique name for the resource, required by GCE. + Changing this forces a new resource to be created. + +* `description` - (Optional) A description of the resource. + Changing this forces a new resource to be created. + +* `peer_ip` - (Required) The VPN gateway sitting outside of GCE. + Changing this forces a new resource to be created. + +* `region` - (Optional) The region this tunnel should sit in. If not specified, + the project region will be used. Changing this forces a new resource to be + created. + +* `shared_secret` - (Required) A passphrase shared between the two VPN gateways. + Changing this forces a new resource to be created. + +* `target_vpn_gateway` - (Required) A link to the VPN gateway sitting inside GCE. + Changing this forces a new resource to be created. + +* `ike_version` - (Optional) Either version 1 or 2. Default is 2. + Changing this forces a new resource to be created. + +## Attributes Reference + +The following attributes are exported: + +* `self_link` - A GCE server assigned link to this resource. + +* `detailed_status` - Information about the status of the VPN tunnel. diff --git a/website/source/layouts/google.erb b/website/source/layouts/google.erb index 3427727df..46a9a61e1 100644 --- a/website/source/layouts/google.erb +++ b/website/source/layouts/google.erb @@ -69,6 +69,14 @@ google_compute_target_pool +