diff --git a/azurerm/internal/services/network/resource_arm_virtual_network_gateway.go b/azurerm/internal/services/network/resource_arm_virtual_network_gateway.go index 0e814ad807b4..48ab003e1637 100644 --- a/azurerm/internal/services/network/resource_arm_virtual_network_gateway.go +++ b/azurerm/internal/services/network/resource_arm_virtual_network_gateway.go @@ -174,7 +174,7 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { "root_certificate": { Type: schema.TypeSet, Optional: true, - + Computed: true, ConflictsWith: []string{ "vpn_client_configuration.0.radius_server_address", "vpn_client_configuration.0.radius_server_secret", diff --git a/azurerm/internal/services/web/data_source_app_service_virtual_network_connection_gateway.go b/azurerm/internal/services/web/data_source_app_service_virtual_network_connection_gateway.go new file mode 100644 index 000000000000..7467fb7504d4 --- /dev/null +++ b/azurerm/internal/services/web/data_source_app_service_virtual_network_connection_gateway.go @@ -0,0 +1,114 @@ +package web + +import ( + "fmt" + "log" + "strings" + "time" + + "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/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func dataSourceArmAppServiceVirtualNetworkConnectionGateway() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAppServiceVirtualNetworkConnectionGatewayRead, + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(5 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "app_service_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateAppServiceName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "virtual_network_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "certificate_blob": { + Type: schema.TypeString, + Computed: true, + }, + + "certificate_thumbprint": { + Type: schema.TypeString, + Computed: true, + }, + + "dns_servers": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "name": { + Type: schema.TypeString, + Computed: true, + }, + + "resync_required": { + Type: schema.TypeBool, + Computed: true, + }, + + "virtual_network_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceAppServiceVirtualNetworkConnectionGatewayRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Web.AppServicesClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + resGroup := d.Get("resource_group_name").(string) + appServiceName := d.Get("app_service_name").(string) + vnetName := d.Get("virtual_network_name").(string) + + resp, err := client.GetVnetConnection(ctx, resGroup, appServiceName, vnetName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] App Service Virtual Network Connection for app %q vnet %q was not found in Resource Group %q - removnig from state!", appServiceName, vnetName, resGroup) + return nil + } + + return fmt.Errorf("Error making Read request on App Service Virtual Network Connection for app %q vnet %q (Resource Group %q): %+v", appServiceName, vnetName, resGroup, err) + } + + if resp.ID != nil || *resp.ID != "" { + d.SetId(*resp.ID) + } + d.Set("name", resp.Name) + d.Set("resource_group_name", resGroup) + d.Set("app_service_name", appServiceName) + if props := resp.VnetInfoProperties; props != nil { + d.Set("virtual_network_id", props.VnetResourceID) + d.Set("certificate_thumbprint", props.CertThumbprint) + d.Set("certificate_blob", props.CertBlob) + d.Set("resync_required", props.ResyncRequired) + if props.DNSServers != nil { + d.Set("dns_servers", strings.Split(*props.DNSServers, ",")) + } else { + d.Set("dns_servers", []string{}) + } + } + + return nil +} diff --git a/azurerm/internal/services/web/parse/app_service_virtual_network_connection.go b/azurerm/internal/services/web/parse/app_service_virtual_network_connection.go new file mode 100644 index 000000000000..0538e5e78caf --- /dev/null +++ b/azurerm/internal/services/web/parse/app_service_virtual_network_connection.go @@ -0,0 +1,38 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type AppServiceVirtualNetworkConnectionId struct { + ResourceGroup string + AppServiceName string + Name string +} + +func AppServiceVirtualNetworkConnectionID(input string) (*AppServiceVirtualNetworkConnectionId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("[ERROR] Unable to parse App Service Environment ID %q: %+v", input, err) + } + + appServiceVirtualNetworkConnection := AppServiceVirtualNetworkConnectionId{ + ResourceGroup: id.ResourceGroup, + } + + if appServiceVirtualNetworkConnection.AppServiceName, err = id.PopSegment("sites"); err != nil { + return nil, err + } + + if appServiceVirtualNetworkConnection.Name, err = id.PopSegment("virtualNetworkConnections"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &appServiceVirtualNetworkConnection, nil +} diff --git a/azurerm/internal/services/web/parse/app_service_virtual_network_connection_test.go b/azurerm/internal/services/web/parse/app_service_virtual_network_connection_test.go new file mode 100644 index 000000000000..25fefcf5d020 --- /dev/null +++ b/azurerm/internal/services/web/parse/app_service_virtual_network_connection_test.go @@ -0,0 +1,78 @@ +package parse + +import "testing" + +func TestParseAppServiceVirtualNetworkConnectionID(t *testing.T) { + testData := []struct { + Name string + Input string + Error bool + Expect *AppServiceVirtualNetworkConnectionId + }{ + { + Name: "Empty", + Input: "", + Error: true, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Error: true, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Error: true, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", + Error: true, + }, + { + Name: "Missing App Service Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/providers/Microsoft.Web/sites/", + Error: true, + }, + { + Name: "Missing Virtual Network Connection Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/providers/Microsoft.Web/sites/as1/virtualNetworkConnections/", + Error: true, + }, + { + Name: "Valid", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.Web/sites/as1/virtualNetworkConnections/vnet1", + Error: false, + Expect: &AppServiceVirtualNetworkConnectionId{ + ResourceGroup: "resGroup1", + AppServiceName: "as1", + Name: "vnet1", + }, + }, + { + Name: "Wrong Case", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.Web/sites/as1/virtualnetworkconnections/vnet1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := AppServiceVirtualNetworkConnectionID(v.Input) + if err != nil { + if v.Expect == nil { + continue + } + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.Name != v.Expect.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expect.Name, actual.Name) + } + + if actual.ResourceGroup != v.Expect.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expect.ResourceGroup, actual.ResourceGroup) + } + } +} diff --git a/azurerm/internal/services/web/registration.go b/azurerm/internal/services/web/registration.go index 282159c5e36e..792af8c12b33 100644 --- a/azurerm/internal/services/web/registration.go +++ b/azurerm/internal/services/web/registration.go @@ -21,28 +21,30 @@ func (r Registration) WebsiteCategories() []string { // SupportedDataSources returns the supported Data Sources supported by this Service func (r Registration) SupportedDataSources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "azurerm_app_service": dataSourceArmAppService(), - "azurerm_app_service_certificate_order": dataSourceArmAppServiceCertificateOrder(), - "azurerm_app_service_environment": dataSourceArmAppServiceEnvironment(), - "azurerm_app_service_certificate": dataSourceAppServiceCertificate(), - "azurerm_app_service_plan": dataSourceAppServicePlan(), - "azurerm_function_app": dataSourceArmFunctionApp(), + "azurerm_app_service": dataSourceArmAppService(), + "azurerm_app_service_certificate_order": dataSourceArmAppServiceCertificateOrder(), + "azurerm_app_service_environment": dataSourceArmAppServiceEnvironment(), + "azurerm_app_service_certificate": dataSourceAppServiceCertificate(), + "azurerm_app_service_plan": dataSourceAppServicePlan(), + "azurerm_function_app": dataSourceArmFunctionApp(), + "azurerm_app_service_virtual_network_connection_gateway": dataSourceArmAppServiceVirtualNetworkConnectionGateway(), } } // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "azurerm_app_service_active_slot": resourceArmAppServiceActiveSlot(), - "azurerm_app_service_certificate": resourceArmAppServiceCertificate(), - "azurerm_app_service_certificate_order": resourceArmAppServiceCertificateOrder(), - "azurerm_app_service_custom_hostname_binding": resourceArmAppServiceCustomHostnameBinding(), - "azurerm_app_service_environment": resourceArmAppServiceEnvironment(), - "azurerm_app_service_plan": resourceArmAppServicePlan(), - "azurerm_app_service_slot": resourceArmAppServiceSlot(), - "azurerm_app_service_source_control_token": resourceArmAppServiceSourceControlToken(), - "azurerm_app_service_virtual_network_swift_connection": resourceArmAppServiceVirtualNetworkSwiftConnection(), - "azurerm_app_service": resourceArmAppService(), - "azurerm_function_app": resourceArmFunctionApp(), + "azurerm_app_service_active_slot": resourceArmAppServiceActiveSlot(), + "azurerm_app_service_certificate": resourceArmAppServiceCertificate(), + "azurerm_app_service_certificate_order": resourceArmAppServiceCertificateOrder(), + "azurerm_app_service_custom_hostname_binding": resourceArmAppServiceCustomHostnameBinding(), + "azurerm_app_service_environment": resourceArmAppServiceEnvironment(), + "azurerm_app_service_plan": resourceArmAppServicePlan(), + "azurerm_app_service_slot": resourceArmAppServiceSlot(), + "azurerm_app_service_source_control_token": resourceArmAppServiceSourceControlToken(), + "azurerm_app_service_virtual_network_connection_gateway": resourceArmAppServiceVirtualNetworkConnectionGateway(), + "azurerm_app_service_virtual_network_swift_connection": resourceArmAppServiceVirtualNetworkSwiftConnection(), + "azurerm_app_service": resourceArmAppService(), + "azurerm_function_app": resourceArmFunctionApp(), } } diff --git a/azurerm/internal/services/web/resource_arm_app_service_virtual_network_connection_gateway.go b/azurerm/internal/services/web/resource_arm_app_service_virtual_network_connection_gateway.go new file mode 100644 index 000000000000..3f07a04256a7 --- /dev/null +++ b/azurerm/internal/services/web/resource_arm_app_service_virtual_network_connection_gateway.go @@ -0,0 +1,355 @@ +package web + +import ( + "context" + "fmt" + "log" + "net/http" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" + "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2019-08-01/web" + "github.com/Azure/go-autorest/autorest" + autorestAzure "github.com/Azure/go-autorest/autorest/azure" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + networkParse "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/parse" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmAppServiceVirtualNetworkConnectionGateway() *schema.Resource { + return &schema.Resource{ + Create: resourceArmAppServiceVirtualNetworkConnectionGatewayCreate, + Read: resourceArmAppServiceVirtualNetworkConnectionGatewayRead, + Update: nil, + Delete: resourceArmAppServiceVirtualNetworkConnectionGatewayDelete, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.AppServiceVirtualNetworkConnectionID(id) + return err + }), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "app_service_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateAppServiceName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "virtual_network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "virtual_network_gateway_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "certificate_blob": { + Type: schema.TypeString, + Computed: true, + }, + + "certificate_thumbprint": { + Type: schema.TypeString, + Computed: true, + }, + + "dns_servers": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "name": { + Type: schema.TypeString, + Computed: true, + }, + + "resync_required": { + Type: schema.TypeBool, + Computed: true, + }, + }, + } +} + +func resourceArmAppServiceVirtualNetworkConnectionGatewayCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Web.AppServicesClient + vnetGatewayClient := meta.(*clients.Client).Network.VnetGatewayClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + log.Printf("[INFO] preparing arguments for AzureRM App Service Virtual Network Connection creation.") + + resGroup := d.Get("resource_group_name").(string) + appServiceName := d.Get("app_service_name").(string) + vnetId := d.Get("virtual_network_id").(string) + + virtualNetworkId, err := networkParse.VirtualNetworkID(vnetId) + if err != nil { + return err + } + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err := client.GetVnetConnection(ctx, resGroup, appServiceName, virtualNetworkId.Name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("error checking for presence of existing App Service Virtual Network Connection for app %q vnet %q (Resource Group %q): %s", appServiceName, virtualNetworkId.Name, resGroup, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_app_service_virtual_network_connection_gateway", *existing.ID) + } + } + + virtualNetworkGatewayId := d.Get("virtual_network_gateway_id").(string) + gatewayId, err := azure.ParseAzureResourceID(virtualNetworkGatewayId) + if err != nil { + return err + } + gatewayResGroup := gatewayId.ResourceGroup + gatewayName := gatewayId.Path["virtualNetworkGateways"] + virtualNetworkGateway, err := vnetGatewayClient.Get(ctx, gatewayResGroup, gatewayName) + if err != nil { + return fmt.Errorf("error making Read request on AzureRM Virtual Network Gateway %q (Resource Group %q): %+v", gatewayName, gatewayResGroup, err) + } + if virtualNetworkGateway.VpnClientConfiguration == nil || virtualNetworkGateway.VpnClientConfiguration.VpnClientAddressPool == nil { + return fmt.Errorf("this gateways %q under vnet %q (Resource Group %q) does not have a Point-to-site Address Range. Please specify one in CIDR notation, e.g. 10.0.0.0/8", gatewayName, virtualNetworkId.Name, virtualNetworkId.ResourceGroup) + } + + // there are two parameters in the schema: virtual_network_id and virtual_network_gateway_id + // we should check the virtual network gateway is within the virtual network + isRelated, err := checkGatewayInVirtualNetwork(virtualNetworkGateway, vnetId) + if err != nil { + return fmt.Errorf("the virtual network gateway %q is not related with vnet %q: %+v", virtualNetworkGatewayId, virtualNetworkId.Name, err) + } + if !isRelated { + return fmt.Errorf("the virtual network gateway %q is not related with vnet %q", virtualNetworkGatewayId, virtualNetworkId.Name) + } + + // the create functions contains four steps: + // 1. CreateOrUpdateVnetConnection + // 2. result of step 1 contains cert information, we should set the cert to virtual network gateway (check duplicate) + // 3. generate vpn package uri + // 4. CreateOrUpdateVnetConnectionGateway using step 3's result + + connectionEnvelope := web.VnetInfo{ + VnetInfoProperties: &web.VnetInfoProperties{ + VnetResourceID: &vnetId, + }, + } + vnetInfo, err := client.CreateOrUpdateVnetConnection(ctx, resGroup, appServiceName, virtualNetworkId.Name, connectionEnvelope) + if err != nil { + return fmt.Errorf("error creating/updating App Service Virtual Network Connection for app %q vnet %q (Resource Group %q): %+v", appServiceName, virtualNetworkId.Name, resGroup, err) + } + + // add certificate if not exists in the gateway + if err := addCertificateIfNotExistsInGateway(vnetGatewayClient, ctx, &virtualNetworkGateway, &vnetInfo, &virtualNetworkId.ResourceGroup, &gatewayName); err != nil { + return fmt.Errorf("error add certificate for gateway %q: %+v", gatewayName, err) + } + + // generate vpn package uri + packageUri, err := retrieveVPNPackageUri(vnetGatewayClient, ctx, &virtualNetworkId.ResourceGroup, &gatewayName) + if err != nil { + return fmt.Errorf("error get vpn package uri of gateway %q: %+v", gatewayName, err) + } + + vnetGateway := web.VnetGateway{ + VnetGatewayProperties: &web.VnetGatewayProperties{ + VnetName: &virtualNetworkId.Name, + VpnPackageURI: &packageUri, + }, + } + if _, err := client.CreateOrUpdateVnetConnectionGateway(ctx, resGroup, appServiceName, virtualNetworkId.Name, "primary", vnetGateway); err != nil { + return fmt.Errorf("error creating/updating App Service Virtual Network Connection gateway for app %q vnet %q (Resource Group %q): %+v", appServiceName, virtualNetworkId.Name, resGroup, err) + } + + d.SetId(*vnetInfo.ID) + + return resourceArmAppServiceVirtualNetworkConnectionGatewayRead(d, meta) +} + +func resourceArmAppServiceVirtualNetworkConnectionGatewayRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Web.AppServicesClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.AppServiceVirtualNetworkConnectionID(d.Id()) + if err != nil { + return err + } + log.Printf("[DEBUG] Reading Azure App Service Virtual Network Connection gateway %s", id) + + resp, err := client.GetVnetConnection(ctx, id.ResourceGroup, id.AppServiceName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] App Service Virtual Network Connection for app %q vnet %q was not found in Resource Group %q - removing from state!", id.AppServiceName, id.Name, id.ResourceGroup) + d.SetId("") + return nil + } + return fmt.Errorf("error making Read request on App Service Virtual Network Connection for app %q vnet %q (Resource Group %q): %+v", id.AppServiceName, id.Name, id.ResourceGroup, err) + } + + d.Set("resource_group_name", id.ResourceGroup) + d.Set("name", resp.Name) + d.Set("app_service_name", id.AppServiceName) + if props := resp.VnetInfoProperties; props != nil { + d.Set("virtual_network_id", props.VnetResourceID) + d.Set("certificate_thumbprint", props.CertThumbprint) + d.Set("certificate_blob", props.CertBlob) + d.Set("resync_required", props.ResyncRequired) + if props.DNSServers != nil { + d.Set("dns_servers", strings.Split(*props.DNSServers, ",")) + } else { + d.Set("dns_servers", []string{}) + } + } + + return nil +} + +func resourceArmAppServiceVirtualNetworkConnectionGatewayDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Web.AppServicesClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.AppServiceVirtualNetworkConnectionID(d.Id()) + if err != nil { + return err + } + + log.Printf("[DEBUG] Deleting App Service Virtual Network Connection for app %q vnet %q (Resource Group %q)", id.AppServiceName, id.Name, id.ResourceGroup) + resp, err := client.DeleteVnetConnection(ctx, id.ResourceGroup, id.AppServiceName, id.Name) + if err != nil { + if !utils.ResponseWasNotFound(resp) { + return fmt.Errorf("error deleting App Service Virtual Network Connection for app %q vnet %q (Resource Group %q): %+v", id.AppServiceName, id.Name, id.ResourceGroup, err) + } + } + + return nil +} + +func checkGatewayInVirtualNetwork(virtualNetworkGateway network.VirtualNetworkGateway, virtualNetworkId string) (bool, error) { + if virtualNetworkGateway.IPConfigurations == nil || len(*virtualNetworkGateway.IPConfigurations) == 0 { + return false, fmt.Errorf("error get IPConfigurations of virtual network gateway %q", *virtualNetworkGateway.ID) + } + subnetID := (*virtualNetworkGateway.IPConfigurations)[0].Subnet.ID + if subnetID == nil { + return false, fmt.Errorf("error get subnetID of virtual network gateway %q", *virtualNetworkGateway.ID) + } + return strings.HasPrefix(*subnetID, virtualNetworkId) && strings.HasSuffix(*subnetID, "GatewaySubnet"), nil +} + +func addCertificateIfNotExistsInGateway(vnetGatewayClient *network.VirtualNetworkGatewaysClient, ctx context.Context, virtualNetworkGateway *network.VirtualNetworkGateway, vnetInfo *web.VnetInfo, resGroup *string, gatewayName *string) error { + for _, certificate := range *virtualNetworkGateway.VpnClientConfiguration.VpnClientRootCertificates { + if *certificate.PublicCertData == *vnetInfo.VnetInfoProperties.CertBlob { + return nil + } + } + log.Printf("[INFO] Adding certificate for virtual network gateway.") + + certName := fmt.Sprintf("AppServiceCertificate_%d.cer", tf.AccRandTimeInt()) + vpnClientRootCertToAdd := network.VpnClientRootCertificate{ + Name: &certName, + VpnClientRootCertificatePropertiesFormat: &network.VpnClientRootCertificatePropertiesFormat{ + PublicCertData: vnetInfo.VnetInfoProperties.CertBlob, + }, + } + *virtualNetworkGateway.VpnClientConfiguration.VpnClientRootCertificates = append(*virtualNetworkGateway.VpnClientConfiguration.VpnClientRootCertificates, vpnClientRootCertToAdd) + + virtualNetworkGatewaysCreateOrUpdateFuture, err := vnetGatewayClient.CreateOrUpdate(ctx, *resGroup, *gatewayName, *virtualNetworkGateway) + if err != nil { + return fmt.Errorf("error adding cerfiticate for gateway %q (Resource Group %q): %+v", *gatewayName, *resGroup, err) + } + if err = virtualNetworkGatewaysCreateOrUpdateFuture.WaitForCompletionRef(ctx, vnetGatewayClient.Client); err != nil { + return fmt.Errorf("error adding cerfiticate for gateway %q (Resource Group %q): %+v", *gatewayName, *resGroup, err) + } + return nil +} + +func retrieveVPNPackageUri(vnetGatewayClient *network.VirtualNetworkGatewaysClient, ctx context.Context, resGroup *string, gatewayName *string) (packageUri string, err error) { + log.Printf("[INFO] Retrieving VPN Package and supplying to App.") + vpnClientParameters := network.VpnClientParameters{ + ProcessorArchitecture: network.Amd64, + } + virtualNetworkGatewaysGeneratevpnclientpackageFuture, err := vnetGatewayClient.Generatevpnclientpackage(ctx, *resGroup, *gatewayName, vpnClientParameters) + if err != nil { + err = fmt.Errorf("error generating vpn client package for Virtual Network Gateway %q (Resource Group %q): vpnClientParameters %+v %+v", *gatewayName, *resGroup, vpnClientParameters, err) + return + } + if err = virtualNetworkGatewaysGeneratevpnclientpackageFuture.WaitForCompletionRef(ctx, vnetGatewayClient.Client); err != nil { + err = fmt.Errorf("error waiting the result of generating vpn client package for Virtual Network Gateway %q (Resource Group %q): vpnClientParameters %+v %+v", *gatewayName, *resGroup, vpnClientParameters, err) + return + } + packageUri, err = getResult(*vnetGatewayClient, virtualNetworkGatewaysGeneratevpnclientpackageFuture) + if err != nil { + err = fmt.Errorf("error getting result vpn client package for Virtual Network Gateway %q (Resource Group %q): vpnClientParameters %+v %+v", *gatewayName, *resGroup, vpnClientParameters, err) + return + } + if len(packageUri) > 0 && packageUri[0] == '"' && packageUri[len(packageUri)-1] == '"' { + packageUri = packageUri[1 : len(packageUri)-1] + } + return +} + +// a bug of azure go sdk: https://github.com/Azure/azure-sdk-for-go/issues/5921 +// the rest api returns a string, but go sdk Unmarshall with json format +// rewrite the azure go sdk function to work around +func getResult(client network.VirtualNetworkGatewaysClient, future network.VirtualNetworkGatewaysGeneratevpnclientpackageFuture) (str string, err error) { + var done bool + done, err = future.DoneWithContext(context.Background(), client) + if err != nil { + err = autorest.NewErrorWithError(err, "network.VirtualNetworkGatewaysGeneratevpnclientpackageFuture", "Result", future.Response(), "Polling failure") + return + } + if !done { + err = autorestAzure.NewAsyncOpIncompleteError("network.VirtualNetworkGatewaysGeneratevpnclientpackageFuture") + return + } + sender := autorest.DecorateSender(client, autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...)) + var s network.String + if s.Response.Response, err = future.GetResult(sender); err == nil && s.Response.Response.StatusCode != http.StatusNoContent { + str, err = generatevpnclientpackageResponder(client, s.Response.Response) + if err != nil { + err = autorest.NewErrorWithError(err, "network.VirtualNetworkGatewaysGeneratevpnclientpackageFuture", "Result", s.Response.Response, "Failure responding to request") + } + } + return +} + +func generatevpnclientpackageResponder(client network.VirtualNetworkGatewaysClient, resp *http.Response) (result string, err error) { + byteArr := make([]byte, 1024) + err = autorest.Respond( + resp, + client.ByInspecting(), + autorestAzure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusAccepted), + autorest.ByUnmarshallingBytes(&byteArr), + autorest.ByClosing()) + result = string(byteArr) + return +} diff --git a/azurerm/internal/services/web/tests/data_source_app_service_virtual_network_connection_gateway_test.go b/azurerm/internal/services/web/tests/data_source_app_service_virtual_network_connection_gateway_test.go new file mode 100644 index 000000000000..055122e29d87 --- /dev/null +++ b/azurerm/internal/services/web/tests/data_source_app_service_virtual_network_connection_gateway_test.go @@ -0,0 +1,42 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" +) + +func TestAccDataSourceAzureRMAppServiceVirtualNetworkConnectionGateway_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_app_service_virtual_network_connection_gateway", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAppServiceVirtualNetworkConnectionGateway_basic(data), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(data.ResourceName, "id"), + resource.TestCheckResourceAttrSet(data.ResourceName, "name"), + resource.TestCheckResourceAttrSet(data.ResourceName, "certificate_thumbprint"), + resource.TestCheckResourceAttrSet(data.ResourceName, "certificate_blob"), + ), + }, + }, + }) +} + +func testAccDataSourceAppServiceVirtualNetworkConnectionGateway_basic(data acceptance.TestData) string { + config := testAccAzureRMAppServiceVirtualNetworkConnectionGateway_basic(data) + return fmt.Sprintf(` +%s + +data "azurerm_app_service_virtual_network_connection_gateway" "test" { + app_service_name = "${azurerm_app_service_virtual_network_connection_gateway.test.app_service_name}" + resource_group_name = "${azurerm_app_service_virtual_network_connection_gateway.test.resource_group_name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" +} +`, config) +} diff --git a/azurerm/internal/services/web/tests/resource_arm_app_service_virtual_network_connection_gateway_test.go b/azurerm/internal/services/web/tests/resource_arm_app_service_virtual_network_connection_gateway_test.go new file mode 100644 index 000000000000..10f78fa3204a --- /dev/null +++ b/azurerm/internal/services/web/tests/resource_arm_app_service_virtual_network_connection_gateway_test.go @@ -0,0 +1,147 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMAppServiceVirtualNetworkConnectionGateway_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_app_service_virtual_network_connection_gateway", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMFunctionAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAppServiceVirtualNetworkConnectionGateway_basic(data), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(data.ResourceName, "name"), + resource.TestCheckResourceAttrSet(data.ResourceName, "id"), + resource.TestCheckResourceAttrSet(data.ResourceName, "certificate_blob"), + resource.TestCheckResourceAttrSet(data.ResourceName, "certificate_thumbprint"), + ), + }, + data.ImportStep("virtual_network_gateway_id"), + }, + }) +} + +func testCheckAzureRMAppServiceVirtualNetworkConnectionGatewayDestroy(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Web.AppServicesClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_app_service_virtual_network_connection_gateway" { + continue + } + + appServiceName := rs.Primary.Attributes["app_service_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + virtualNetworkId, err := parse.VirtualNetworkID(rs.Primary.Attributes["virtual_network_id"]) + if err != nil { + return err + } + + resp, err := client.GetVnetConnection(ctx, resourceGroup, appServiceName, virtualNetworkId.Name) + + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return nil + } + return err + } + + return nil + } + + return nil +} + +func testAccAzureRMAppServiceVirtualNetworkConnectionGateway_basic(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-appservice-%d" + location = "%s" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_app_service" "test" { + name = "acctestAS-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id +} + +resource "azurerm_virtual_network" "test" { + name = "acctestVnet-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + 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 + + allocation_method = "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" + sku = "Standard" + + 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"] + vpn_client_protocols = ["SSTP"] + } +} + +resource "azurerm_app_service_virtual_network_connection_gateway" "test" { + app_service_name = azurerm_app_service.test.name + resource_group_name = azurerm_resource_group.test.name + virtual_network_id = azurerm_virtual_network.test.id + virtual_network_gateway_id = azurerm_virtual_network_gateway.test.id +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index fe15a0396745..8980e8ec1e33 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -98,6 +98,10 @@ azurerm_app_service_certificate_order +