Public/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.
 
        .Component
         PaaS
 
        .Example
         New-CmAzPaasWeb -SettingsFile ./web.yml
 
        .Example
         New-CmAzPaasWeb -SettingsObject $settings
    #>


    [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
    )

    $ErrorActionPreference = "Stop"

    try {

        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"
            }

            $permanentPSScriptRoot = $PSScriptRoot
            $resourceGroupName = Get-CmAzResourceName -Resource "ResourceGroup" -Architecture "PaaS" -Region $SettingsObject.location -Name $SettingsObject.resourceGroupName
            $frontdoorName = Get-CmAzResourceName -Resource "FrontDoor" -Architecture "PaaS" -Region $SettingsObject.location -Name $SettingsObject.frontdoor.hostName
            $applicationInstrumentationKey = "none"

            if ($SettingsObject.monitoring.applicationInstrumentationKey) {
                $applicationInstrumentationKey = $SettingsObject.monitoring.applicationInstrumentationKey
            }

            New-AzResourceGroup -ResourceGroupName $resourceGroupName -Location $SettingsObject.location -Tag @{ "cm-service" = $SettingsObject.ResourceGroupTag } -Force

            $frontdoorCheck = Get-AzFrontDoor -Name $frontdoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue

            if ($frontdoorCheck) {
                Write-Verbose "Frontdoor by the name $($frontdoorName) already exists."
                Remove-AzFrontDoor -Name $frontdoorName -ResourceGroupName $resourceGroupName
            }

            # Crawl across SettingsObject and create defined webapps
            $SettingsObject.AppServicePlans | ForEach-Object -Parallel {

                $threadResourceGroupName = $using:resourceGroupName
                $threadPermanentPSScriptRoot = $using:permanentPSScriptRoot
                $threadApplicationInstrumentationKey = $using:applicationInstrumentationKey

                foreach ($app in $_.Webapps) {

                    Write-Verbose "Initiating deployment of webapp : $($app.Name)"
                    $_.Name = Get-CmAzResourceName -Resource "AppServicePlan" -Architecture "PaaS" -Region $_.Region -Name $_.Name
                    $app.generatedName = Get-CmAzResourceName -Resource "WebApp" -Architecture "PaaS" -Region $_.Region -Name $app.Name

                    New-AzResourceGroupDeployment  `
                        -Name $app.generatedName `
                        -ResourceGroupName $threadResourceGroupName `
                        -TemplateFile "$threadPermanentPSScriptRoot\New-CmAzPaasWeb.json" `
                        -WebAppName $app.generatedName `
                        -Kind "linux" `
                        -LinuxFxVersion $app.Runtime `
                        -AppServicePlanName $_.Name `
                        -Sku $_.Sku `
                        -Location $_.Region `
                        -StagingSlotName ($app.Slots).ToArray() `
                        -AppInstrumatationKey $threadApplicationInstrumentationKey `
                        -Force `
                        -Verbose

                    Write-Verbose "$($app.Name) is created"
                }
            }

            # Create FrontendEndpoint Object
            $SettingsObject.ApiManagementServices | ForEach-Object -Parallel {

                if (!$_.Name -or !$_.Region -or !$_.Organization -or !$_.AdminEmail -or !$_.Sku ) {
                    Continue
                }

                $threadResourceGroupName = $using:resourceGroupName

                try {

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

                    Write-Verbose "Creating ApiManagementService $($_.Name)"
                    New-AzApiManagement `
                        -ResourceGroupName $threadResourceGroupName `
                        -Location $_.Region `
                        -Name $_.Name `
                        -Organization $_.Organization `
                        -AdminEmail $_.AdminEmail `
                        -Sku $_.Sku

                    while (!(([system.uri](Get-AzApiManagement -Name $_.Name -ResourceGroupName $threadResourceGroupName).RuntimeUrl).Host)) {
                        Start-Sleep -minutes 5
                        Write-Verbose "Waiting for API To generate URL....."
                    }
                }
                catch {
                    Write-Error "An error occurred, The API service is potentially already present" -ErrorAction Continue
                }

                $url = ([system.uri](Get-AzApiManagement -Name $_.Name -ResourceGroupName $threadResourceGroupName).RuntimeUrl).Host
                Write-Verbose "Api url: $($url)"
            }

            function CustomDomainOnFrontDoorEnableHttps {
                param(
                    $vaultName,
                    $domainName,
                    $secretName,
                    $resourceGroupName,
                    $frontdoorName
                )

                if (!$vaultName) {

                    Enable-AzFrontDoorCustomDomainHttps `
                        -ResourceGroupName $resourceGroupName `
                        -FrontDoorName $frontdoorName `
                        -FrontendEndpointName ($frontendEndpointObjectArray | Where-Object Hostname -eq $domainName).Name `
                        -MinimumTlsVersion "1.2"
                }
                else {
                    Enable-AzFrontDoorCustomDomainHttps `
                        -ResourceGroupName $resourceGroupName `
                        -FrontDoorName $frontdoorName `
                        -FrontendEndpointName ($frontendEndpointObjectArray | Where-Object Hostname -eq $domainName).Name `
                        -Vault (Get-AzKeyVault -VaultName $vaultName).ResourceId `
                        -SecretName $secretName `
                        -SecretVersion (Get-AzKeyVaultSecret -VaultName kvaultcorerg -Name $secretName).Version `
                        -MinimumTlsVersion "1.0"
                }
            }

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

            function SetFrontendEndpointObject {
                param (
                    $domainName,
                    $sessionAffinity,
                    $webApplicationFirewallPolicy,
                    $name
                )

                if ($sessionAffinity) {
                    $sessionAffinity = "Enabled"
                }
                else {
                    $sessionAffinity = "Disabled"
                }

                Write-Verbose "Initiating creation of frontend endpoint object.."
                if ($SettingsObject.frontDoor.webApplicationFirewallPolicy) {

                    $frontendEndpointObject = New-AzFrontDoorFrontendEndpointObject `
                        -Name $name `
                        -HostName $domainName `
                        -SessionAffinityEnabledState $sessionAffinity `
                        -WebApplicationFirewallPolicyLink $webApplicationFirewallPolicy
                }
                else {

                    $frontendEndpointObject = New-AzFrontDoorFrontendEndpointObject `
                        -Name $name `
                        -HostName $domainName `
                        -SessionAffinityEnabledState $sessionAffinity
                }

                $frontendEndpointObject
            }

            $frontendEndpointObjectMain = SetFrontendEndpointObject `
                    -Name $frontdoorName `
                    -DomainName "$frontdoorName.azurefd.net" `
                    -SessionAffinity $SettingsObject.frontDoor.sessionAffinity `
                    -WebApplicationFirewallPolicy $SettingsObject.frontDoor.webApplicationFirewallPolicy

            $frontendEndpointObjectArray.add($frontendEndpointObjectMain) > $null

            Write-Verbose "Frontend Local Hostname:"
            $frontendEndpointObjectMain | Write-Verbose

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

                foreach ($domain in $SettingsObject.frontDoor.customDomains) {

                    Write-Verbose "Adding $($domain.domainName) Object"

                    $name = (Get-CmAzResourceName -Resource "frontDoor" -Architecture "Core" -Region $SettingsObject.location -Name "frontendCustomObject")

                    $frontendEndpointObjectCustom = SetFrontendEndpointObject `
                        -Name $name `
                        -Domain $domain.domainName `
                        -SessionAffinity $domain.sessionAffinity `
                        -WebApplicationFirewallPolicy $domain.webApplicationFirewallPolicy

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

                $frontendEndpointObjectArray
            }

            # Create Back end pool Object
            Write-Verbose "Initiating creation of Backend Pool"
            $backEndPoolObjectArray = [System.Collections.ArrayList]@()
            $healthProbeSettingObjectArray = [System.Collections.ArrayList]@()
            $loadBalancingSettingObjectArray = [System.Collections.ArrayList]@()
            $routingRuleObjectArray = [System.Collections.ArrayList]@()

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

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

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

                    $backEndDomainName = (Get-AzWebApp -ResourceGroupName $resourceGroupName -Name  $_.generatedName).DefaultHostName

                    $backendHostHeader = $backEndDomainName

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

                    $backEndObject = New-AzFrontDoorBackendObject -Address $backEndDomainName -BackendHostHeader $backendHostHeader
                    $backendObjectArray.Add($backEndObject) > $null
                }

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

                    $backEndDomainName = ([system.uri](Get-AzApiManagement -ResourceGroupName $resourceGroupName -Name $_.name).RuntimeUrl).Host

                    $backendHostHeader = $backEndDomainName

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

                    $backEndObject = New-AzFrontDoorBackendObject -Address $backEndDomainName -BackendHostHeader $backendHostHeader
                    $backendObjectArray.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"
                }

                $healthProbeSettingObject = New-AzFrontDoorHealthProbeSettingObject -Name "HealthProbeSetting-$($backEndPool.Name)" -Path  $backEndPool.HealthCheckPath -Protocol $backEndPool.protocol
                $loadBalancingSettingObject = New-AzFrontDoorLoadBalancingSettingObject -Name "Loadbalancingsetting-$($backEndPool.Name)"

                $backEndPoolObject = New-AzFrontDoorBackendPoolObject -Name $backEndPool.Name `
                    -FrontDoorName $frontdoorName `
                    -ResourceGroupName $resourceGroupName `
                    -Backend $backendObjectArray `
                    -HealthProbeSettingsName "HealthProbeSetting-$($backEndPool.Name)" `
                    -LoadBalancingSettingsName "Loadbalancingsetting-$($backEndPool.Name)"

                Write-Verbose "Backend Pool Object Created for $($backEndPool.Name)"

                $backEndPoolObjectArray.Add($backEndPoolObject) > $null
                $healthProbeSettingObjectArray.Add($healthProbeSettingObject) > $null
                $loadBalancingSettingObjectArray.Add($loadBalancingSettingObject) > $null
            }

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

                $backendPool = $SettingsObject.frontDoor.backEndPools | Where-Object { $_.Name -eq $rule.backendPoolName } | Select-Object -First 1

                if(!$backEndPool) {
                    Write-Error "Backend pool $($rule.backendPoolName) does not exist" -Category InvalidArgument -CategoryTargetName "SettingsObject.frontdoor.rules.backendPoolName"
                }

                foreach ($endpointObject in $frontendEndpointObjectArray) {

                    $routingRuleObject = New-AzFrontDoorRoutingRuleObject `
                        -Name $rule.Name `
                        -FrontDoorName $frontdoorName `
                        -ResourceGroupName $resourceGroupName `
                        -FrontendEndpointName $endpointObject.Name `
                        -BackendPoolName  $backendPool.Name `
                        -PatternToMatch  $rule.Pattern
                }

                $routingRuleObjectArray.Add($routingRuleObject) > $null
            }

            # Create Frontdoor
            Write-Verbose "Initiating creation of Azure frontdoor"
            $newAzFrontdoor = New-AzFrontDoor `
                -Name $frontdoorName `
                -ResourceGroupName $resourceGroupName `
                -FrontendEndpoint $frontendEndpointObjectArray `
                -BackendPool $backEndPoolObjectArray `
                -HealthProbeSetting $healthProbeSettingObjectArray `
                -LoadBalancingSetting $loadBalancingSettingObjectArray `
                -RoutingRule $routingRuleObjectArray

            $newAzFrontdoor | Write-Verbose

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

                foreach ($domain in $SettingsObject.frontDoor.customDomains) {

                    $enableHttps = CustomDomainOnFrontDoorEnableHttps `
                        -DomainName $domain.domainName  `
                        -VaultName $domain.customCertificateVaultName  `
                        -SecretName $domain.customCertificateSecretName  `
                        -ResourceGroupName $resourceGroupName `
                        -FrontDoorName $frontdoorName

                    $enableHttps | Write-Verbose
                }
            }

            if ($SettingsObject.contentDeliveryNetwork.Name) {

                $resourceType = (Get-AzResource -ResourceGroupName $resourceGroupName -Name $SettingsObject.contentDeliveryNetwork.attachObjectName -ErrorAction SilentlyContinue).ResourceType

                Write-Verbose "Resource type identified to be : $($resourceType)"

                if ($resourceType -eq "Microsoft.Storage/storageAccounts") {
                    $domain = ([system.uri](New-AzStorageContext -StorageAccountName  $SettingsObject.contentDeliveryNetwork.attachObjectName).BlobEndPoint).Host
                }
                elseif ($resourceType -eq "Microsoft.Web/sites") {
                    $domain = (Get-AzWebApp -ResourceGroupName $resourceGroupName -Name  $SettingsObject.contentDeliveryNetwork.attachObjectName).DefaultHostName
                }
                else {
                    Write-Verbose "$($SettingsObject.contentDeliveryNetwork.attachObjectName) not found "
                    break
                }

                # Create a new profile and endpoint in one line
                $cdn = New-AzCdnProfile -ProfileName $SettingsObject.contentDeliveryNetwork.Name -ResourceGroupName $resourceGroupName -Sku $SettingsObject.contentDeliveryNetwork.Sku -Location $SettingsObject.location | `
                New-AzCdnEndpoint -EndpointName $SettingsObject.contentDeliveryNetwork.Name -OriginName $SettingsObject.contentDeliveryNetwork.attachObjectName -OriginHostName $domain

                $cdn | Write-Verbose
            }

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