diff --git a/azurerm/import_arm_virtual_network_gateway_connection_test.go b/azurerm/import_arm_virtual_network_gateway_connection_test.go new file mode 100644 index 000000000000..074004b12f36 --- /dev/null +++ b/azurerm/import_arm_virtual_network_gateway_connection_test.go @@ -0,0 +1,32 @@ +package azurerm + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAzureRMVirtualNetworkGatewayConnection_importSiteToSite(t *testing.T) { + resourceName := "azurerm_virtual_network_gateway_connection.test" + + ri := acctest.RandInt() + config := testAccAzureRMVirtualNetworkGatewayConnection_sitetosite(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualNetworkGatewayConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: config, + }, + + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/azurerm/import_arm_virtual_network_gateway_test.go b/azurerm/import_arm_virtual_network_gateway_test.go new file mode 100644 index 000000000000..b0f2df81a12a --- /dev/null +++ b/azurerm/import_arm_virtual_network_gateway_test.go @@ -0,0 +1,32 @@ +package azurerm + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAzureRMVirtualNetworkGateway_importBasic(t *testing.T) { + resourceName := "azurerm_virtual_network_gateway.test" + + ri := acctest.RandInt() + config := testAccAzureRMVirtualNetworkGateway_basic(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: config, + }, + + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/azurerm/provider.go b/azurerm/provider.go index 339fa46b4f59..c3a86303384b 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -86,98 +86,100 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "azurerm_application_gateway": resourceArmApplicationGateway(), - "azurerm_application_insights": resourceArmApplicationInsights(), - "azurerm_app_service": resourceArmAppService(), - "azurerm_app_service_plan": resourceArmAppServicePlan(), - "azurerm_automation_account": resourceArmAutomationAccount(), - "azurerm_automation_credential": resourceArmAutomationCredential(), - "azurerm_automation_runbook": resourceArmAutomationRunbook(), - "azurerm_automation_schedule": resourceArmAutomationSchedule(), - "azurerm_availability_set": resourceArmAvailabilitySet(), - "azurerm_cdn_endpoint": resourceArmCdnEndpoint(), - "azurerm_cdn_profile": resourceArmCdnProfile(), - "azurerm_container_registry": resourceArmContainerRegistry(), - "azurerm_container_service": resourceArmContainerService(), - "azurerm_container_group": resourceArmContainerGroup(), - "azurerm_cosmosdb_account": resourceArmCosmosDBAccount(), - "azurerm_dns_a_record": resourceArmDnsARecord(), - "azurerm_dns_aaaa_record": resourceArmDnsAAAARecord(), - "azurerm_dns_cname_record": resourceArmDnsCNameRecord(), - "azurerm_dns_mx_record": resourceArmDnsMxRecord(), - "azurerm_dns_ns_record": resourceArmDnsNsRecord(), - "azurerm_dns_ptr_record": resourceArmDnsPtrRecord(), - "azurerm_dns_srv_record": resourceArmDnsSrvRecord(), - "azurerm_dns_txt_record": resourceArmDnsTxtRecord(), - "azurerm_dns_zone": resourceArmDnsZone(), - "azurerm_eventgrid_topic": resourceArmEventGridTopic(), - "azurerm_eventhub": resourceArmEventHub(), - "azurerm_eventhub_authorization_rule": resourceArmEventHubAuthorizationRule(), - "azurerm_eventhub_consumer_group": resourceArmEventHubConsumerGroup(), - "azurerm_eventhub_namespace": resourceArmEventHubNamespace(), - "azurerm_express_route_circuit": resourceArmExpressRouteCircuit(), - "azurerm_function_app": resourceArmFunctionApp(), - "azurerm_image": resourceArmImage(), - "azurerm_key_vault": resourceArmKeyVault(), - "azurerm_key_vault_certificate": resourceArmKeyVaultCertificate(), - "azurerm_key_vault_key": resourceArmKeyVaultKey(), - "azurerm_key_vault_secret": resourceArmKeyVaultSecret(), - "azurerm_lb": resourceArmLoadBalancer(), - "azurerm_lb_backend_address_pool": resourceArmLoadBalancerBackendAddressPool(), - "azurerm_lb_nat_rule": resourceArmLoadBalancerNatRule(), - "azurerm_lb_nat_pool": resourceArmLoadBalancerNatPool(), - "azurerm_lb_probe": resourceArmLoadBalancerProbe(), - "azurerm_lb_rule": resourceArmLoadBalancerRule(), - "azurerm_local_network_gateway": resourceArmLocalNetworkGateway(), - "azurerm_log_analytics_workspace": resourceArmLogAnalyticsWorkspace(), - "azurerm_managed_disk": resourceArmManagedDisk(), - "azurerm_management_lock": resourceArmManagementLock(), - "azurerm_metric_alertrule": resourceArmMetricAlertRule(), - "azurerm_mysql_configuration": resourceArmMySQLConfiguration(), - "azurerm_mysql_database": resourceArmMySqlDatabase(), - "azurerm_mysql_firewall_rule": resourceArmMySqlFirewallRule(), - "azurerm_mysql_server": resourceArmMySqlServer(), - "azurerm_network_interface": resourceArmNetworkInterface(), - "azurerm_network_security_group": resourceArmNetworkSecurityGroup(), - "azurerm_network_security_rule": resourceArmNetworkSecurityRule(), - "azurerm_network_watcher": resourceArmNetworkWatcher(), - "azurerm_postgresql_configuration": resourceArmPostgreSQLConfiguration(), - "azurerm_postgresql_database": resourceArmPostgreSQLDatabase(), - "azurerm_postgresql_firewall_rule": resourceArmPostgreSQLFirewallRule(), - "azurerm_postgresql_server": resourceArmPostgreSQLServer(), - "azurerm_public_ip": resourceArmPublicIp(), - "azurerm_redis_cache": resourceArmRedisCache(), - "azurerm_redis_firewall_rule": resourceArmRedisFirewallRule(), - "azurerm_resource_group": resourceArmResourceGroup(), - "azurerm_role_assignment": resourceArmRoleAssignment(), - "azurerm_role_definition": resourceArmRoleDefinition(), - "azurerm_route": resourceArmRoute(), - "azurerm_route_table": resourceArmRouteTable(), - "azurerm_search_service": resourceArmSearchService(), - "azurerm_servicebus_namespace": resourceArmServiceBusNamespace(), - "azurerm_servicebus_queue": resourceArmServiceBusQueue(), - "azurerm_servicebus_subscription": resourceArmServiceBusSubscription(), - "azurerm_servicebus_topic": resourceArmServiceBusTopic(), - "azurerm_snapshot": resourceArmSnapshot(), - "azurerm_sql_database": resourceArmSqlDatabase(), - "azurerm_sql_elasticpool": resourceArmSqlElasticPool(), - "azurerm_sql_firewall_rule": resourceArmSqlFirewallRule(), - "azurerm_sql_server": resourceArmSqlServer(), - "azurerm_storage_account": resourceArmStorageAccount(), - "azurerm_storage_blob": resourceArmStorageBlob(), - "azurerm_storage_container": resourceArmStorageContainer(), - "azurerm_storage_share": resourceArmStorageShare(), - "azurerm_storage_queue": resourceArmStorageQueue(), - "azurerm_storage_table": resourceArmStorageTable(), - "azurerm_subnet": resourceArmSubnet(), - "azurerm_template_deployment": resourceArmTemplateDeployment(), - "azurerm_traffic_manager_endpoint": resourceArmTrafficManagerEndpoint(), - "azurerm_traffic_manager_profile": resourceArmTrafficManagerProfile(), - "azurerm_virtual_machine_extension": resourceArmVirtualMachineExtensions(), - "azurerm_virtual_machine": resourceArmVirtualMachine(), - "azurerm_virtual_machine_scale_set": resourceArmVirtualMachineScaleSet(), - "azurerm_virtual_network": resourceArmVirtualNetwork(), - "azurerm_virtual_network_peering": resourceArmVirtualNetworkPeering(), + "azurerm_application_gateway": resourceArmApplicationGateway(), + "azurerm_application_insights": resourceArmApplicationInsights(), + "azurerm_app_service": resourceArmAppService(), + "azurerm_app_service_plan": resourceArmAppServicePlan(), + "azurerm_automation_account": resourceArmAutomationAccount(), + "azurerm_automation_credential": resourceArmAutomationCredential(), + "azurerm_automation_runbook": resourceArmAutomationRunbook(), + "azurerm_automation_schedule": resourceArmAutomationSchedule(), + "azurerm_availability_set": resourceArmAvailabilitySet(), + "azurerm_cdn_endpoint": resourceArmCdnEndpoint(), + "azurerm_cdn_profile": resourceArmCdnProfile(), + "azurerm_container_registry": resourceArmContainerRegistry(), + "azurerm_container_service": resourceArmContainerService(), + "azurerm_container_group": resourceArmContainerGroup(), + "azurerm_cosmosdb_account": resourceArmCosmosDBAccount(), + "azurerm_dns_a_record": resourceArmDnsARecord(), + "azurerm_dns_aaaa_record": resourceArmDnsAAAARecord(), + "azurerm_dns_cname_record": resourceArmDnsCNameRecord(), + "azurerm_dns_mx_record": resourceArmDnsMxRecord(), + "azurerm_dns_ns_record": resourceArmDnsNsRecord(), + "azurerm_dns_ptr_record": resourceArmDnsPtrRecord(), + "azurerm_dns_srv_record": resourceArmDnsSrvRecord(), + "azurerm_dns_txt_record": resourceArmDnsTxtRecord(), + "azurerm_dns_zone": resourceArmDnsZone(), + "azurerm_eventgrid_topic": resourceArmEventGridTopic(), + "azurerm_eventhub": resourceArmEventHub(), + "azurerm_eventhub_authorization_rule": resourceArmEventHubAuthorizationRule(), + "azurerm_eventhub_consumer_group": resourceArmEventHubConsumerGroup(), + "azurerm_eventhub_namespace": resourceArmEventHubNamespace(), + "azurerm_express_route_circuit": resourceArmExpressRouteCircuit(), + "azurerm_function_app": resourceArmFunctionApp(), + "azurerm_image": resourceArmImage(), + "azurerm_key_vault": resourceArmKeyVault(), + "azurerm_key_vault_certificate": resourceArmKeyVaultCertificate(), + "azurerm_key_vault_key": resourceArmKeyVaultKey(), + "azurerm_key_vault_secret": resourceArmKeyVaultSecret(), + "azurerm_lb": resourceArmLoadBalancer(), + "azurerm_lb_backend_address_pool": resourceArmLoadBalancerBackendAddressPool(), + "azurerm_lb_nat_rule": resourceArmLoadBalancerNatRule(), + "azurerm_lb_nat_pool": resourceArmLoadBalancerNatPool(), + "azurerm_lb_probe": resourceArmLoadBalancerProbe(), + "azurerm_lb_rule": resourceArmLoadBalancerRule(), + "azurerm_local_network_gateway": resourceArmLocalNetworkGateway(), + "azurerm_log_analytics_workspace": resourceArmLogAnalyticsWorkspace(), + "azurerm_managed_disk": resourceArmManagedDisk(), + "azurerm_management_lock": resourceArmManagementLock(), + "azurerm_metric_alertrule": resourceArmMetricAlertRule(), + "azurerm_mysql_configuration": resourceArmMySQLConfiguration(), + "azurerm_mysql_database": resourceArmMySqlDatabase(), + "azurerm_mysql_firewall_rule": resourceArmMySqlFirewallRule(), + "azurerm_mysql_server": resourceArmMySqlServer(), + "azurerm_network_interface": resourceArmNetworkInterface(), + "azurerm_network_security_group": resourceArmNetworkSecurityGroup(), + "azurerm_network_security_rule": resourceArmNetworkSecurityRule(), + "azurerm_network_watcher": resourceArmNetworkWatcher(), + "azurerm_postgresql_configuration": resourceArmPostgreSQLConfiguration(), + "azurerm_postgresql_database": resourceArmPostgreSQLDatabase(), + "azurerm_postgresql_firewall_rule": resourceArmPostgreSQLFirewallRule(), + "azurerm_postgresql_server": resourceArmPostgreSQLServer(), + "azurerm_public_ip": resourceArmPublicIp(), + "azurerm_redis_cache": resourceArmRedisCache(), + "azurerm_redis_firewall_rule": resourceArmRedisFirewallRule(), + "azurerm_resource_group": resourceArmResourceGroup(), + "azurerm_role_assignment": resourceArmRoleAssignment(), + "azurerm_role_definition": resourceArmRoleDefinition(), + "azurerm_route": resourceArmRoute(), + "azurerm_route_table": resourceArmRouteTable(), + "azurerm_search_service": resourceArmSearchService(), + "azurerm_servicebus_namespace": resourceArmServiceBusNamespace(), + "azurerm_servicebus_queue": resourceArmServiceBusQueue(), + "azurerm_servicebus_subscription": resourceArmServiceBusSubscription(), + "azurerm_servicebus_topic": resourceArmServiceBusTopic(), + "azurerm_snapshot": resourceArmSnapshot(), + "azurerm_sql_database": resourceArmSqlDatabase(), + "azurerm_sql_elasticpool": resourceArmSqlElasticPool(), + "azurerm_sql_firewall_rule": resourceArmSqlFirewallRule(), + "azurerm_sql_server": resourceArmSqlServer(), + "azurerm_storage_account": resourceArmStorageAccount(), + "azurerm_storage_blob": resourceArmStorageBlob(), + "azurerm_storage_container": resourceArmStorageContainer(), + "azurerm_storage_share": resourceArmStorageShare(), + "azurerm_storage_queue": resourceArmStorageQueue(), + "azurerm_storage_table": resourceArmStorageTable(), + "azurerm_subnet": resourceArmSubnet(), + "azurerm_template_deployment": resourceArmTemplateDeployment(), + "azurerm_traffic_manager_endpoint": resourceArmTrafficManagerEndpoint(), + "azurerm_traffic_manager_profile": resourceArmTrafficManagerProfile(), + "azurerm_virtual_machine_extension": resourceArmVirtualMachineExtensions(), + "azurerm_virtual_machine": resourceArmVirtualMachine(), + "azurerm_virtual_machine_scale_set": resourceArmVirtualMachineScaleSet(), + "azurerm_virtual_network": resourceArmVirtualNetwork(), + "azurerm_virtual_network_gateway": resourceArmVirtualNetworkGateway(), + "azurerm_virtual_network_gateway_connection": resourceArmVirtualNetworkGatewayConnection(), + "azurerm_virtual_network_peering": resourceArmVirtualNetworkPeering(), }, } diff --git a/azurerm/resource_arm_local_network_gateway.go b/azurerm/resource_arm_local_network_gateway.go index 2e0b9b9c09e0..0f0ae312a0aa 100644 --- a/azurerm/resource_arm_local_network_gateway.go +++ b/azurerm/resource_arm_local_network_gateway.go @@ -131,12 +131,10 @@ func resourceArmLocalNetworkGatewayRead(d *schema.ResourceData, meta interface{} client := meta.(*ArmClient).localNetConnClient ctx := meta.(*ArmClient).StopContext - id, err := parseAzureResourceID(d.Id()) + resGroup, name, err := resourceGroupAndLocalNetworkGatewayFromId(d.Id()) if err != nil { return err } - name := id.Path["localNetworkGateways"] - resGroup := id.ResourceGroup resp, err := client.Get(ctx, resGroup, name) if err != nil { @@ -175,12 +173,10 @@ func resourceArmLocalNetworkGatewayDelete(d *schema.ResourceData, meta interface client := meta.(*ArmClient).localNetConnClient ctx := meta.(*ArmClient).StopContext - id, err := parseAzureResourceID(d.Id()) + resGroup, name, err := resourceGroupAndLocalNetworkGatewayFromId(d.Id()) if err != nil { return err } - name := id.Path["localNetworkGateways"] - resGroup := id.ResourceGroup future, err := client.Delete(ctx, resGroup, name) if err != nil { @@ -203,6 +199,17 @@ func resourceArmLocalNetworkGatewayDelete(d *schema.ResourceData, meta interface return nil } +func resourceGroupAndLocalNetworkGatewayFromId(localNetworkGatewayId string) (string, string, error) { + id, err := parseAzureResourceID(localNetworkGatewayId) + if err != nil { + return "", "", err + } + name := id.Path["localNetworkGateways"] + resGroup := id.ResourceGroup + + return resGroup, name, nil +} + func expandLocalNetworkGatewayBGPSettings(d *schema.ResourceData) (*network.BgpSettings, error) { v, exists := d.GetOk("bgp_settings") if !exists { diff --git a/azurerm/resource_arm_virtual_network_gateway.go b/azurerm/resource_arm_virtual_network_gateway.go new file mode 100644 index 000000000000..26d6259181f7 --- /dev/null +++ b/azurerm/resource_arm_virtual_network_gateway.go @@ -0,0 +1,660 @@ +package azurerm + +import ( + "bytes" + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2017-09-01/network" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmVirtualNetworkGateway() *schema.Resource { + return &schema.Resource{ + Create: resourceArmVirtualNetworkGatewayCreateUpdate, + Read: resourceArmVirtualNetworkGatewayRead, + Update: resourceArmVirtualNetworkGatewayCreateUpdate, + Delete: resourceArmVirtualNetworkGatewayDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "resource_group_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "location": locationSchema(), + + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(network.VirtualNetworkGatewayTypeExpressRoute), + string(network.VirtualNetworkGatewayTypeVpn), + }, true), + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + }, + + "vpn_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: string(network.RouteBased), + ValidateFunc: validation.StringInSlice([]string{ + string(network.RouteBased), + string(network.PolicyBased), + }, true), + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + }, + + "enable_bgp": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + + "active_active": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + + "sku": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + ValidateFunc: validation.StringInSlice([]string{ + string(network.VirtualNetworkGatewaySkuTierBasic), + string(network.VirtualNetworkGatewaySkuTierStandard), + string(network.VirtualNetworkGatewaySkuTierHighPerformance), + string(network.VirtualNetworkGatewaySkuTierUltraPerformance), + string(network.VirtualNetworkGatewaySkuNameVpnGw1), + string(network.VirtualNetworkGatewaySkuNameVpnGw2), + string(network.VirtualNetworkGatewaySkuNameVpnGw3), + }, true), + }, + + "ip_configuration": { + Type: schema.TypeList, + Required: true, + MaxItems: 2, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + // Azure Management API requires a name but does not generate a name if the field is missing + // The name "vnetGatewayConfig" is used when creating a virtual network gateway via the + // Azure portal. + Default: "vnetGatewayConfig", + }, + "private_ip_address_allocation": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + string(network.Static), + string(network.Dynamic), + }, false), + Default: string(network.Dynamic), + }, + "subnet_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArmVirtualNetworkGatewaySubnetId, + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + }, + "public_ip_address_id": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + + "vpn_client_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address_space": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "root_certificate": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "public_cert_data": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + Set: hashVirtualNetworkGatewayRootCert, + }, + "revoked_certificate": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "thumbprint": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + Set: hashVirtualNetworkGatewayRevokedCert, + }, + }, + }, + }, + + "bgp_settings": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "asn": { + Type: schema.TypeInt, + Optional: true, + }, + "peering_address": { + Type: schema.TypeString, + Optional: true, + }, + "peer_weight": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + + "default_local_network_gateway_id": { + Type: schema.TypeString, + Optional: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceArmVirtualNetworkGatewayCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).vnetGatewayClient + ctx := meta.(*ArmClient).StopContext + + log.Printf("[INFO] preparing arguments for AzureRM Virtual Network Gateway creation.") + + name := d.Get("name").(string) + location := d.Get("location").(string) + resGroup := d.Get("resource_group_name").(string) + tags := d.Get("tags").(map[string]interface{}) + + properties, err := getArmVirtualNetworkGatewayProperties(d) + if err != nil { + return err + } + + gateway := network.VirtualNetworkGateway{ + Name: &name, + Location: &location, + Tags: expandTags(tags), + VirtualNetworkGatewayPropertiesFormat: properties, + } + + future, err := client.CreateOrUpdate(ctx, resGroup, name, gateway) + if err != nil { + return fmt.Errorf("Error Creating/Updating AzureRM Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + + err = future.WaitForCompletion(ctx, client.Client) + if err != nil { + return fmt.Errorf("Error waiting for completion of AzureRM Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + + read, err := client.Get(ctx, resGroup, name) + if err != nil { + return err + } + if read.ID == nil { + return fmt.Errorf("Cannot read AzureRM Virtual Network Gateway %s (resource group %s) ID", name, resGroup) + } + + d.SetId(*read.ID) + + return resourceArmVirtualNetworkGatewayRead(d, meta) +} + +func resourceArmVirtualNetworkGatewayRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).vnetGatewayClient + ctx := meta.(*ArmClient).StopContext + + resGroup, name, err := resourceGroupAndVirtualNetworkGatewayFromId(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, resGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on AzureRM Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", resGroup) + + if location := resp.Location; location != nil { + d.Set("location", azureRMNormalizeLocation(*location)) + } + + if resp.VirtualNetworkGatewayPropertiesFormat != nil { + gw := *resp.VirtualNetworkGatewayPropertiesFormat + + d.Set("type", string(gw.GatewayType)) + d.Set("enable_bgp", gw.EnableBgp) + d.Set("active_active", gw.ActiveActive) + + if string(gw.VpnType) != "" { + d.Set("vpn_type", string(gw.VpnType)) + } + + if gw.GatewayDefaultSite != nil { + d.Set("default_local_network_gateway_id", gw.GatewayDefaultSite.ID) + } + + if gw.Sku != nil { + d.Set("sku", string(gw.Sku.Name)) + } + + d.Set("ip_configuration", flattenArmVirtualNetworkGatewayIPConfigurations(gw.IPConfigurations)) + + if gw.VpnClientConfiguration != nil { + vpnConfigFlat := flattenArmVirtualNetworkGatewayVpnClientConfig(gw.VpnClientConfiguration) + if err := d.Set("vpn_client_configuration", vpnConfigFlat); err != nil { + return fmt.Errorf("Error setting `vpn_client_configuration`: %+v", err) + } + } + + if gw.BgpSettings != nil { + bgpSettingsFlat := flattenArmVirtualNetworkGatewayBgpSettings(gw.BgpSettings) + if err := d.Set("bgp_settings", bgpSettingsFlat); err != nil { + return fmt.Errorf("Error setting `bgp_settings`: %+v", err) + } + } + } + + flattenAndSetTags(d, resp.Tags) + + return nil +} + +func resourceArmVirtualNetworkGatewayDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).vnetGatewayClient + ctx := meta.(*ArmClient).StopContext + + resGroup, name, err := resourceGroupAndVirtualNetworkGatewayFromId(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, resGroup, name) + if err != nil { + return fmt.Errorf("Error deleting Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + + err = future.WaitForCompletion(ctx, client.Client) + if err != nil { + return fmt.Errorf("Error waiting for deletion of Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + + return nil +} + +func getArmVirtualNetworkGatewayProperties(d *schema.ResourceData) (*network.VirtualNetworkGatewayPropertiesFormat, error) { + gatewayType := network.VirtualNetworkGatewayType(d.Get("type").(string)) + vpnType := network.VpnType(d.Get("vpn_type").(string)) + enableBgp := d.Get("enable_bgp").(bool) + activeActive := d.Get("active_active").(bool) + + props := &network.VirtualNetworkGatewayPropertiesFormat{ + GatewayType: gatewayType, + VpnType: vpnType, + EnableBgp: &enableBgp, + ActiveActive: &activeActive, + Sku: expandArmVirtualNetworkGatewaySku(d), + IPConfigurations: expandArmVirtualNetworkGatewayIPConfigurations(d), + } + + if gatewayDefaultSiteID := d.Get("default_local_network_gateway_id").(string); gatewayDefaultSiteID != "" { + props.GatewayDefaultSite = &network.SubResource{ + ID: &gatewayDefaultSiteID, + } + } + + if _, ok := d.GetOk("vpn_client_configuration"); ok { + props.VpnClientConfiguration = expandArmVirtualNetworkGatewayVpnClientConfig(d) + } + + if _, ok := d.GetOk("bgp_settings"); ok { + props.BgpSettings = expandArmVirtualNetworkGatewayBgpSettings(d) + } + + // Sku validation for policy-based VPN gateways + if props.GatewayType == network.VirtualNetworkGatewayTypeVpn && props.VpnType == network.PolicyBased { + ok, err := evaluateSchemaValidateFunc(string(props.Sku.Name), "sku", validateArmVirtualNetworkGatewayPolicyBasedVpnSku()) + + if !ok { + return nil, err + } + } + + // Sku validation for route-based VPN gateways + if props.GatewayType == network.VirtualNetworkGatewayTypeVpn && props.VpnType == network.RouteBased { + ok, err := evaluateSchemaValidateFunc(string(props.Sku.Name), "sku", validateArmVirtualNetworkGatewayRouteBasedVpnSku()) + + if !ok { + return nil, err + } + } + + // Sku validation for ExpressRoute gateways + if props.GatewayType == network.VirtualNetworkGatewayTypeExpressRoute { + ok, err := evaluateSchemaValidateFunc(string(props.Sku.Name), "sku", validateArmVirtualNetworkGatewayExpressRouteSku()) + + if !ok { + return nil, err + } + } + + return props, nil +} + +func expandArmVirtualNetworkGatewayBgpSettings(d *schema.ResourceData) *network.BgpSettings { + bgpSets := d.Get("bgp_settings").([]interface{}) + bgp := bgpSets[0].(map[string]interface{}) + + asn := int64(bgp["asn"].(int)) + peeringAddress := bgp["peering_address"].(string) + peerWeight := int32(bgp["peer_weight"].(int)) + + return &network.BgpSettings{ + Asn: &asn, + BgpPeeringAddress: &peeringAddress, + PeerWeight: &peerWeight, + } +} + +func expandArmVirtualNetworkGatewayIPConfigurations(d *schema.ResourceData) *[]network.VirtualNetworkGatewayIPConfiguration { + configs := d.Get("ip_configuration").([]interface{}) + ipConfigs := make([]network.VirtualNetworkGatewayIPConfiguration, 0, len(configs)) + + for _, c := range configs { + conf := c.(map[string]interface{}) + + name := conf["name"].(string) + privateIPAllocMethod := network.IPAllocationMethod(conf["private_ip_address_allocation"].(string)) + + props := &network.VirtualNetworkGatewayIPConfigurationPropertiesFormat{ + PrivateIPAllocationMethod: privateIPAllocMethod, + } + + if subnetID := conf["subnet_id"].(string); subnetID != "" { + props.Subnet = &network.SubResource{ + ID: &subnetID, + } + } + + if publicIP := conf["public_ip_address_id"].(string); publicIP != "" { + props.PublicIPAddress = &network.SubResource{ + ID: &publicIP, + } + } + + ipConfig := network.VirtualNetworkGatewayIPConfiguration{ + Name: &name, + VirtualNetworkGatewayIPConfigurationPropertiesFormat: props, + } + + ipConfigs = append(ipConfigs, ipConfig) + } + + return &ipConfigs +} + +func expandArmVirtualNetworkGatewayVpnClientConfig(d *schema.ResourceData) *network.VpnClientConfiguration { + configSets := d.Get("vpn_client_configuration").([]interface{}) + conf := configSets[0].(map[string]interface{}) + + confAddresses := conf["address_space"].([]interface{}) + addresses := make([]string, 0, len(confAddresses)) + for _, addr := range confAddresses { + addresses = append(addresses, addr.(string)) + } + + var rootCerts []network.VpnClientRootCertificate + for _, rootCertSet := range conf["root_certificate"].(*schema.Set).List() { + rootCert := rootCertSet.(map[string]interface{}) + name := rootCert["name"].(string) + publicCertData := rootCert["public_cert_data"].(string) + r := network.VpnClientRootCertificate{ + Name: &name, + VpnClientRootCertificatePropertiesFormat: &network.VpnClientRootCertificatePropertiesFormat{ + PublicCertData: &publicCertData, + }, + } + rootCerts = append(rootCerts, r) + } + + var revokedCerts []network.VpnClientRevokedCertificate + for _, revokedCertSet := range conf["revoked_certificate"].(*schema.Set).List() { + revokedCert := revokedCertSet.(map[string]interface{}) + name := revokedCert["name"].(string) + thumbprint := revokedCert["thumbprint"].(string) + r := network.VpnClientRevokedCertificate{ + Name: &name, + VpnClientRevokedCertificatePropertiesFormat: &network.VpnClientRevokedCertificatePropertiesFormat{ + Thumbprint: &thumbprint, + }, + } + revokedCerts = append(revokedCerts, r) + } + + return &network.VpnClientConfiguration{ + VpnClientAddressPool: &network.AddressSpace{ + AddressPrefixes: &addresses, + }, + VpnClientRootCertificates: &rootCerts, + VpnClientRevokedCertificates: &revokedCerts, + } +} + +func expandArmVirtualNetworkGatewaySku(d *schema.ResourceData) *network.VirtualNetworkGatewaySku { + sku := d.Get("sku").(string) + + return &network.VirtualNetworkGatewaySku{ + Name: network.VirtualNetworkGatewaySkuName(sku), + Tier: network.VirtualNetworkGatewaySkuTier(sku), + } +} + +func flattenArmVirtualNetworkGatewayBgpSettings(settings *network.BgpSettings) []interface{} { + flat := make(map[string]interface{}) + + flat["asn"] = int(*settings.Asn) + flat["peering_address"] = *settings.BgpPeeringAddress + flat["peer_weight"] = int(*settings.PeerWeight) + + return []interface{}{flat} +} + +func flattenArmVirtualNetworkGatewayIPConfigurations(ipConfigs *[]network.VirtualNetworkGatewayIPConfiguration) []interface{} { + flat := make([]interface{}, 0, len(*ipConfigs)) + + for _, cfg := range *ipConfigs { + props := cfg.VirtualNetworkGatewayIPConfigurationPropertiesFormat + v := make(map[string]interface{}) + + v["name"] = *cfg.Name + v["private_ip_address_allocation"] = string(props.PrivateIPAllocationMethod) + v["subnet_id"] = *props.Subnet.ID + v["public_ip_address_id"] = *props.PublicIPAddress.ID + + flat = append(flat, v) + } + + return flat +} + +func flattenArmVirtualNetworkGatewayVpnClientConfig(cfg *network.VpnClientConfiguration) []interface{} { + flat := make(map[string]interface{}) + + addressSpace := make([]interface{}, 0) + if pool := cfg.VpnClientAddressPool; pool != nil { + if prefixes := pool.AddressPrefixes; prefixes != nil { + for _, addr := range *prefixes { + addressSpace = append(addressSpace, addr) + } + } + } + flat["address_space"] = addressSpace + + rootCerts := make([]interface{}, 0) + if certs := cfg.VpnClientRootCertificates; certs != nil { + for _, cert := range *certs { + v := map[string]interface{}{ + "name": *cert.Name, + "public_cert_data": *cert.VpnClientRootCertificatePropertiesFormat.PublicCertData, + } + rootCerts = append(rootCerts, v) + } + } + flat["root_certificate"] = schema.NewSet(hashVirtualNetworkGatewayRootCert, rootCerts) + + revokedCerts := make([]interface{}, 0) + if certs := cfg.VpnClientRevokedCertificates; certs != nil { + for _, cert := range *certs { + v := map[string]interface{}{ + "name": *cert.Name, + "thumbprint": *cert.VpnClientRevokedCertificatePropertiesFormat.Thumbprint, + } + revokedCerts = append(revokedCerts, v) + } + } + flat["revoked_certificate"] = schema.NewSet(hashVirtualNetworkGatewayRevokedCert, revokedCerts) + + return []interface{}{flat} +} + +func hashVirtualNetworkGatewayRootCert(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["public_cert_data"].(string))) + + return hashcode.String(buf.String()) +} + +func hashVirtualNetworkGatewayRevokedCert(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["thumbprint"].(string))) + + return hashcode.String(buf.String()) +} + +func resourceGroupAndVirtualNetworkGatewayFromId(virtualNetworkGatewayId string) (string, string, error) { + id, err := parseAzureResourceID(virtualNetworkGatewayId) + if err != nil { + return "", "", err + } + name := id.Path["virtualNetworkGateways"] + resGroup := id.ResourceGroup + + return resGroup, name, nil +} + +func validateArmVirtualNetworkGatewaySubnetId(i interface{}, k string) (s []string, es []error) { + value, ok := i.(string) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be string", k)) + return + } + + id, err := parseAzureResourceID(value) + if err != nil { + es = append(es, fmt.Errorf("expected %s to be an Azure resource id", k)) + return + } + + subnet, ok := id.Path["subnets"] + if !ok { + es = append(es, fmt.Errorf("expected %s to reference a subnet resource", k)) + return + } + + if subnet != "GatewaySubnet" { + es = append(es, fmt.Errorf("expected %s to reference a gateway subnet with name GatewaySubnet", k)) + } + + return +} + +func validateArmVirtualNetworkGatewayPolicyBasedVpnSku() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + string(network.VirtualNetworkGatewaySkuTierBasic), + }, true) +} + +func validateArmVirtualNetworkGatewayRouteBasedVpnSku() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + string(network.VirtualNetworkGatewaySkuTierBasic), + string(network.VirtualNetworkGatewaySkuTierStandard), + string(network.VirtualNetworkGatewaySkuTierHighPerformance), + string(network.VirtualNetworkGatewaySkuNameVpnGw1), + string(network.VirtualNetworkGatewaySkuNameVpnGw2), + string(network.VirtualNetworkGatewaySkuNameVpnGw3), + }, true) +} + +func validateArmVirtualNetworkGatewayExpressRouteSku() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + string(network.VirtualNetworkGatewaySkuTierStandard), + string(network.VirtualNetworkGatewaySkuTierHighPerformance), + string(network.VirtualNetworkGatewaySkuTierUltraPerformance), + }, true) +} diff --git a/azurerm/resource_arm_virtual_network_gateway_connection.go b/azurerm/resource_arm_virtual_network_gateway_connection.go new file mode 100644 index 000000000000..d15f83c71a06 --- /dev/null +++ b/azurerm/resource_arm_virtual_network_gateway_connection.go @@ -0,0 +1,341 @@ +package azurerm + +import ( + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2017-09-01/network" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmVirtualNetworkGatewayConnection() *schema.Resource { + return &schema.Resource{ + Create: resourceArmVirtualNetworkGatewayConnectionCreateUpdate, + Read: resourceArmVirtualNetworkGatewayConnectionRead, + Update: resourceArmVirtualNetworkGatewayConnectionCreateUpdate, + Delete: resourceArmVirtualNetworkGatewayConnectionDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "resource_group_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "location": locationSchema(), + + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(network.ExpressRoute), + string(network.IPsec), + string(network.Vnet2Vnet), + }, true), + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + }, + + "virtual_network_gateway_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "authorization_key": { + Type: schema.TypeString, + Optional: true, + }, + + "express_route_circuit_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "peer_virtual_network_gateway_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "local_network_gateway_id": { + Type: schema.TypeString, + Optional: true, + }, + + "enable_bgp": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "routing_weight": { + Type: schema.TypeInt, + Optional: true, + Default: 10, + }, + + "shared_key": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceArmVirtualNetworkGatewayConnectionCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).vnetGatewayConnectionsClient + ctx := meta.(*ArmClient).StopContext + + log.Printf("[INFO] preparing arguments for AzureRM Virtual Network Gateway Connection creation.") + + name := d.Get("name").(string) + location := d.Get("location").(string) + resGroup := d.Get("resource_group_name").(string) + tags := d.Get("tags").(map[string]interface{}) + + properties, err := getArmVirtualNetworkGatewayConnectionProperties(d) + if err != nil { + return err + } + + connection := network.VirtualNetworkGatewayConnection{ + Name: &name, + Location: &location, + Tags: expandTags(tags), + VirtualNetworkGatewayConnectionPropertiesFormat: properties, + } + + future, err := client.CreateOrUpdate(ctx, resGroup, name, connection) + if err != nil { + return fmt.Errorf("Error Creating/Updating AzureRM Virtual Network Gateway Connection %q (Resource Group %q): %+v", name, resGroup, err) + } + + err = future.WaitForCompletion(ctx, client.Client) + if err != nil { + return fmt.Errorf("Error waiting for completion of Virtual Network Gateway Connection %q (Resource Group %q): %+v", name, resGroup, err) + } + + read, err := client.Get(ctx, resGroup, name) + if err != nil { + return err + } + if read.ID == nil { + return fmt.Errorf("Cannot read AzureRM Virtual Network Gateway Connection %q (resource group %q) ID", name, resGroup) + } + + d.SetId(*read.ID) + + return resourceArmVirtualNetworkGatewayConnectionRead(d, meta) +} + +func resourceArmVirtualNetworkGatewayConnectionRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).vnetGatewayConnectionsClient + ctx := meta.(*ArmClient).StopContext + + resGroup, name, err := resourceGroupAndVirtualNetworkGatewayConnectionFromId(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, resGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on AzureRM Virtual Network Gateway Connection %q: %+v", name, err) + } + + conn := *resp.VirtualNetworkGatewayConnectionPropertiesFormat + + d.Set("name", resp.Name) + d.Set("resource_group_name", resGroup) + + if location := resp.Location; location != nil { + d.Set("location", azureRMNormalizeLocation(*location)) + } + + if string(conn.ConnectionType) != "" { + d.Set("type", string(conn.ConnectionType)) + } + + if conn.VirtualNetworkGateway1 != nil { + d.Set("virtual_network_gateway_id", conn.VirtualNetworkGateway1.ID) + } + + d.Set("authorization_key", conn.AuthorizationKey) + + if conn.Peer != nil { + d.Set("express_route_circuit_id", conn.Peer.ID) + } + + if conn.VirtualNetworkGateway2 != nil { + d.Set("peer_virtual_network_gateway_id", conn.VirtualNetworkGateway2.ID) + } + + if conn.LocalNetworkGateway2 != nil { + d.Set("local_network_gateway_id", conn.LocalNetworkGateway2.ID) + } + + d.Set("enable_bgp", conn.EnableBgp) + d.Set("routing_weight", conn.RoutingWeight) + d.Set("shared_key", conn.SharedKey) + + flattenAndSetTags(d, resp.Tags) + + return nil +} + +func resourceArmVirtualNetworkGatewayConnectionDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).vnetGatewayConnectionsClient + ctx := meta.(*ArmClient).StopContext + + resGroup, name, err := resourceGroupAndVirtualNetworkGatewayConnectionFromId(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, resGroup, name) + if err != nil { + return fmt.Errorf("Error Deleting Virtual Network Gateway Connection %q (Resource Group %q): %+v", name, resGroup, err) + } + + err = future.WaitForCompletion(ctx, client.Client) + if err != nil { + return fmt.Errorf("Error waiting for deletion of Virtual Network Gateway Connection %q (Resource Group %q): %+v", name, resGroup, err) + } + + return nil +} + +func getArmVirtualNetworkGatewayConnectionProperties(d *schema.ResourceData) (*network.VirtualNetworkGatewayConnectionPropertiesFormat, error) { + connectionType := network.VirtualNetworkGatewayConnectionType(d.Get("type").(string)) + enableBgp := d.Get("enable_bgp").(bool) + + props := &network.VirtualNetworkGatewayConnectionPropertiesFormat{ + ConnectionType: connectionType, + EnableBgp: &enableBgp, + } + + if v, ok := d.GetOk("virtual_network_gateway_id"); ok { + virtualNetworkGatewayId := v.(string) + _, name, err := resourceGroupAndVirtualNetworkGatewayFromId(virtualNetworkGatewayId) + if err != nil { + return nil, errwrap.Wrapf("Error Getting VirtualNetworkGateway Name and Group: {{err}}", err) + } + + props.VirtualNetworkGateway1 = &network.VirtualNetworkGateway{ + ID: &virtualNetworkGatewayId, + Name: &name, + VirtualNetworkGatewayPropertiesFormat: &network.VirtualNetworkGatewayPropertiesFormat{ + IPConfigurations: &[]network.VirtualNetworkGatewayIPConfiguration{}, + }, + } + } + + if v, ok := d.GetOk("authorization_key"); ok { + authorizationKey := v.(string) + props.AuthorizationKey = &authorizationKey + } + + if v, ok := d.GetOk("express_route_circuit_id"); ok { + expressRouteCircuitId := v.(string) + props.Peer = &network.SubResource{ + ID: &expressRouteCircuitId, + } + } + + if v, ok := d.GetOk("peer_virtual_network_gateway_id"); ok { + peerVirtualNetworkGatewayId := v.(string) + _, name, err := resourceGroupAndVirtualNetworkGatewayFromId(peerVirtualNetworkGatewayId) + if err != nil { + return nil, errwrap.Wrapf("Error Getting VirtualNetworkGateway Name and Group: {{err}}", err) + } + + props.VirtualNetworkGateway2 = &network.VirtualNetworkGateway{ + ID: &peerVirtualNetworkGatewayId, + Name: &name, + VirtualNetworkGatewayPropertiesFormat: &network.VirtualNetworkGatewayPropertiesFormat{ + IPConfigurations: &[]network.VirtualNetworkGatewayIPConfiguration{}, + }, + } + } + + if v, ok := d.GetOk("local_network_gateway_id"); ok { + localNetworkGatewayId := v.(string) + _, name, err := resourceGroupAndLocalNetworkGatewayFromId(localNetworkGatewayId) + if err != nil { + return nil, errwrap.Wrapf("Error Getting LocalNetworkGateway Name and Group: {{err}}", err) + } + + props.LocalNetworkGateway2 = &network.LocalNetworkGateway{ + ID: &localNetworkGatewayId, + Name: &name, + LocalNetworkGatewayPropertiesFormat: &network.LocalNetworkGatewayPropertiesFormat{ + LocalNetworkAddressSpace: &network.AddressSpace{}, + }, + } + } + + if v, ok := d.GetOk("routing_weight"); ok { + routingWeight := int32(v.(int)) + props.RoutingWeight = &routingWeight + } + + if v, ok := d.GetOk("shared_key"); ok { + sharedKey := v.(string) + props.SharedKey = &sharedKey + } + + if props.ConnectionType == network.ExpressRoute { + if props.Peer == nil || props.Peer.ID == nil { + return nil, fmt.Errorf("`express_route_circuit_id` must be specified when `type` is set to `ExpressRoute") + } + } + + if props.ConnectionType == network.IPsec { + if props.LocalNetworkGateway2 == nil || props.LocalNetworkGateway2.ID == nil { + return nil, fmt.Errorf("`local_network_gateway_id` and `shared_key` must be specified when `type` is set to `IPsec") + } + + if props.SharedKey == nil { + return nil, fmt.Errorf("`local_network_gateway_id` and `shared_key` must be specified when `type` is set to `IPsec") + } + } + + if props.ConnectionType == network.Vnet2Vnet { + if props.VirtualNetworkGateway2 == nil || props.VirtualNetworkGateway2.ID == nil { + return nil, fmt.Errorf("`peer_virtual_network_gateway_id` and `shared_key` must be specified when `type` is set to `Vnet2Vnet") + } + } + + return props, nil +} + +func resourceGroupAndVirtualNetworkGatewayConnectionFromId(virtualNetworkGatewayConnectionId string) (string, string, error) { + id, err := parseAzureResourceID(virtualNetworkGatewayConnectionId) + if err != nil { + return "", "", err + } + name := id.Path["connections"] + resGroup := id.ResourceGroup + + return resGroup, name, nil +} diff --git a/azurerm/resource_arm_virtual_network_gateway_connection_test.go b/azurerm/resource_arm_virtual_network_gateway_connection_test.go new file mode 100644 index 000000000000..fa58f8d84ba5 --- /dev/null +++ b/azurerm/resource_arm_virtual_network_gateway_connection_test.go @@ -0,0 +1,343 @@ +package azurerm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMVirtualNetworkGatewayConnection_sitetosite(t *testing.T) { + ri := acctest.RandInt() + config := testAccAzureRMVirtualNetworkGatewayConnection_sitetosite(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualNetworkGatewayConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualNetworkGatewayConnectionExists("azurerm_virtual_network_gateway_connection.test"), + ), + }, + }, + }) +} + +func TestAccAzureRMVirtualNetworkGatewayConnection_vnettonet(t *testing.T) { + firstResourceName := "azurerm_virtual_network_gateway_connection.test_1" + secondResourceName := "azurerm_virtual_network_gateway_connection.test_2" + + ri := acctest.RandInt() + ri2 := acctest.RandInt() + sharedKey := "4-v3ry-53cr37-1p53c-5h4r3d-k3y" + config := testAccAzureRMVirtualNetworkGatewayConnection_vnettovnet(ri, ri2, sharedKey, testLocation(), testAltLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualNetworkGatewayConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualNetworkGatewayConnectionExists(firstResourceName), + testCheckAzureRMVirtualNetworkGatewayConnectionExists(secondResourceName), + resource.TestCheckResourceAttr(firstResourceName, "shared_key", sharedKey), + resource.TestCheckResourceAttr(secondResourceName, "shared_key", sharedKey), + ), + }, + }, + }) +} + +func TestAccAzureRMVirtualNetworkGatewayConnection_updatingSharedKey(t *testing.T) { + firstResourceName := "azurerm_virtual_network_gateway_connection.test_1" + secondResourceName := "azurerm_virtual_network_gateway_connection.test_2" + + ri := acctest.RandInt() + ri2 := acctest.RandInt() + loc1 := testLocation() + loc2 := testAltLocation() + + firstSharedKey := "4-v3ry-53cr37-1p53c-5h4r3d-k3y" + secondSharedKey := "4-r33ly-53cr37-1p53c-5h4r3d-k3y" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualNetworkGatewayConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMVirtualNetworkGatewayConnection_vnettovnet(ri, ri2, firstSharedKey, loc1, loc2), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualNetworkGatewayConnectionExists(firstResourceName), + testCheckAzureRMVirtualNetworkGatewayConnectionExists(secondResourceName), + resource.TestCheckResourceAttr(firstResourceName, "shared_key", firstSharedKey), + resource.TestCheckResourceAttr(secondResourceName, "shared_key", firstSharedKey), + ), + }, + { + Config: testAccAzureRMVirtualNetworkGatewayConnection_vnettovnet(ri, ri2, secondSharedKey, loc1, loc2), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualNetworkGatewayConnectionExists(firstResourceName), + testCheckAzureRMVirtualNetworkGatewayConnectionExists(secondResourceName), + resource.TestCheckResourceAttr(firstResourceName, "shared_key", secondSharedKey), + resource.TestCheckResourceAttr(secondResourceName, "shared_key", secondSharedKey), + ), + }, + }, + }) +} + +func testCheckAzureRMVirtualNetworkGatewayConnectionExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + name, resourceGroup, err := getArmResourceNameAndGroup(s, name) + if err != nil { + return err + } + + client := testAccProvider.Meta().(*ArmClient).vnetGatewayConnectionsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Bad: Get on vnetGatewayConnectionsClient: %+v", err) + } + + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Virtual Network Gateway Connection %q (resource group: %q) does not exist", name, resourceGroup) + } + + return nil + } +} + +func testCheckAzureRMVirtualNetworkGatewayConnectionDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).vnetGatewayConnectionsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_virtual_network_gateway_connection" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := client.Get(ctx, resourceGroup, name) + + if err != nil { + return nil + } + + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Virtual Network Gateway Connection still exists: %#v", resp.VirtualNetworkGatewayConnectionPropertiesFormat) + } + } + + return nil +} + +func testAccAzureRMVirtualNetworkGatewayConnection_sitetosite(rInt int, location string) string { + return fmt.Sprintf(` +variable "random" { + default = "%d" +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-${var.random}" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "test-${var.random}" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurerm_subnet" "test" { + name = "GatewaySubnet" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurerm_public_ip" "test" { + name = "test-${var.random}" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurerm_virtual_network_gateway" "test" { + name = "test-${var.random}" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "Basic" + + ip_configuration { + name = "vnetGatewayConfig" + public_ip_address_id = "${azurerm_public_ip.test.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurerm_subnet.test.id}" + } +} + +resource "azurerm_local_network_gateway" "test" { + name = "test-${var.random}" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + gateway_address = "168.62.225.23" + address_space = ["10.1.1.0/24"] +} + +resource "azurerm_virtual_network_gateway_connection" "test" { + name = "test-${var.random}" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + type = "IPsec" + virtual_network_gateway_id = "${azurerm_virtual_network_gateway.test.id}" + local_network_gateway_id = "${azurerm_local_network_gateway.test.id}" + + shared_key = "4-v3ry-53cr37-1p53c-5h4r3d-k3y" +} +`, rInt, location) +} + +func testAccAzureRMVirtualNetworkGatewayConnection_vnettovnet(rInt, rInt2 int, sharedKey, location, altLocation string) string { + return fmt.Sprintf(` +variable "random1" { + default = "%d" +} + +variable "random2" { + default = "%d" +} + +variable "shared_key" { + default = "%s" +} + +resource "azurerm_resource_group" "test_1" { + name = "acctestRG-${var.random1}" + location = "%s" +} + +resource "azurerm_virtual_network" "test_1" { + name = "acctest-${var.random1}" + location = "${azurerm_resource_group.test_1.location}" + resource_group_name = "${azurerm_resource_group.test_1.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurerm_subnet" "test_1" { + name = "GatewaySubnet" + resource_group_name = "${azurerm_resource_group.test_1.name}" + virtual_network_name = "${azurerm_virtual_network.test_1.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurerm_public_ip" "test_1" { + name = "acctest-${var.random1}" + location = "${azurerm_resource_group.test_1.location}" + resource_group_name = "${azurerm_resource_group.test_1.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurerm_virtual_network_gateway" "test_1" { + name = "acctest-${var.random1}" + location = "${azurerm_resource_group.test_1.location}" + resource_group_name = "${azurerm_resource_group.test_1.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "Basic" + + ip_configuration { + name = "vnetGatewayConfig" + public_ip_address_id = "${azurerm_public_ip.test_1.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurerm_subnet.test_1.id}" + } +} + +resource "azurerm_virtual_network_gateway_connection" "test_1" { + name = "acctest-${var.random1}" + location = "${azurerm_resource_group.test_1.location}" + resource_group_name = "${azurerm_resource_group.test_1.name}" + + type = "Vnet2Vnet" + virtual_network_gateway_id = "${azurerm_virtual_network_gateway.test_1.id}" + peer_virtual_network_gateway_id = "${azurerm_virtual_network_gateway.test_2.id}" + + shared_key = "${var.shared_key}" +} + +resource "azurerm_resource_group" "test_2" { + name = "acctestRG-${var.random2}" + location = "%s" +} + +resource "azurerm_virtual_network" "test_2" { + name = "acctest-${var.random2}" + location = "${azurerm_resource_group.test_2.location}" + resource_group_name = "${azurerm_resource_group.test_2.name}" + address_space = ["10.1.0.0/16"] +} + +resource "azurerm_subnet" "test_2" { + name = "GatewaySubnet" + resource_group_name = "${azurerm_resource_group.test_2.name}" + virtual_network_name = "${azurerm_virtual_network.test_2.name}" + address_prefix = "10.1.1.0/24" +} + +resource "azurerm_public_ip" "test_2" { + name = "acctest-${var.random2}" + location = "${azurerm_resource_group.test_2.location}" + resource_group_name = "${azurerm_resource_group.test_2.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurerm_virtual_network_gateway" "test_2" { + name = "acctest-${var.random2}" + location = "${azurerm_resource_group.test_2.location}" + resource_group_name = "${azurerm_resource_group.test_2.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "Basic" + + ip_configuration { + name = "vnetGatewayConfig" + public_ip_address_id = "${azurerm_public_ip.test_2.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurerm_subnet.test_2.id}" + } +} + +resource "azurerm_virtual_network_gateway_connection" "test_2" { + name = "acctest-${var.random2}" + location = "${azurerm_resource_group.test_2.location}" + resource_group_name = "${azurerm_resource_group.test_2.name}" + + type = "Vnet2Vnet" + virtual_network_gateway_id = "${azurerm_virtual_network_gateway.test_2.id}" + peer_virtual_network_gateway_id = "${azurerm_virtual_network_gateway.test_1.id}" + + shared_key = "${var.shared_key}" +} +`, rInt, rInt2, sharedKey, location, altLocation) +} diff --git a/azurerm/resource_arm_virtual_network_gateway_test.go b/azurerm/resource_arm_virtual_network_gateway_test.go new file mode 100644 index 000000000000..597e6d2d16da --- /dev/null +++ b/azurerm/resource_arm_virtual_network_gateway_test.go @@ -0,0 +1,190 @@ +package azurerm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMVirtualNetworkGateway_basic(t *testing.T) { + ri := acctest.RandInt() + config := testAccAzureRMVirtualNetworkGateway_basic(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualNetworkGatewayExists("azurerm_virtual_network_gateway.test"), + ), + }, + }, + }) +} + +func TestAccAzureRMVirtualNetworkGateway_vpnGw1(t *testing.T) { + ri := acctest.RandInt() + config := testAccAzureRMVirtualNetworkGateway_vpnGw1(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualNetworkGatewayExists("azurerm_virtual_network_gateway.test"), + ), + }, + }, + }) +} + +func testCheckAzureRMVirtualNetworkGatewayExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + name, resourceGroup, err := getArmResourceNameAndGroup(s, name) + if err != nil { + return err + } + + client := testAccProvider.Meta().(*ArmClient).vnetGatewayClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Bad: Get on vnetGatewayClient: %+v", err) + } + + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Virtual Network Gateway %q (resource group: %q) does not exist", name, resourceGroup) + } + + return nil + } +} + +func testCheckAzureRMVirtualNetworkGatewayDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).vnetGatewayClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_virtual_network_gateway" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := client.Get(ctx, resourceGroup, name) + + if err != nil { + return nil + } + + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Virtual Network Gateway still exists:\n%#v", resp.VirtualNetworkGatewayPropertiesFormat) + } + } + + return nil +} + +func testAccAzureRMVirtualNetworkGateway_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvn-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurerm_subnet" "test" { + name = "GatewaySubnet" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurerm_public_ip" "test" { + name = "acctestpip-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurerm_virtual_network_gateway" "test" { + name = "acctestvng-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "Basic" + + ip_configuration { + public_ip_address_id = "${azurerm_public_ip.test.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurerm_subnet.test.id}" + } +} +`, rInt, location, rInt, rInt, rInt) +} + +func testAccAzureRMVirtualNetworkGateway_vpnGw1(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvn-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurerm_subnet" "test" { + name = "GatewaySubnet" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurerm_public_ip" "test" { + name = "acctestpip-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurerm_virtual_network_gateway" "test" { + name = "acctestvng-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "VpnGw1" + + ip_configuration { + public_ip_address_id = "${azurerm_public_ip.test.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurerm_subnet.test.id}" + } +} +`, rInt, location, rInt, rInt, rInt) +} diff --git a/azurerm/test_utils.go b/azurerm/test_utils.go new file mode 100644 index 000000000000..ec3f8dcf350e --- /dev/null +++ b/azurerm/test_utils.go @@ -0,0 +1,21 @@ +package azurerm + +import ( + "fmt" + "github.com/hashicorp/terraform/terraform" +) + +func getArmResourceNameAndGroup(s *terraform.State, name string) (string, string, error) { + rs, ok := s.RootModule().Resources[name] + if !ok { + return "", "", fmt.Errorf("Not found: %s", name) + } + + armName := rs.Primary.Attributes["name"] + armResourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return "", "", fmt.Errorf("Bad: no resource group found in state for virtual network gateway: %s", name) + } + + return armName, armResourceGroup, nil +} diff --git a/azurerm/validators.go b/azurerm/validators.go index a55853cdba8c..c54d632b8f06 100644 --- a/azurerm/validators.go +++ b/azurerm/validators.go @@ -64,6 +64,16 @@ func validateDBAccountName(v interface{}, k string) (ws []string, errors []error return } +func evaluateSchemaValidateFunc(i interface{}, k string, validateFunc schema.SchemaValidateFunc) (bool, error) { + _, es := validateFunc(i, k) + + if len(es) > 0 { + return false, es[0] + } + + return true, nil +} + func validateStringLength(maxLength int) schema.SchemaValidateFunc { return func(v interface{}, k string) (ws []string, errors []error) { value := v.(string) diff --git a/website/azurerm.erb b/website/azurerm.erb index 4cea2754e2f0..dc5415355a19 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -81,7 +81,7 @@ > azurerm_virtual_network - + @@ -513,6 +513,14 @@ azurerm_virtual_network + > + azurerm_virtual_network_gateway + + + > + azurerm_virtual_network_gateway_connection + + > azurerm_virtual_network_peering diff --git a/website/docs/r/virtual_network_gateway.html.markdown b/website/docs/r/virtual_network_gateway.html.markdown new file mode 100644 index 000000000000..904190964040 --- /dev/null +++ b/website/docs/r/virtual_network_gateway.html.markdown @@ -0,0 +1,220 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azure_virtual_network_gateway" +sidebar_current: "docs-azurerm-resource-network-virtual-network-gateway-x" +description: |- + Creates a new virtual network gateway to establish secure, cross-premises connectivity. +--- + +# azurerm_virtual_network_gateway + +Creates a new Virtual Network Gateway to establish secure, cross-premises connectivity. + +-> **Note:** Please be aware that provisioning a Virtual Network Gateway takes a long time (between 30 minutes and 1 hour) + +## Example Usage + +``` +resource "azurerm_resource_group" "test" { + name = "test" + location = "West US" +} + +resource "azurerm_virtual_network" "test" { + name = "test" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurerm_subnet" "test" { + name = "GatewaySubnet" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurerm_public_ip" "test" { + name = "test" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurerm_virtual_network_gateway" "test" { + name = "test" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + type = "Vpn" + vpn_type = "RouteBased" + + active_active = false + enable_bgp = false + sku = "Basic" + + ip_configuration { + name = "vnetGatewayConfig" + public_ip_address_id = "${azurerm_public_ip.test.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurerm_subnet.test.id}" + } + + vpn_client_configuration { + address_space = [ "10.2.0.0/24" ] + + root_certificate { + name = "DigiCert-Federated-ID-Root-CA" + public_cert_data = <