diff --git a/google/resource_compute_router_peer.go b/google/resource_compute_router_peer.go index 3f3afd0c25e..bc0b4e9e462 100644 --- a/google/resource_compute_router_peer.go +++ b/google/resource_compute_router_peer.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" "google.golang.org/api/compute/v1" "google.golang.org/api/googleapi" ) @@ -16,6 +17,7 @@ func resourceComputeRouterPeer() *schema.Resource { Create: resourceComputeRouterPeerCreate, Read: resourceComputeRouterPeerRead, Delete: resourceComputeRouterPeerDelete, + Update: resourceComputeRouterPeerUpdate, Importer: &schema.ResourceImporter{ State: resourceComputeRouterPeerImportState, }, @@ -55,6 +57,38 @@ func resourceComputeRouterPeer() *schema.Resource { ForceNew: true, }, + "advertise_mode": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"DEFAULT", "CUSTOM", ""}, false), + Default: "DEFAULT", + }, + + "advertised_groups": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "advertised_ip_ranges": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + }, + "range": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "ip_address": { Type: schema.TypeString, Computed: true, @@ -136,6 +170,23 @@ func resourceComputeRouterPeerCreate(d *schema.ResourceData, meta interface{}) e peer.AdvertisedRoutePriority = int64(v.(int)) } + if v, ok := d.GetOk("advertise_mode"); ok { + peer.AdvertiseMode = v.(string) + } + + if v, ok := d.GetOk("advertised_groups"); ok { + agInterface := v.([]interface{}) + agString := make([]string, len(agInterface)) + for i, v2 := range agInterface { + agString[i] = v2.(string) + } + peer.AdvertisedGroups = agString + } + + if ipRanges, err := expandComputeRouterPeerBgpAdvertisedIPRanges(d, config); err == nil { + peer.AdvertisedIpRanges = ipRanges + } + log.Printf("[INFO] Adding peer %s", peerName) peers = append(peers, peer) patchRouter := &compute.Router{ @@ -195,6 +246,9 @@ func resourceComputeRouterPeerRead(d *schema.ResourceData, meta interface{}) err d.Set("peer_ip_address", peer.PeerIpAddress) d.Set("peer_asn", peer.PeerAsn) d.Set("advertised_route_priority", peer.AdvertisedRoutePriority) + d.Set("advertise_mode", peer.AdvertiseMode) + d.Set("advertised_groups", peer.AdvertisedGroups) + d.Set("advertised_ip_ranges", flattenComputeRouterPeerBgpAdvertisedIpRanges(peer.AdvertisedIpRanges, d)) d.Set("ip_address", peer.IpAddress) d.Set("region", region) d.Set("project", project) @@ -207,6 +261,93 @@ func resourceComputeRouterPeerRead(d *schema.ResourceData, meta interface{}) err return nil } +func resourceComputeRouterPeerUpdate(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) + peerName := d.Get("name").(string) + + routerLock := getRouterLockName(region, routerName) + mutexKV.Lock(routerLock) + defer mutexKV.Unlock(routerLock) + + routersService := config.clientCompute.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 peer %s because its router %s/%s is gone", peerName, region, routerName) + d.SetId("") + + return nil + } + + return fmt.Errorf("Error Reading router %s/%s: %s", region, routerName, err) + } + + peers := router.BgpPeers + for _, peer := range peers { + if peer.Name == peerName { + if v, ok := d.GetOk("peer_ip_address"); ok { + peer.PeerIpAddress = v.(string) + } + + if v, ok := d.GetOk("peer_asn"); ok { + peer.PeerAsn = int64(v.(int)) + } + + if v, ok := d.GetOk("advertised_route_priority"); ok { + peer.AdvertisedRoutePriority = int64(v.(int)) + } + + if v, ok := d.GetOk("advertise_mode"); ok { + peer.AdvertiseMode = v.(string) + } + + if v, ok := d.GetOk("advertised_groups"); ok { + agInterface := v.([]interface{}) + agString := make([]string, len(agInterface)) + for i, v2 := range agInterface { + agString[i] = v2.(string) + } + peer.AdvertisedGroups = agString + } + + if ipRanges, err := expandComputeRouterPeerBgpAdvertisedIPRanges(d, config); err == nil { + peer.AdvertisedIpRanges = ipRanges + } + + } + } + + patchRouter := &compute.Router{ + BgpPeers: peers, + } + + log.Printf("[DEBUG] Updating router %s/%s with peers: %+v", region, routerName, peers) + 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, peerName)) + err = computeOperationWait(config.clientCompute, op, project, "Patching router") + if err != nil { + d.SetId("") + return fmt.Errorf("Error waiting to patch router %s/%s: %s", region, routerName, err) + } + + return resourceComputeRouterPeerRead(d, meta) +} + func resourceComputeRouterPeerDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) @@ -240,7 +381,7 @@ func resourceComputeRouterPeerDelete(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error Reading Router %s: %s", routerName, err) } - var newPeers []*compute.RouterBgpPeer = make([]*compute.RouterBgpPeer, 0, len(router.BgpPeers)) + var newPeers = make([]*compute.RouterBgpPeer, 0, len(router.BgpPeers)) for _, peer := range router.BgpPeers { if peer.Name == peerName { continue @@ -292,3 +433,34 @@ func resourceComputeRouterPeerImportState(d *schema.ResourceData, meta interface return []*schema.ResourceData{d}, nil } + +func expandComputeRouterPeerBgpAdvertisedIPRanges(d TerraformResourceData, config *Config) ([]*compute.RouterAdvertisedIpRange, error) { + l := d.Get("advertised_ip_ranges").([]interface{}) + result := []*compute.RouterAdvertisedIpRange{} + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + current := &compute.RouterAdvertisedIpRange{} + current.Range = original["range"].(string) + current.Description = original["description"].(string) + result = append(result, current) + } + return result, nil +} + +func flattenComputeRouterPeerBgpAdvertisedIpRanges(v []*compute.RouterAdvertisedIpRange, d *schema.ResourceData) interface{} { + if v == nil { + return v + } + l := v + transformed := make([]interface{}, 0, len(l)) + for _, e := range l { + transformed = append(transformed, map[string]string{ + "range": e.Range, + "description": e.Description, + }) + } + return transformed +} diff --git a/google/resource_compute_router_peer_test.go b/google/resource_compute_router_peer_test.go index 89ea3a96f28..3201531ebde 100644 --- a/google/resource_compute_router_peer_test.go +++ b/google/resource_compute_router_peer_test.go @@ -37,6 +37,29 @@ func TestAccComputeRouterPeer_basic(t *testing.T) { }) } +func TestAccComputeRouterPeer_customAdvertisement(t *testing.T) { + t.Parallel() + + testId := acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRouterPeerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeRouterPeerCustomAdvertisement(testId), + Check: testAccCheckComputeRouterPeerCustomSetCorrect( + "google_compute_router_peer.foobar"), + }, + { + ResourceName: "google_compute_router_peer.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckComputeRouterPeerDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) @@ -303,3 +326,153 @@ func testAccComputeRouterPeerKeepRouter(testId string) string { } `, testId, testId, testId, testId, testId, testId, testId, testId, testId, testId) } + +func testAccComputeRouterPeerCustomAdvertisement(testId string) string { + return fmt.Sprintf(` + resource "google_compute_network" "foobar" { + name = "router-peer-test-%s" + } + resource "google_compute_subnetwork" "foobar" { + name = "router-peer-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-peer-test-%s" + region = "${google_compute_subnetwork.foobar.region}" + } + resource "google_compute_vpn_gateway" "foobar" { + name = "router-peer-test-%s" + network = "${google_compute_network.foobar.self_link}" + region = "${google_compute_subnetwork.foobar.region}" + } + resource "google_compute_forwarding_rule" "foobar_esp" { + name = "router-peer-test-%s-1" + 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 = "router-peer-test-%s-2" + region = "${google_compute_forwarding_rule.foobar_esp.region}" + ip_protocol = "UDP" + port_range = "500-500" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_forwarding_rule" "foobar_udp4500" { + name = "router-peer-test-%s-3" + region = "${google_compute_forwarding_rule.foobar_udp500.region}" + ip_protocol = "UDP" + port_range = "4500-4500" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_router" "foobar"{ + name = "router-peer-test-%s" + region = "${google_compute_forwarding_rule.foobar_udp500.region}" + network = "${google_compute_network.foobar.self_link}" + bgp { + asn = 64514 + } + } + resource "google_compute_vpn_tunnel" "foobar" { + name = "router-peer-test-%s" + region = "${google_compute_forwarding_rule.foobar_udp4500.region}" + target_vpn_gateway = "${google_compute_vpn_gateway.foobar.self_link}" + shared_secret = "unguessable" + peer_ip = "8.8.8.8" + router = "${google_compute_router.foobar.name}" + } + resource "google_compute_router_interface" "foobar" { + name = "router-peer-test-%s" + router = "${google_compute_router.foobar.name}" + region = "${google_compute_router.foobar.region}" + ip_range = "169.254.3.1/30" + vpn_tunnel = "${google_compute_vpn_tunnel.foobar.name}" + } + resource "google_compute_router_peer" "foobar" { + name = "router-peer-test-%s" + router = "${google_compute_router.foobar.name}" + region = "${google_compute_router.foobar.region}" + peer_ip_address = "169.254.3.2" + peer_asn = 65515 + advertised_route_priority = 100 + interface = "${google_compute_router_interface.foobar.name}" + advertise_mode = "CUSTOM" + advertised_groups = ["ALL_SUBNETS"] + advertised_ip_ranges { + range = "6.7.0.0/16" + description = "test number 1" + } + advertised_ip_ranges { + range = "8.9.0.0/16" + description = "test number 2" + } + } + `, testId, testId, testId, testId, testId, testId, testId, testId, testId, testId, testId) +} + +func testAccCheckComputeRouterPeerCustomSetCorrect(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) + + 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"] + + routersService := config.clientCompute.Routers + router, err := routersService.Get(project, region, routerName).Do() + + if err != nil { + return fmt.Errorf("Error Reading Router %s: %s", routerName, err) + } + + for _, peer := range router.BgpPeers { + + if peer.Name == name { + if peer.AdvertiseMode != "CUSTOM" { + return fmt.Errorf("Advertisement Mode is incorrect") + } + if len(peer.AdvertisedGroups) != 1 { + return fmt.Errorf("Number of advertised groups is incorrect") + } + if peer.AdvertisedGroups[0] != "ALL_SUBNETS" { + return fmt.Errorf("Advertised group is incorrect") + } + if len(peer.AdvertisedIpRanges) != 2 { + return fmt.Errorf("Number of advertised ip ranges is incorrect") + } + if peer.AdvertisedIpRanges[0].Range != "6.7.0.0/16" { + return fmt.Errorf("Advertised ip range value is incorrect") + } + if peer.AdvertisedIpRanges[0].Description != "test number 1" { + return fmt.Errorf("Advertised ip range description is incorrect") + } + return nil + } + } + + return fmt.Errorf("Peer %s not found for router %s", name, router.Name) + } +} diff --git a/website/docs/r/compute_router_peer.html.markdown b/website/docs/r/compute_router_peer.html.markdown index 882506069e1..182b0f8ef28 100644 --- a/website/docs/r/compute_router_peer.html.markdown +++ b/website/docs/r/compute_router_peer.html.markdown @@ -27,6 +27,27 @@ resource "google_compute_router_peer" "foobar" { } ``` +To create a peer with custom advertised ranges: + +```hcl +resource "google_compute_router_peer" "foobar" { + name = "peer-1" + router = "router-1" + region = "us-central1" + peer_ip_address = "169.254.1.2" + peer_asn = 65513 + advertised_route_priority = 100 + interface = "interface-1" + advertise_mode = "CUSTOM" + advertised_groups = ["ALL_SUBNETS"] + + advertised_ip_ranges { + range = "192.168.0.0/16" + description = "example range" + } +} +``` + ## Argument Reference The following arguments are supported: @@ -58,6 +79,31 @@ The following arguments are supported: the project region will be used. Changing this forces a new peer to be created. +* `advertise_mode` - (Optional) User-specified flag to indicate which mode to use for advertisement. + Valid values of this enum field are: DEFAULT, CUSTOM + +* `advertised_groups` - (Optional) User-specified list of prefix groups to advertise in custom mode. + This field can only be populated if advertiseMode is CUSTOM and + is advertised to all peers of the router. These groups will be + advertised in addition to any specified prefixes. Leave this field + blank to advertise no custom groups. + This enum field has the one valid value: ALL_SUBNETS + +* `advertised_ip_ranges` - (Optional) User-specified list of individual IP ranges to advertise in + custom mode. This field can only be populated if advertiseMode + is CUSTOM and is advertised to all peers of the router. These IP + ranges will be advertised in addition to any specified groups. + Leave this field blank to advertise no custom IP ranges. Structure is documented below. + + +The `advertised_ip_ranges` block supports: + +* `range` - (Optional) The IP range to advertise. The value must be a + CIDR-formatted string. + +* `description` - (Optional) User-specified description for the IP range. + + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are