Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_vpn_gateway - support block "bgp_peering_addresses" #9035

Merged
merged 4 commits into from
Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,14 @@ resource "azurerm_vpn_gateway" "test" {
bgp_settings {
asn = 65515
peer_weight = 0

instance_bgp_peering_address {
njuCZ marked this conversation as resolved.
Show resolved Hide resolved
custom_ips = ["169.254.21.5"]
}

instance_bgp_peering_address {
njuCZ marked this conversation as resolved.
Show resolved Hide resolved
custom_ips = ["169.254.21.10"]
}
}
}
`, template, data.RandomInteger)
Expand Down
217 changes: 176 additions & 41 deletions azurerm/internal/services/network/vpn_gateway_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,26 @@ import (
"log"
"time"

"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/validate"

"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-05-01/network"
"github.com/hashicorp/go-azure-helpers/response"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
commonValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/validate"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceArmVPNGateway() *schema.Resource {
return &schema.Resource{
Create: resourceArmVPNGatewayCreateUpdate,
Create: resourceArmVPNGatewayCreate,
Read: resourceArmVPNGatewayRead,
Update: resourceArmVPNGatewayCreateUpdate,
Update: resourceArmVPNGatewayUpdate,
Delete: resourceArmVPNGatewayDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
Expand All @@ -42,6 +42,7 @@ func resourceArmVPNGateway() *schema.Resource {
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
},

Expand All @@ -60,6 +61,7 @@ func resourceArmVPNGateway() *schema.Resource {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"asn": {
Expand All @@ -78,6 +80,47 @@ func resourceArmVPNGateway() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},

"instance_bgp_peering_address": {
njuCZ marked this conversation as resolved.
Show resolved Hide resolved
Type: schema.TypeList,
Optional: true,
Computed: true,
MinItems: 2,
MaxItems: 2,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"custom_ips": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: commonValidate.IPv4Address,
},
},

"ip_configuration_id": {
Type: schema.TypeString,
Computed: true,
},

"default_ips": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},

"tunnel_ips": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
},
},
},
},
},
Expand All @@ -94,25 +137,23 @@ func resourceArmVPNGateway() *schema.Resource {
}
}

func resourceArmVPNGatewayCreateUpdate(d *schema.ResourceData, meta interface{}) error {
func resourceArmVPNGatewayCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Network.VpnGatewaysClient
ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d)
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
defer cancel()

name := d.Get("name").(string)
resourceGroup := d.Get("resource_group_name").(string)

if d.IsNewResource() {
existing, err := client.Get(ctx, resourceGroup, name)
if err != nil {
if !utils.ResponseWasNotFound(existing.Response) {
return fmt.Errorf("Error checking for presence of existing VPN Gateway %q (Resource Group %q): %+v", name, resourceGroup, err)
}
existing, err := client.Get(ctx, resourceGroup, name)
if err != nil {
if !utils.ResponseWasNotFound(existing.Response) {
return fmt.Errorf("Error checking for presence of existing VPN Gateway %q (Resource Group %q): %+v", name, resourceGroup, err)
}
}

if existing.ID != nil && *existing.ID != "" {
return tf.ImportAsExistsError("azurerm_vpn_gateway", *existing.ID)
}
if existing.ID != nil && *existing.ID != "" {
return tf.ImportAsExistsError("azurerm_vpn_gateway", *existing.ID)
}

bgpSettingsRaw := d.Get("bgp_settings").([]interface{})
Expand All @@ -138,33 +179,70 @@ func resourceArmVPNGatewayCreateUpdate(d *schema.ResourceData, meta interface{})
if _, err := client.CreateOrUpdate(ctx, resourceGroup, name, parameters); err != nil {
return fmt.Errorf("Error creating VPN Gateway %q (Resource Group %q): %+v", name, resourceGroup, err)
}

log.Printf("[DEBUG] Waiting for Virtual Hub %q (Resource Group %q) to become available", name, resourceGroup)
stateConf := &resource.StateChangeConf{
Pending: []string{"pending"},
Target: []string{"available"},
Refresh: vpnGatewayWaitForCreatedRefreshFunc(ctx, client, resourceGroup, name),
Delay: 30 * time.Second,
PollInterval: 10 * time.Second,
ContinuousTargetOccurence: 3,
if err := waitForCompletion(d, ctx, client, resourceGroup, name); err != nil {
return err
}

if d.IsNewResource() {
stateConf.Timeout = d.Timeout(schema.TimeoutCreate)
} else {
stateConf.Timeout = d.Timeout(schema.TimeoutUpdate)
resp, err := client.Get(ctx, resourceGroup, name)
if err != nil {
return fmt.Errorf("Error retrieving VPN Gateway %q (Resource Group %q): %+v", name, resourceGroup, err)
}

if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf("Error waiting for creation of Virtual Hub %q (Resource Group %q): %+v", name, resourceGroup, err)
// `vpnGatewayParameters.Properties.bgpSettings.bgpPeeringAddress` customer cannot provide this field during create. This will be set with default value once gateway is created.
// it could only be updated
if len(bgpSettingsRaw) > 0 {
val := bgpSettingsRaw[0].(map[string]interface{})
input := val["instance_bgp_peering_address"].([]interface{})
if len(input) > 0 {
if err := expandVPNGatewayIPConfigurationBgpPeeringAddress(resp.VpnGatewayProperties.BgpSettings.BgpPeeringAddresses, input); err != nil {
return err
}
if _, err := client.CreateOrUpdate(ctx, resourceGroup, name, resp); err != nil {
return fmt.Errorf("creating VPN Gateway %q (Resource Group %q): %+v", name, resourceGroup, err)
}
if err := waitForCompletion(d, ctx, client, resourceGroup, name); err != nil {
return err
}
}
}

resp, err := client.Get(ctx, resourceGroup, name)
d.SetId(*resp.ID)

return resourceArmVPNGatewayRead(d, meta)
}

func resourceArmVPNGatewayUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Network.VpnGatewaysClient
ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d)
defer cancel()

name := d.Get("name").(string)
resourceGroup := d.Get("resource_group_name").(string)

existing, err := client.Get(ctx, resourceGroup, name)
if err != nil {
return fmt.Errorf("Error retrieving VPN Gateway %q (Resource Group %q): %+v", name, resourceGroup, err)
return fmt.Errorf("retrieving for presence of existing VPN Gateway %q (Resource Group %q): %+v", name, resourceGroup, err)
}

d.SetId(*resp.ID)
if d.HasChange("tags") {
existing.Tags = tags.Expand(d.Get("tags").(map[string]interface{}))
}
if d.HasChange("bgp_settings.0.instance_bgp_peering_address") {
bgpSettingsRaw := d.Get("bgp_settings").([]interface{})
if len(bgpSettingsRaw) > 0 {
val := bgpSettingsRaw[0].(map[string]interface{})
if err := expandVPNGatewayIPConfigurationBgpPeeringAddress(existing.VpnGatewayProperties.BgpSettings.BgpPeeringAddresses, val["instance_bgp_peering_address"].([]interface{})); err != nil {
return err
}
}
}

if _, err := client.CreateOrUpdate(ctx, resourceGroup, name, existing); err != nil {
return fmt.Errorf("creating VPN Gateway %q (Resource Group %q): %+v", name, resourceGroup, err)
}
if err := waitForCompletion(d, ctx, client, resourceGroup, name); err != nil {
return err
}

return resourceArmVPNGatewayRead(d, meta)
}
Expand Down Expand Up @@ -248,20 +326,55 @@ func resourceArmVPNGatewayDelete(d *schema.ResourceData, meta interface{}) error
return nil
}

func waitForCompletion(d *schema.ResourceData, ctx context.Context, client *network.VpnGatewaysClient, resourceGroup, name string) error {
log.Printf("[DEBUG] Waiting for Virtual Hub %q (Resource Group %q) to become available", name, resourceGroup)
stateConf := &resource.StateChangeConf{
Pending: []string{"pending"},
Target: []string{"available"},
Refresh: vpnGatewayWaitForCreatedRefreshFunc(ctx, client, resourceGroup, name),
Delay: 30 * time.Second,
PollInterval: 10 * time.Second,
ContinuousTargetOccurence: 3,
}

if d.IsNewResource() {
stateConf.Timeout = d.Timeout(schema.TimeoutCreate)
} else {
stateConf.Timeout = d.Timeout(schema.TimeoutUpdate)
}

if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf("waiting for creation of Virtual Hub %q (Resource Group %q): %+v", name, resourceGroup, err)
}

return nil
}

func expandVPNGatewayBGPSettings(input []interface{}) *network.BgpSettings {
if len(input) == 0 {
return nil
}

val := input[0].(map[string]interface{})
return &network.BgpSettings{
Asn: utils.Int64(int64(val["asn"].(int))),
PeerWeight: utils.Int32(int32(val["peer_weight"].(int))),
}
}

asn := val["asn"].(int)
peerWeight := val["peer_weight"].(int)
func expandVPNGatewayIPConfigurationBgpPeeringAddress(ipConfigurationBgpPeeringAddress *[]network.IPConfigurationBgpPeeringAddress, input []interface{}) error {
if len(input) == 0 || ipConfigurationBgpPeeringAddress == nil {
return nil
}
if len(input) != 2 || len(*ipConfigurationBgpPeeringAddress) != 2 {
return fmt.Errorf("the size of block `instance_bgp_peering_address` must be 2")
}

return &network.BgpSettings{
Asn: utils.Int64(int64(asn)),
PeerWeight: utils.Int32(int32(peerWeight)),
for i, v := range input {
val := v.(map[string]interface{})
(*ipConfigurationBgpPeeringAddress)[i].CustomBgpIPAddresses = utils.ExpandStringSlice(val["custom_ips"].(*schema.Set).List())
}
return nil
}

func flattenVPNGatewayBGPSettings(input *network.BgpSettings) []interface{} {
Expand All @@ -286,13 +399,35 @@ func flattenVPNGatewayBGPSettings(input *network.BgpSettings) []interface{} {

return []interface{}{
map[string]interface{}{
"asn": asn,
"bgp_peering_address": bgpPeeringAddress,
"peer_weight": peerWeight,
"asn": asn,
"bgp_peering_address": bgpPeeringAddress,
"instance_bgp_peering_address": flattenVPNGatewayIPConfigurationBgpPeeringAddress(input.BgpPeeringAddresses),
"peer_weight": peerWeight,
},
}
}

func flattenVPNGatewayIPConfigurationBgpPeeringAddress(input *[]network.IPConfigurationBgpPeeringAddress) []interface{} {
if input == nil {
return []interface{}{}
}
result := make([]interface{}, 0)
for _, v := range *input {
ipConfigurationID := ""
if v.IpconfigurationID != nil {
ipConfigurationID = *v.IpconfigurationID
}

result = append(result, map[string]interface{}{
"ip_configuration_id": ipConfigurationID,
"custom_ips": utils.FlattenStringSlice(v.CustomBgpIPAddresses),
"default_ips": utils.FlattenStringSlice(v.DefaultBgpIPAddresses),
"tunnel_ips": utils.FlattenStringSlice(v.TunnelIPAddresses),
})
}
return result
}

func vpnGatewayWaitForCreatedRefreshFunc(ctx context.Context, client *network.VpnGatewaysClient, resourceGroup, name string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
log.Printf("[DEBUG] Checking to see if VPN Gateway %q (Resource Group %q) has finished provisioning..", name, resourceGroup)
Expand Down
20 changes: 20 additions & 0 deletions website/docs/r/vpn_gateway.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ A `bgp_settings` block supports the following:

* `peer_weight` - (Required) The weight added to Routes learned from this BGP Speaker. Changing this forces a new resource to be created.

* `instance_bgp_peering_address` - (Optional) One or more `instance_bgp_peering_address` block as defined below.

---

A `instance_bgp_peering_address` block supports the following:

* `custom_ips` - (Required) The list of custom BGP peering addresses which belong to pre-defined IP configuration.
njuCZ marked this conversation as resolved.
Show resolved Hide resolved

## Attributes Reference

In addition to the arguments above, the following attributes are exported:
Expand All @@ -89,6 +97,18 @@ A `bgp_settings` block exports the following:

* `bgp_peering_address` - The Address which should be used for the BGP Peering.

* `instance_bgp_peering_address` - One or more `instance_bgp_peering_address` block as defined below.

---

A `instance_bgp_peering_address` block exports the following:

* `ip_configuration_id` - The pre-defined id of VPN Gateway Ip Configuration.

* `default_ips` - The list of default BGP peering addresses which belong to the pre-defined VPN Gateway IP configuration.

* `tunnel_ips` - The list of tunnel public IP addresses which belong to the pre-defined VPN Gateway IP configuration.

## Timeouts

The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions:
Expand Down