Public/Deploy/PaaS/web/New-CmAzPaasWeb.ps1

function New-CmAzPaasWeb {

    <#
        .Synopsis
         Create an Frontdoor with backing webapps
 
        .Description
         Completes following:
            * Creates Frontdoor
            * Creates Webapp and attaches to frontdoor
            * Optional API routing available
 
        .Parameter SettingsFile
         File path for the settings file to be converted into a settings object.
 
        .Parameter SettingsObject
         Object containing the configuration values required to run this cmdlet.
 
        .Parameter TagSettingsFile
          File path for the tag settings file to be converted into a tag settings object.
 
        .Component
         PaaS
 
        .Example
         New-CmAzPaasWeb -SettingsFile ./web.yml
 
        .Example
         New-CmAzPaasWeb -SettingsObject $settings
 
        .Example
         New-CmAzPaasWeb -SettingsObject $settings -TagSettingFile ./tag.yml
    #>


    [OutputType([System.Collections.ArrayList])]
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Medium")]
    param(
        [parameter(Mandatory = $true, ParameterSetName = "Settings File")]
        [String]$SettingsFile,
        [parameter(Mandatory = $true, ParameterSetName = "Settings Object")]
        [Object]$SettingsObject,
        [String]$TagSettingsFile
    )

    $ErrorActionPreference = "Stop"

    try {

        Get-InvocationInfo -CommandName $MyInvocation.MyCommand.Name

        if ($PSCmdlet.ShouldProcess((Get-CmAzSubscriptionName), "Deploy Azure - Frontdoor | Backendpool | Webapps along with routing rules")) {

            if ($SettingsFile -and -not $SettingsObject) {
                $SettingsObject = Get-CmAzSettingsFile -Path $SettingsFile
            }
            elseif (-not $SettingsFile -and -not $SettingsObject) {
                Write-Error "No valid input settings." -Category InvalidArgument -CategoryTargetName "SettingsObject"
            }

            $azContext = Get-AzContext

            $applicationInstrumentationKey = ""

            if ($SettingsObject.service.dependencies.appInsights) {

                Write-Verbose "Fetching appinsights instrumentation key..."
                $appInsightsService = Get-CmAzService -Service $SettingsObject.service.dependencies.appInsights -ThrowIfUnavailable -ThrowIfMultiple
                $appInsights = Get-AzApplicationInsights -ResourceGroupName $appInsightsService.resourceGroupName -Name $appInsightsService.name

                $applicationInstrumentationKey = $appInsights.InstrumentationKey
            }

            [System.Collections.ArrayList]$resourceGroupsToSet = @()

            function New-ResourceGroup() {

                param(
                    $resourceGroupName,
                    $ResourceServiceContainer,
                    $GlobalServiceContainer,
                    $Region,
                    $ServiceKey
                )

                $generatedResourceGroupName = Get-CmAzResourceName -Resource "ResourceGroup" -Architecture "PaaS" -Region $Region -Name $resourceGroupName

                Set-GlobalServiceValues -GlobalServiceContainer $GlobalServiceContainer -ServiceKey $serviceKey -ResourceServiceContainer $ResourceServiceContainer > $null
                New-AzResourceGroup -ResourceGroupName $generatedResourceGroupName -Location $Region -Tag @{ "cm-service" = $ResourceServiceContainer.service.publish.resourceGroup } -Force > $null

                $resourceGroupsToSet.Add($generatedResourceGroupName) > $Null

                return $generatedResourceGroupName
            }

            # Crawl across SettingsObject and create defined webapps

            foreach ($webSolution in $SettingsObject.WebSolutions) {

                if ($webSolution.AppServicePlans.Region.count -gt 1) {
                    $region = $webSolution.AppServicePlans[0].Region
                }
                else {
                    $region = $webSolution.AppServicePlans.Region
                }

                if (!$region) {
                    if ($webSolution.apiManagementServices.Region.count -gt 1) {
                        $region = $webSolution.apiManagementServices[0].Region
                    }
                    else {
                        $region = $webSolution.apiManagementServices.Region
                    }
                }

                $webSolution.generatedResourceGroupName = New-ResourceGroup `
                    -ResourceGroupName $webSolution.Name `
                    -GlobalServiceContainer $SettingsObject `
                    -ResourceServiceContainer $webSolution `
                    -Region $region `
                    -ServiceKey "resourceGroup"

                if ($webSolution.AppServicePlans) {

                    foreach ($appServicePlan in $webSolution.AppServicePlans) {

                        Set-GlobalServiceValues -GlobalServiceContainer $SettingsObject -ServiceKey "appServicePlan" -ResourceServiceContainer $appServicePlan
                        $appServicePlan.name = Get-CmAzResourceName -Resource "AppServicePlan" -Architecture "PaaS" -Region $appServicePlan.region -Name $appServicePlan.name

                        if (!$appServicePlan.kind) {
                            $appServicePlan.kind = "linux"
                        }

                        if (!$appServicePlan.region) {
                            Write-Error "Please provide region for service plan."
                        }

                        $appServicePlan.resourceGroupName = $webSolution.generatedResourceGroupName

                        foreach ($webapp in $appServicePlan.webapps) {

                            Write-Verbose "Generating Object for deployment of webapp : $($webapp.name)..."

                            if (!$webapp.region) {
                                $webapp.region = $appServicePlan.region
                            }

                            $webapp.webAppGeneratedName = Get-CmAzResourceName -Resource "WebApp" -Architecture "PaaS" -Region $webapp.region -Name $webapp.name

                            Set-GlobalServiceValues -GlobalServiceContainer $SettingsObject -ServiceKey "webapp" -ResourceServiceContainer $webapp

                            if (!$webapp.Slots) {
                                $webapp.slotsObject = @(@{ name = "none"; service = @{ publish = @{ slot = $null } } })
                            }
                            else {
                                $webapp.slotsObject = [System.Collections.ArrayList]@()

                                foreach ($slot in $webapp.Slots) {

                                    if ($slot -Is [System.String]) {
                                        $slotObject = @{ Name = $slot }
                                    }
                                    else {
                                        $slotObject = $slot
                                    }

                                    Set-GlobalServiceValues -GlobalServiceContainer $SettingsObject -ServiceKey "slot" -ResourceServiceContainer $slotObject

                                    Write-Verbose "Adding slot: $($slotObject.name) to webapp : $($webapp.Name)..."
                                    $webapp.slotsObject.Add($slotObject) > $null
                                }
                            }

                            if ($webapp.contentDeliveryNetwork.Name) {

                                $webapp.contentDeliveryNetwork.Name = Get-CmAzResourceName -Resource "CdnProfile" -Architecture "PaaS" -Region $webapp.contentDeliveryNetwork.region -Name $webapp.contentDeliveryNetwork.Name

                                Set-GlobalServiceValues -GlobalServiceContainer $SettingsObject -ServiceKey "cdn" -ResourceServiceContainer $webapp.contentDeliveryNetwork
                                Set-GlobalServiceValues -GlobalServiceContainer $SettingsObject -ServiceKey "endpoint" -ResourceServiceContainer $webapp.contentDeliveryNetwork
                            }
                            else {
                                $webapp.contentDeliveryNetwork = @{
                                    name    = "none";
                                    sku     = "standard_microsoft";
                                    region  = "global";
                                    service = @{
                                        publish = @{
                                            cdn      = $null;
                                            endpoint = $null
                                        }
                                    }
                                }
                            }

                            if ($webapp.enableAppInsight) {
                                $webapp.applicationInstrumentationKey = $applicationInstrumentationKey
                            }
                            else {
                                $webapp.applicationInstrumentationKey = ""
                            }
                        }
                    }
                }

                if ($webSolution.ApiManagementServices ) {

                    $webSolution.ApiManagementServices | forEach-Object {

                        if (!$_.Name -or !$_.publisherName -or !$_.publisherEmail -or !$_.Sku ) {
                            Write-Error "Api gateway is missing mandatory parameters. Please provide name, region, organization, admin email and sku."
                        }

                        if (!$_.region) {
                            Write-Error "Please provide region for api management service"
                        }

                        Set-GlobalServiceValues -GlobalServiceContainer $SettingsObject -ServiceKey "apiManagement" -ResourceServiceContainer $_

                        $_.resourceGroupName = $webSolution.generatedResourceGroupName

                        if (!$_.skucount) {
                            $_.skucount = 1
                        }

                        $_.generatedName = Get-CmAzResourceName -Resource "APImanagementServiceInstance" -Architecture "PaaS" -Region $_.Region -Name $_.Name
                    }


                }
            }

            if ($SettingsObject.WebSolutions.appServicePlans) {

                if ($SettingsObject.WebSolutions.AppServicePlans.Region.count -gt 1) {
                    $region = $SettingsObject.WebSolutions.AppServicePlans[0].Region
                }
                else {
                    $region = $SettingsObject.WebSolutions.AppServicePlans.Region
                }

                [System.Collections.ArrayList]$AppServiceDetails = @()
                $AppServiceDetails += $SettingsObject.WebSolutions.AppServicePlans

                Write-Verbose "Initiating deployment of webapps..."

                New-AzDeployment  `
                    -Name "CmAzWebStack_Parent" `
                    -Location $region `
                    -TemplateFile "$PSScriptRoot\New-CmAzPaasWeb-Webapp.json" `
                    -AppServiceDetails $AppServiceDetails
            }

            if ($SettingsObject.WebSolutions.ApiManagementServices) {

                if ($SettingsObject.WebSolutions.ApiManagementServices.Region.count -gt 1) {
                    $region = $SettingsObject.WebSolutions.ApiManagementServices[0].Region
                }
                else {
                    $region = $SettingsObject.WebSolutions.ApiManagementServices.Region
                }

                [System.Collections.ArrayList]$ApiManagementServices = @()
                $ApiManagementServices += $SettingsObject.WebSolutions.ApiManagementServices

                Write-Verbose "Initiating deployment of api management services..."

                New-AzDeployment  `
                    -Name "CmAzApiManagementServices_Parent" `
                    -Location $region `
                    -TemplateFile "$PSScriptRoot\New-CmAzPaasWeb-ApiManagementServices.json" `
                    -ApiManagementServices $ApiManagementServices
            }


            if ($SettingsObject.frontdoor) {

                # Frontdoor configuration:
                $frontendEndpointObjectArray = [System.Collections.ArrayList]@()

                $frontdoorRG = New-ResourceGroup `
                    -ResourceGroupName $SettingsObject.frontdoor.name `
                    -GlobalServiceContainer $SettingsObject `
                    -ResourceServiceContainer $SettingsObject.frontdoor `
                    -Region $SettingsObject.frontdoor.region `
                    -ServiceKey "frontDoorResourceGroup"

                Write-Verbose "Initiating compilation of Frontends..."

                $frontdoorSuppliedHostName = $SettingsObject.frontdoor.name
                $SettingsObject.frontdoor.name = Get-CmAzResourceName -Resource "FrontDoor" -Architecture "PaaS" -Region $SettingsObject.frontdoor.region -Name $SettingsObject.frontdoor.name

                # Frontdoor Front endpoints configuration

                if ($SettingsObject.frontdoor.service.dependencies.webApplicationFirewallPolicy) {

                    $webApplicationFirewallPolicyService = @{
                        id = (Get-CmAzService -Service $SettingsObject.frontdoor.service.dependencies.webApplicationFirewallPolicy -ThrowIfUnavailable -ThrowIfMultiple).ResourceId
                    }
                }

                $frontendEndpointObjectMain = @{
                    frontEndName                           = $SettingsObject.frontdoor.name;
                    domainName                             = "$($SettingsObject.frontdoor.name).azurefd.net"
                    sessionAffinity                        = $SettingsObject.frontdoor.sessionAffinity
                    webApplicationFirewallPolicyResourceId = $webApplicationFirewallPolicyService
                }

                Write-Verbose "Frontdoor Azure FQDN: $($SettingsObject.frontdoor.name).azurefd.net..."

                $frontendEndpointObjectArray.Add($frontendEndpointObjectMain) > $Null

                if ($SettingsObject.frontDoor.customDomains.domainName) {

                    $SettingsObject.frontDoor.customDomains | forEach-Object {

                        Write-Verbose "Adding $($_.domainName) to FrontDoor endpoint configuration..."

                        if ($_.service.dependencies.webApplicationFirewallPolicy) {
                            $CustomWebApplicationFirewallPolicyService = @{
                                id = (Get-CmAzService -Service $_.service.dependencies.webApplicationFirewallPolicy -ThrowIfUnavailable -ThrowIfMultiple).ResourceId
                            }
                        }

                        $frontendEndpointObjectCustom = @{
                            frontEndName                           = $_.domainName.replace('.', '-');
                            domainName                             = $_.domainName
                            sessionAffinity                        = $_.sessionAffinity
                            webApplicationFirewallPolicyResourceId = $CustomWebApplicationFirewallPolicyService
                        }

                        $frontendEndpointObjectArray.add($frontendEndpointObjectCustom) > $null
                    }
                }

                $SettingsObject.FrontDoor.frontendEndpoints = $frontendEndpointObjectArray

                # Frontdoor backendpools configuration

                Write-Verbose "Initiating compilation of Backend Pools..."

                foreach ($backEndPool in $SettingsObject.frontDoor.backEndPools) {

                    if (($SettingsObject.Websolutions.appServicePlans.webapps.backendpool -contains $backEndPool.Name) -or ($SettingsObject.Websolutions.ApiManagementServices.backendPool -contains $backEndPool.Name)) {

                        Write-Verbose "Adding backend pool: $($backEndPool.Name) to FrontDoor backend configuration..."

                        $backEndPool.backends = [System.Collections.ArrayList]@()

                        $SettingsObject.Websolutions.appServicePlans.webapps | Where-Object { $_.backendpool -match $backEndPool.Name } | forEach-Object {

                            $address = "$($_.webAppGeneratedName).azurewebsites.net"
                            $backendHostHeader = $address

                            if ($_.backendHostHeader -eq $false) {
                                $backendHostHeader = ""
                            }

                            if (!$_.weight) {
                                $_.weight = 100
                            }

                            if (!$_.priority) {
                                $_.priority = 1
                            }

                            $backEndObject = @{
                                Address           = $address;
                                httpPort          = 80;
                                httpsPort         = 443;
                                weight            = $_.weight ;
                                priority          = $_.priority ;
                                enabledState      = "Enabled";
                                backendHostHeader = $backendHostHeader
                            }

                            $backEndPool.backends.Add($backEndObject) > $null
                        }

                        $SettingsObject.Websolutions.ApiManagementServices | Where-Object { $_.backendPool -eq $backEndPool.Name } | forEach-Object {

                            $address = "$($_.generatedName).azure-api.net"
                            $backendHostHeader = $address

                            if ($_.backendHostHeader -eq $false) {
                                $backendHostHeader = ""
                            }

                            if (!$_.weight) {
                                $_.weight = 100
                            }

                            if (!$_.priority) {
                                $_.priority = 1
                            }

                            $backEndObject = @{
                                Address           = $address;
                                httpPort          = 80;
                                httpsPort         = 443;
                                weight            = $_.weight ;
                                priority          = $_.priority ;
                                enabledState      = "Enabled";
                                backendHostHeader = $backendHostHeader
                            }

                            $backEndPool.backends.Add($backEndObject) > $null
                        }

                        if (!$backEndPool.healthCheckPath) {
                            $backEndPool.healthCheckPath = "/index.html"
                        }

                        if (!$backEndPool.protocol) {
                            $backEndPool.protocol = "Https"
                        }
                        elseif ($backEndPool.protocol -ne "Https" -and $backEndPool.protocol -ne "Http") {
                            Write-Error "Invalid backend pool protocol." -Category InvalidArgument -CategoryTargetName "SettingsObject.frontdoor.backendPools.protocol"
                        }
                    }
                    else {
                        Write-Error "Backend pool: $($backEndPool.Name) not found" -Category ObjectNotFound -TargetObject $backEndPool.Name
                    }
                }

                # Frondoor routing rules configuration

                Write-Verbose "Initiating compilation of routing rules..."

                foreach ($routingRule in $SettingsObject.frontDoor.rules) {

                    Write-Verbose "Adding backend pool: $($routingRule.Name) to FrontDoor routing rule configuration..."

                    $routingRule.frontendEndpoints = [System.Collections.ArrayList]@()

                    if (!$routingRule.endpoints){
                        $routingRule.endpoints = @($frontdoorSuppliedHostName)
                    }

                    foreach ($endpoint in $routingRule.endpoints) {

                        if ($endpoint -match $frontdoorSuppliedHostName) {
                            $endpoint = $SettingsObject.frontdoor.name
                        }
                        else {
                            $endpoint = $endpoint.replace('.', '-')
                        }

                        $frontEndPointObject = @{
                            id = "/subscriptions/$($azContext.Subscription.Id)/resourcegroups/$frontdoorRG/providers/Microsoft.Network/Frontdoors/$($SettingsObject.frontdoor.name)/FrontendEndpoints/$endpoint"
                        }

                        $routingRule.frontendEndpoints.Add($frontEndPointObject) > $Null
                    }

                    if (!$routingRule.acceptedProtocols) {
                        $routingRule.acceptedProtocols = @("Https", "Http")
                    }

                    if ($routingRule.enableCaching) {
                        $routingRule.cacheConfiguration = @{
                            queryParameterStripDirective = "StripNone";
                            dynamicCompression           = "Enabled"
                        }
                    } else {
                        $routingRule.cacheConfiguration = $null
                    }
                }

                New-AzResourceGroupDeployment  `
                    -Name "CmAz_Frontdoor" `
                    -ResourceGroupName $frontdoorRG `
                    -TemplateFile "$PSScriptRoot\New-CmAzPaasWeb-Frontdoor.json" `
                    -Frontdoor $SettingsObject.frontdoor `
                    -FrontdoorService $SettingsObject.service.publish.frontDoor `
                    -Verbose

                if ($SettingsObject.frontDoor.customDomains.domainName) {

                    function CustomDomainOnFrontDoorEnableHttps {
                        param(
                            $CustomDomainsObject
                        )

                        if (!$CustomDomainsObject.customCertificateSecretName) {

                            Write-Verbose "Enabling TLS with Azure Frontdoor managed certificates on Domain: $($CustomDomainsObject.domainName)..."

                            Enable-AzFrontDoorCustomDomainHttps `
                                -ResourceGroupName $frontdoorRG `
                                -FrontDoorName $SettingsObject.frontdoor.name `
                                -FrontendEndpointName $CustomDomainsObject.domainName.replace('.', '-') `
                                -MinimumTlsVersion "1.2"
                        }
                        else {

                            Write-Verbose "Enabling TLS with Azure custom certificates on Domain: $($CustomDomainsObject.domainName)..."

                            Enable-AzFrontDoorCustomDomainHttps `
                                -ResourceGroupName $frontdoorRG `
                                -FrontDoorName $SettingsObject.frontdoor.name `
                                -FrontendEndpointName $CustomDomainsObject.domainName.replace('.', '-')`
                                -Vault $CustomDomainsObject.vaultService.ResourceId `
                                -SecretName $CustomDomainsObject.customCertificateSecretName `
                                -SecretVersion $CustomDomainsObject.customCertificateSecretVersion `
                                -MinimumTlsVersion "1.2"

                        }
                    }

                    $customDomainStatus = Get-AzFrontDoorFrontendEndpoint -FrontDoorName $SettingsObject.frontdoor.name -ResourceGroupName $frontdoorRG

                    $SettingsObject.frontDoor.customDomains | Where-Object { $_.enableHttps -eq $true } | forEach-Object {

                        if ($_.customCertificateSecretName) {

                            Set-GlobalServiceValues -GlobalServiceContainer $SettingsObject -ServiceKey "keyvault" -ResourceServiceContainer $_ -Isdependency
                            $_.vaultService = Get-CmAzService -Service $_.service.dependencies.keyvault -ThrowIfUnavailable -ThrowIfMultiple

                            if (!$_.customCertificateSecretVersion) {
                                $_.customCertificateSecretVersion = (Get-AzKeyVaultSecret -VaultName $_.vaultService.Name -Name $_.customCertificateSecretName).Version
                            }
                        }

                        $domainName = $_.domainName

                        if (($customDomainStatus | Where-Object { $_.Hostname -eq $domainName }).CustomHttpsProvisioningState -eq "Disabled") {
                            CustomDomainOnFrontDoorEnableHttps -CustomDomainsObject $_
                        }
                        else {
                            Write-Verbose "$($_.domainName) already has TLS enabled and certificates deployed..."
                            $customDomainStatus | Where-Object { $_.Hostname -eq $domainName }
                        }
                    }
                }
            }

            Set-DeployedResourceTags -TagSettingsFile $TagSettingsFile -ResourceGroupIds $resourceGroupsToSet

            Write-Verbose "Finished!"
        }
    }
    catch {
        $PSCmdlet.ThrowTerminatingError($PSitem);
    }
}