diff --git a/.github/workflows/reusable-apply-infrastructure.yaml b/.github/workflows/reusable-apply-infrastructure.yaml index 43fc519..fa50b35 100644 --- a/.github/workflows/reusable-apply-infrastructure.yaml +++ b/.github/workflows/reusable-apply-infrastructure.yaml @@ -47,9 +47,11 @@ jobs: with: scope: subscription subscriptionId: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - template: ./infrastructure/websiteDeploy.bicep + template: ./infrastructure/website.bicep deploymentName: deploy-${{ inputs.application }} - parameters: "resourceGroupSuffix=${{ env.GITHUB_REF_NAME_SLUG }} application=${{ inputs.application }}" + parameters: >- + ./infrastructure/dotnet.bicepparam + resourceGroupSuffix=${{ env.GITHUB_REF_NAME_SLUG }} region: westeurope - name: Comment Website FQDNs on Pull Requests diff --git a/infrastructure/dotnet.bicepparam b/infrastructure/dotnet.bicepparam new file mode 100644 index 0000000..fdc2faf --- /dev/null +++ b/infrastructure/dotnet.bicepparam @@ -0,0 +1,34 @@ +using './website.bicep' + +param resourceGroupSuffix = 'main' +param application = 'dotnet' +param hostnames = [ + { + dnsZoneName: 'xprtz.nl' + hostname: '' + } + { + dnsZoneName: 'xprtz.nl' + hostname: 'www' + } + { + dnsZoneName: 'xprtz.cloud' + hostname: '' + } + { + dnsZoneName: 'xprtz.cloud' + hostname: 'www' + } + { + dnsZoneName: 'xprtz.net' + hostname: '' + } + { + dnsZoneName: 'xprtz.net' + hostname: 'www' + } +] +param imageHostname = { + dnsZoneName: 'xprtz.nl' + hostname: 'images' +} diff --git a/infrastructure/modules/dns.bicep b/infrastructure/modules/dns.bicep index eb014ba..fb9058e 100644 --- a/infrastructure/modules/dns.bicep +++ b/infrastructure/modules/dns.bicep @@ -1,18 +1,19 @@ -import { domainsType, validationTokenType } from '../types.bicep' +import { hostnameType, validationTokenType } from '../types.bicep' +@description('The dns record details') +param hostname hostnameType +@description('The ID of the Front Door endpoint to link the DNS records to.') param frontDoorEndpointId string -param origin string -param domains domainsType[] -param validationTokens validationTokenType[] -param deployApexRecord bool = true - -var cnames = filter(domains, domain => domain.subDomain != '') +@description('The frontdoor hostname to be used for the DNS CNAME records.') +param frontDoorHostname string +@description('The validation token for DNS verification.') +param validationToken validationTokenType resource dnsZone 'Microsoft.Network/dnsZones@2023-07-01-preview' existing = { - name: domains[0].rootDomain + name: hostname.dnsZoneName } -resource apexRecord 'Microsoft.Network/dnszones/A@2023-07-01-preview' = if(deployApexRecord) { +resource apexRecord 'Microsoft.Network/dnszones/A@2023-07-01-preview' = if (empty(hostname.hostname)) { parent: dnsZone name: '@' properties: { @@ -23,25 +24,20 @@ resource apexRecord 'Microsoft.Network/dnszones/A@2023-07-01-preview' = if(deplo } } -@batchSize(1) -resource cnameRecord 'Microsoft.Network/dnsZones/CNAME@2023-07-01-preview' = [for domain in cnames: { +resource cnameRecord 'Microsoft.Network/dnsZones/CNAME@2023-07-01-preview' = if (!empty(hostname.hostname)) { parent: dnsZone - name: domain.subDomain + name: hostname.hostname properties: { TTL: 3600 CNAMERecord: { - cname: origin + cname: frontDoorHostname } } - dependsOn: [ - apexRecord - ] -}] +} -@batchSize(1) -resource validationTxtRecord 'Microsoft.Network/dnsZones/TXT@2023-07-01-preview' = [for validationToken in validationTokens: { +resource validationTxtRecordApex 'Microsoft.Network/dnsZones/TXT@2023-07-01-preview' = { parent: dnsZone - name: '_dnsauth.${validationToken.domain.subDomain}' + name: empty(hostname.hostname) ? '_dnsauth' : '_dnsauth.${hostname.hostname}' properties: { TTL: 3600 TXTRecords: [ @@ -52,4 +48,4 @@ resource validationTxtRecord 'Microsoft.Network/dnsZones/TXT@2023-07-01-preview' } ] } -}] +} diff --git a/infrastructure/modules/frontdoor-images.bicep b/infrastructure/modules/frontdoor-images.bicep index c855f74..6804e47 100644 --- a/infrastructure/modules/frontdoor-images.bicep +++ b/infrastructure/modules/frontdoor-images.bicep @@ -324,5 +324,5 @@ resource frontDoorCustomDomain 'Microsoft.Cdn/profiles/customDomains@2024-02-01' } output frontDoorCustomDomainValidationToken string = frontDoorCustomDomain.properties.validationProperties.validationToken -output frontDoorCustomDomainHost string = frontDoorEndpoint.properties.hostName +output frontDoorHostname string = frontDoorEndpoint.properties.hostName output frontDoorEndpointId string = frontDoorEndpoint.id diff --git a/infrastructure/modules/frontdoor.bicep b/infrastructure/modules/frontdoor.bicep index 73af615..fa3868d 100644 --- a/infrastructure/modules/frontdoor.bicep +++ b/infrastructure/modules/frontdoor.bicep @@ -1,18 +1,22 @@ -import { domainsType } from '../types.bicep' +import { hostnameType, validationTokenType } from '../types.bicep' +@description('The name of the Front Door profile to use for the CDN.') param frontDoorProfileName string +@description('The hostname of the origin for the Front Door.') param frontDoorOriginHost string +@description('The name of the application, used to prefix resource names.') param application string -param domains domainsType[] +@description('List of hostnames to be configured for the Front Door.') +param hostnames hostnameType[] var frontDoorOriginName = 'afd-origin-${application}' var frontDoorEndpointName = 'fde-${application}-${uniqueString(resourceGroup().id)}' var frontDoorOriginGroupName = 'xprtz-website-${application}' var frontDoorRouteName = 'inbound' -resource dnsZone 'Microsoft.Network/dnsZones@2018-05-01' existing = { - name: domains[0].rootDomain -} +resource dnsZones 'Microsoft.Network/dnsZones@2018-05-01' existing = [for hostname in hostnames: { + name: hostname.dnsZoneName +}] resource frontDoorProfile 'Microsoft.Cdn/profiles@2024-02-01' existing = { name: frontDoorProfileName @@ -24,7 +28,6 @@ resource frontDoorEndpoint 'Microsoft.Cdn/profiles/afdEndpoints@2024-02-01' = { location: 'global' properties: { enabledState: 'Enabled' - } } @@ -58,6 +61,24 @@ resource frontDoorOrigin 'Microsoft.Cdn/profiles/originGroups/origins@2024-02-01 } } +var domainNames = [for hostname in hostnames: empty(hostname.hostname) ? hostname.dnsZoneName : '${hostname.hostname}.${hostname.dnsZoneName}'] + +@batchSize(1) +resource frontDoorCustomDomains 'Microsoft.Cdn/profiles/customDomains@2024-02-01' = [for (domainName, index) in domainNames: { + name: replace(domainName, '.', '-') + parent: frontDoorProfile + properties: { + hostName: domainName + tlsSettings: { + certificateType: 'ManagedCertificate' + minimumTlsVersion: 'TLS12' + } + azureDnsZone: { + id: dnsZones[index].id + } + } +}] + resource frontDoorRoute 'Microsoft.Cdn/profiles/afdEndpoints/routes@2024-02-01' = { name: frontDoorRouteName parent: frontDoorEndpoint @@ -65,7 +86,7 @@ resource frontDoorRoute 'Microsoft.Cdn/profiles/afdEndpoints/routes@2024-02-01' frontDoorOrigin ] properties: { - customDomains: [for (domain, index) in domains: { + customDomains: [for (hostname, index) in hostnames: { id: frontDoorCustomDomains[index].id }] originGroup: { @@ -84,24 +105,9 @@ resource frontDoorRoute 'Microsoft.Cdn/profiles/afdEndpoints/routes@2024-02-01' } } -resource frontDoorCustomDomains 'Microsoft.Cdn/profiles/customDomains@2024-02-01' = [for (domain, index) in domains: { - name: replace(domain.fullDomain, '.', '-') - parent: frontDoorProfile - properties: { - hostName: domain.fullDomain - tlsSettings: { - certificateType: 'ManagedCertificate' - minimumTlsVersion: 'TLS12' - } - azureDnsZone: { - id: dnsZone.id - } - } -}] - -output frontDoorCustomDomainValidationTokens array = [for (domain, index) in domains: { - domain: domain +output frontDoorCustomDomainValidationTokens validationTokenType[] = [for (hostname, index) in hostnames: { + hostname: hostname validationToken: frontDoorCustomDomains[index].properties.validationProperties.validationToken }] -output frontDoorCustomDomainHost string = frontDoorEndpoint.properties.hostName +output frontDoorHostname string = frontDoorEndpoint.properties.hostName output frontDoorEndpointId string = frontDoorEndpoint.id diff --git a/infrastructure/shared-values.json b/infrastructure/shared-values.json index a3203da..4c8fa82 100644 --- a/infrastructure/shared-values.json +++ b/infrastructure/shared-values.json @@ -4,5 +4,6 @@ }, "resourceGroups": { "infrastructure": "rg-xprtzbv-infrastructure" - } + }, + "frontDoorProfileName": "afd-xprtzbv-websites" } diff --git a/infrastructure/types.bicep b/infrastructure/types.bicep index af54bb7..6e937a6 100644 --- a/infrastructure/types.bicep +++ b/infrastructure/types.bicep @@ -1,12 +1,11 @@ @export() -type domainsType = { - rootDomain: string - subDomain: string - fullDomain: string +type hostnameType = { + dnsZoneName: string + hostname: string } @export() type validationTokenType = { - domain: domainsType + hostname: hostnameType validationToken: string } diff --git a/infrastructure/website.bicep b/infrastructure/website.bicep new file mode 100644 index 0000000..ef30845 --- /dev/null +++ b/infrastructure/website.bicep @@ -0,0 +1,93 @@ +import { hostnameType } from './types.bicep' + +targetScope = 'subscription' + +@description('The suffix for the resource group, typically \'main\' for production or a specific identifier for non-production environments.') +param resourceGroupSuffix string +@description('The name of the application, used to prefix resource names.') +param application string +@description('List of hostnames to be configured for the website.') +param hostnames hostnameType[] +@description('Hostname for the images domain, used for static content.') +param imageHostname hostnameType + +var isProd = endsWith(resourceGroupSuffix, 'main') + +var sharedValues = json(loadTextContent('shared-values.json')) +var infrastructureResourceGroup = resourceGroup( + sharedValues.subscriptionIds.xprtz, + sharedValues.resourceGroups.infrastructure +) + +var resourceGroupPrefix = 'rg-xprtzbv-website-${application}' +var resourceGroupName = isProd ? resourceGroupPrefix : '${resourceGroupPrefix}-${resourceGroupSuffix}' +resource websiteResourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { + location: deployment().location + name: resourceGroupName +} + +module storageAccountModule 'modules/storageAccount.bicep' = { + scope: websiteResourceGroup + name: 'storageAccountDeploy-${application}' + params: { + app: application + } +} + +module frontDoorSettings 'modules/frontdoor.bicep' = if (isProd) { + scope: infrastructureResourceGroup + name: 'frontDoorSettingsDeploy-${application}' + params: { + frontDoorOriginHost: storageAccountModule.outputs.storageAccountHost + frontDoorProfileName: sharedValues.frontDoorProfileName + application: application + hostnames: hostnames + } +} + +@batchSize(1) +module dnsSettings 'modules/dns.bicep' = [for hostname in hostnames: if (isProd) { + scope: infrastructureResourceGroup + name: 'dnsSettingsDeploy-${hostname.hostname}-${hostname.dnsZoneName}-${application}' + params: { + hostname: hostname + frontDoorEndpointId: frontDoorSettings!.outputs.frontDoorEndpointId + frontDoorHostname: frontDoorSettings!.outputs.frontDoorHostname + validationToken: filter( + frontDoorSettings!.outputs.frontDoorCustomDomainValidationTokens, + validation => validation.hostname.hostname == hostname.hostname && validation.hostname.dnsZoneName == hostname.dnsZoneName + )[0] + } + } +] + +module imagesFrontDoorSettings 'modules/frontdoor-images.bicep' = if (isProd) { + scope: infrastructureResourceGroup + name: 'imagesFrontDoorSettingsDeploy-${application}' + params: { + storageAccountName: storageAccountModule!.outputs.storageAccountName + storageAccountResourceGroup: websiteResourceGroup.name + frontDoorProfileName: sharedValues.frontDoorProfileName + application: application + rootDomain: imageHostname.dnsZoneName + subDomain: imageHostname.hostname + } +} + +module imagesDnsSettings 'modules/dns.bicep' = if (isProd) { + scope: infrastructureResourceGroup + name: 'imagesDnsSettingsDeploy-${application}' + params: { + hostname: imageHostname + frontDoorEndpointId: imagesFrontDoorSettings!.outputs.frontDoorEndpointId + frontDoorHostname: imagesFrontDoorSettings!.outputs.frontDoorHostname + validationToken: { + hostname: imageHostname + validationToken: imagesFrontDoorSettings!.outputs.frontDoorCustomDomainValidationToken + } + } +} + +output storageAccountName string = storageAccountModule.outputs.storageAccountName +output resourceGroupName string = websiteResourceGroup.name +output applicationFqdn string = isProd ? 'https://${hostnames[0].dnsZoneName}/' : storageAccountModule.outputs.storageAccountFqdn diff --git a/infrastructure/website.json b/infrastructure/website.json new file mode 100644 index 0000000..5e9b7b1 --- /dev/null +++ b/infrastructure/website.json @@ -0,0 +1,1190 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "14064476434264607174" + } + }, + "definitions": { + "hostnameType": { + "type": "object", + "properties": { + "dnsZoneName": { + "type": "string" + }, + "hostname": { + "type": "string" + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "types.bicep" + } + } + } + }, + "parameters": { + "resourceGroupSuffix": { + "type": "string", + "metadata": { + "description": "The suffix for the resource group, typically 'main' for production or a specific identifier for non-production environments." + } + }, + "application": { + "type": "string", + "metadata": { + "description": "The name of the application, used to prefix resource names." + } + }, + "hostnames": { + "type": "array", + "items": { + "$ref": "#/definitions/hostnameType" + }, + "metadata": { + "description": "List of hostnames to be configured for the website." + } + }, + "imageHostname": { + "$ref": "#/definitions/hostnameType", + "metadata": { + "description": "Hostname for the images domain, used for static content." + } + } + }, + "variables": { + "$fxv#0": "{\n \"subscriptionIds\": {\n \"xprtz\": \"92f4e2a9-8f0a-4ecc-90fc-6c77c24a1b31\"\n },\n \"resourceGroups\": {\n \"infrastructure\": \"rg-xprtzbv-infrastructure\"\n },\n \"frontDoorProfileName\": \"afd-xprtzbv-websites\"\n}\n", + "isProd": "[endsWith(parameters('resourceGroupSuffix'), 'main')]", + "sharedValues": "[json(variables('$fxv#0'))]", + "infrastructureResourceGroup": {}, + "resourceGroupPrefix": "[format('rg-xprtzbv-website-{0}', parameters('application'))]", + "resourceGroupName": "[if(variables('isProd'), variables('resourceGroupPrefix'), format('{0}-{1}', variables('resourceGroupPrefix'), parameters('resourceGroupSuffix')))]" + }, + "resources": { + "websiteResourceGroup": { + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2024-03-01", + "name": "[variables('resourceGroupName')]", + "location": "[deployment().location]" + }, + "storageAccountModule": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('storageAccountDeploy-{0}', parameters('application'))]", + "resourceGroup": "[variables('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('application')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "142108496684892283" + } + }, + "parameters": { + "sku": { + "type": "string", + "defaultValue": "Standard_LRS", + "allowedValues": [ + "Premium_LRS", + "Premium_ZRS", + "Standard_GRS", + "Standard_GZRS", + "Standard_LRS", + "Standard_RAGRS", + "Standard_RAGZRS", + "Standard_ZRS" + ] + }, + "app": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[format('st{0}{1}', parameters('app'), uniqueString(resourceGroup().id))]", + "location": "[resourceGroup().location]", + "kind": "StorageV2", + "sku": { + "name": "[parameters('sku')]" + }, + "properties": { + "supportsHttpsTrafficOnly": true, + "accessTier": "Hot", + "allowBlobPublicAccess": true, + "minimumTlsVersion": "TLS1_2" + } + }, + { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}/{2}', format('st{0}{1}', parameters('app'), uniqueString(resourceGroup().id)), 'default', '$web')]", + "properties": { + "immutableStorageWithVersioning": { + "enabled": false + }, + "defaultEncryptionScope": "$account-encryption-key", + "denyEncryptionScopeOverride": false, + "publicAccess": "None" + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', format('st{0}{1}', parameters('app'), uniqueString(resourceGroup().id)))]" + ] + }, + { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}/{2}', format('st{0}{1}', parameters('app'), uniqueString(resourceGroup().id)), 'default', 'media')]", + "properties": { + "immutableStorageWithVersioning": { + "enabled": false + }, + "defaultEncryptionScope": "$account-encryption-key", + "denyEncryptionScopeOverride": false, + "publicAccess": "Blob" + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', format('st{0}{1}', parameters('app'), uniqueString(resourceGroup().id)))]" + ] + } + ], + "outputs": { + "storageAccountName": { + "type": "string", + "value": "[format('st{0}{1}', parameters('app'), uniqueString(resourceGroup().id))]" + }, + "storageAccountFqdn": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', format('st{0}{1}', parameters('app'), uniqueString(resourceGroup().id))), '2023-04-01').primaryEndpoints.web]" + }, + "storageAccountHost": { + "type": "string", + "value": "[split(reference(resourceId('Microsoft.Storage/storageAccounts', format('st{0}{1}', parameters('app'), uniqueString(resourceGroup().id))), '2023-04-01').primaryEndpoints.web, '/')[2]]" + } + } + } + }, + "dependsOn": [ + "websiteResourceGroup" + ] + }, + "frontDoorSettings": { + "condition": "[variables('isProd')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('frontDoorSettingsDeploy-{0}', parameters('application'))]", + "subscriptionId": "[variables('sharedValues').subscriptionIds.xprtz]", + "resourceGroup": "[variables('sharedValues').resourceGroups.infrastructure]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "frontDoorOriginHost": { + "value": "[reference('storageAccountModule').outputs.storageAccountHost.value]" + }, + "frontDoorProfileName": { + "value": "[variables('sharedValues').frontDoorProfileName]" + }, + "application": { + "value": "[parameters('application')]" + }, + "hostnames": { + "value": "[parameters('hostnames')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "14685104079853229267" + } + }, + "definitions": { + "hostnameType": { + "type": "object", + "properties": { + "dnsZoneName": { + "type": "string" + }, + "hostname": { + "type": "string" + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "../types.bicep" + } + } + }, + "validationTokenType": { + "type": "object", + "properties": { + "hostname": { + "$ref": "#/definitions/hostnameType" + }, + "validationToken": { + "type": "string" + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "../types.bicep" + } + } + } + }, + "parameters": { + "frontDoorProfileName": { + "type": "string", + "metadata": { + "description": "The name of the Front Door profile to use for the CDN." + } + }, + "frontDoorOriginHost": { + "type": "string", + "metadata": { + "description": "The hostname of the origin for the Front Door." + } + }, + "application": { + "type": "string", + "metadata": { + "description": "The name of the application, used to prefix resource names." + } + }, + "hostnames": { + "type": "array", + "items": { + "$ref": "#/definitions/hostnameType" + }, + "metadata": { + "description": "List of hostnames to be configured for the Front Door." + } + } + }, + "variables": { + "copy": [ + { + "name": "domainNames", + "count": "[length(parameters('hostnames'))]", + "input": "[if(empty(parameters('hostnames')[copyIndex('domainNames')].hostname), parameters('hostnames')[copyIndex('domainNames')].dnsZoneName, format('{0}.{1}', parameters('hostnames')[copyIndex('domainNames')].hostname, parameters('hostnames')[copyIndex('domainNames')].dnsZoneName))]" + } + ], + "frontDoorOriginName": "[format('afd-origin-{0}', parameters('application'))]", + "frontDoorEndpointName": "[format('fde-{0}-{1}', parameters('application'), uniqueString(resourceGroup().id))]", + "frontDoorOriginGroupName": "[format('xprtz-website-{0}', parameters('application'))]", + "frontDoorRouteName": "inbound" + }, + "resources": { + "dnsZones": { + "copy": { + "name": "dnsZones", + "count": "[length(parameters('hostnames'))]" + }, + "existing": true, + "type": "Microsoft.Network/dnsZones", + "apiVersion": "2018-05-01", + "name": "[parameters('hostnames')[copyIndex()].dnsZoneName]" + }, + "frontDoorProfile": { + "existing": true, + "type": "Microsoft.Cdn/profiles", + "apiVersion": "2024-02-01", + "name": "[parameters('frontDoorProfileName')]" + }, + "frontDoorEndpoint": { + "type": "Microsoft.Cdn/profiles/afdEndpoints", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}', parameters('frontDoorProfileName'), variables('frontDoorEndpointName'))]", + "location": "global", + "properties": { + "enabledState": "Enabled" + } + }, + "frontDoorOriginGroup": { + "type": "Microsoft.Cdn/profiles/originGroups", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}', parameters('frontDoorProfileName'), variables('frontDoorOriginGroupName'))]", + "properties": { + "loadBalancingSettings": { + "sampleSize": 4, + "successfulSamplesRequired": 3 + }, + "healthProbeSettings": { + "probePath": "/", + "probeRequestType": "HEAD", + "probeProtocol": "Http", + "probeIntervalInSeconds": 100 + } + } + }, + "frontDoorOrigin": { + "type": "Microsoft.Cdn/profiles/originGroups/origins", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}/{2}', parameters('frontDoorProfileName'), variables('frontDoorOriginGroupName'), variables('frontDoorOriginName'))]", + "properties": { + "hostName": "[parameters('frontDoorOriginHost')]", + "httpPort": 80, + "httpsPort": 443, + "originHostHeader": "[parameters('frontDoorOriginHost')]", + "priority": 1, + "weight": 1000 + }, + "dependsOn": [ + "frontDoorOriginGroup" + ] + }, + "frontDoorCustomDomains": { + "copy": { + "name": "frontDoorCustomDomains", + "count": "[length(variables('domainNames'))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.Cdn/profiles/customDomains", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}', parameters('frontDoorProfileName'), replace(variables('domainNames')[copyIndex()], '.', '-'))]", + "properties": { + "hostName": "[variables('domainNames')[copyIndex()]]", + "tlsSettings": { + "certificateType": "ManagedCertificate", + "minimumTlsVersion": "TLS12" + }, + "azureDnsZone": { + "id": "[resourceId('Microsoft.Network/dnsZones', parameters('hostnames')[copyIndex()].dnsZoneName)]" + } + } + }, + "frontDoorRoute": { + "type": "Microsoft.Cdn/profiles/afdEndpoints/routes", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}/{2}', parameters('frontDoorProfileName'), variables('frontDoorEndpointName'), variables('frontDoorRouteName'))]", + "properties": { + "copy": [ + { + "name": "customDomains", + "count": "[length(parameters('hostnames'))]", + "input": { + "id": "[resourceId('Microsoft.Cdn/profiles/customDomains', parameters('frontDoorProfileName'), replace(variables('domainNames')[copyIndex('customDomains')], '.', '-'))]" + } + } + ], + "originGroup": { + "id": "[resourceId('Microsoft.Cdn/profiles/originGroups', parameters('frontDoorProfileName'), variables('frontDoorOriginGroupName'))]" + }, + "supportedProtocols": [ + "Http", + "Https" + ], + "patternsToMatch": [ + "/*" + ], + "forwardingProtocol": "HttpsOnly", + "linkToDefaultDomain": "Enabled", + "httpsRedirect": "Enabled" + }, + "dependsOn": [ + "frontDoorCustomDomains", + "frontDoorEndpoint", + "frontDoorOrigin", + "frontDoorOriginGroup" + ] + } + }, + "outputs": { + "frontDoorCustomDomainValidationTokens": { + "type": "array", + "items": { + "$ref": "#/definitions/validationTokenType" + }, + "copy": { + "count": "[length(parameters('hostnames'))]", + "input": { + "hostname": "[parameters('hostnames')[copyIndex()]]", + "validationToken": "[reference(format('frontDoorCustomDomains[{0}]', copyIndex())).validationProperties.validationToken]" + } + } + }, + "frontDoorHostname": { + "type": "string", + "value": "[reference('frontDoorEndpoint').hostName]" + }, + "frontDoorEndpointId": { + "type": "string", + "value": "[resourceId('Microsoft.Cdn/profiles/afdEndpoints', parameters('frontDoorProfileName'), variables('frontDoorEndpointName'))]" + } + } + } + }, + "dependsOn": [ + "storageAccountModule" + ] + }, + "dnsSettings": { + "copy": { + "name": "dnsSettings", + "count": "[length(parameters('hostnames'))]", + "mode": "serial", + "batchSize": 1 + }, + "condition": "[variables('isProd')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('dnsSettingsDeploy-{0}-{1}-{2}', parameters('hostnames')[copyIndex()].hostname, parameters('hostnames')[copyIndex()].dnsZoneName, parameters('application'))]", + "subscriptionId": "[variables('sharedValues').subscriptionIds.xprtz]", + "resourceGroup": "[variables('sharedValues').resourceGroups.infrastructure]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "hostname": { + "value": "[parameters('hostnames')[copyIndex()]]" + }, + "frontDoorEndpointId": { + "value": "[reference('frontDoorSettings').outputs.frontDoorEndpointId.value]" + }, + "frontDoorHostname": { + "value": "[reference('frontDoorSettings').outputs.frontDoorHostname.value]" + }, + "validationToken": { + "value": "[filter(reference('frontDoorSettings').outputs.frontDoorCustomDomainValidationTokens.value, lambda('validation', and(equals(lambdaVariables('validation').hostname.hostname, parameters('hostnames')[copyIndex()].hostname), equals(lambdaVariables('validation').hostname.dnsZoneName, parameters('hostnames')[copyIndex()].dnsZoneName))))[0]]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "9607504885755172534" + } + }, + "definitions": { + "hostnameType": { + "type": "object", + "properties": { + "dnsZoneName": { + "type": "string" + }, + "hostname": { + "type": "string" + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "../types.bicep" + } + } + }, + "validationTokenType": { + "type": "object", + "properties": { + "hostname": { + "$ref": "#/definitions/hostnameType" + }, + "validationToken": { + "type": "string" + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "../types.bicep" + } + } + } + }, + "parameters": { + "hostname": { + "$ref": "#/definitions/hostnameType", + "metadata": { + "description": "The dns record details" + } + }, + "frontDoorEndpointId": { + "type": "string", + "metadata": { + "description": "The ID of the Front Door endpoint to link the DNS records to." + } + }, + "frontDoorHostname": { + "type": "string", + "metadata": { + "description": "The frontdoor hostname to be used for the DNS CNAME records." + } + }, + "validationToken": { + "$ref": "#/definitions/validationTokenType", + "metadata": { + "description": "The validation token for DNS verification." + } + } + }, + "resources": { + "dnsZone": { + "existing": true, + "type": "Microsoft.Network/dnsZones", + "apiVersion": "2023-07-01-preview", + "name": "[parameters('hostname').dnsZoneName]" + }, + "apexRecord": { + "condition": "[empty(parameters('hostname').hostname)]", + "type": "Microsoft.Network/dnsZones/A", + "apiVersion": "2023-07-01-preview", + "name": "[format('{0}/{1}', parameters('hostname').dnsZoneName, '@')]", + "properties": { + "TTL": 3600, + "targetResource": { + "id": "[parameters('frontDoorEndpointId')]" + } + } + }, + "cnameRecord": { + "condition": "[not(empty(parameters('hostname').hostname))]", + "type": "Microsoft.Network/dnsZones/CNAME", + "apiVersion": "2023-07-01-preview", + "name": "[format('{0}/{1}', parameters('hostname').dnsZoneName, parameters('hostname').hostname)]", + "properties": { + "TTL": 3600, + "CNAMERecord": { + "cname": "[parameters('frontDoorHostname')]" + } + } + }, + "validationTxtRecordApex": { + "type": "Microsoft.Network/dnsZones/TXT", + "apiVersion": "2023-07-01-preview", + "name": "[format('{0}/{1}', parameters('hostname').dnsZoneName, if(empty(parameters('hostname').hostname), '_dnsauth', format('_dnsauth.{0}', parameters('hostname').hostname)))]", + "properties": { + "TTL": 3600, + "TXTRecords": [ + { + "value": [ + "[parameters('validationToken').validationToken]" + ] + } + ] + } + } + } + } + }, + "dependsOn": [ + "frontDoorSettings" + ] + }, + "imagesFrontDoorSettings": { + "condition": "[variables('isProd')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('imagesFrontDoorSettingsDeploy-{0}', parameters('application'))]", + "subscriptionId": "[variables('sharedValues').subscriptionIds.xprtz]", + "resourceGroup": "[variables('sharedValues').resourceGroups.infrastructure]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[reference('storageAccountModule').outputs.storageAccountName.value]" + }, + "storageAccountResourceGroup": { + "value": "[variables('resourceGroupName')]" + }, + "frontDoorProfileName": { + "value": "[variables('sharedValues').frontDoorProfileName]" + }, + "application": { + "value": "[parameters('application')]" + }, + "rootDomain": { + "value": "[parameters('imageHostname').dnsZoneName]" + }, + "subDomain": { + "value": "[parameters('imageHostname').hostname]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "6970417190122259700" + } + }, + "parameters": { + "frontDoorProfileName": { + "type": "string" + }, + "storageAccountName": { + "type": "string" + }, + "storageAccountResourceGroup": { + "type": "string" + }, + "application": { + "type": "string" + }, + "rootDomain": { + "type": "string" + }, + "subDomain": { + "type": "string" + } + }, + "variables": { + "frontDoorOriginName": "[format('afd-origin-images-{0}', parameters('application'))]", + "frontDoorEndpointName": "[format('fde-images-{0}-{1}', parameters('application'), uniqueString(resourceGroup().id))]", + "frontDoorOriginGroupName": "[format('xprtz-images-{0}', parameters('application'))]", + "frontDoorRouteName": "images-route", + "customDomainHost": "[format('{0}.{1}', parameters('subDomain'), parameters('rootDomain'))]", + "customDomainResourceName": "[replace(format('{0}', variables('customDomainHost')), '.', '-')]", + "ruleSetName": "images" + }, + "resources": [ + { + "type": "Microsoft.Cdn/profiles/afdEndpoints", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}', parameters('frontDoorProfileName'), variables('frontDoorEndpointName'))]", + "location": "global", + "properties": { + "enabledState": "Enabled" + } + }, + { + "type": "Microsoft.Cdn/profiles/ruleSets", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}', parameters('frontDoorProfileName'), variables('ruleSetName'))]" + }, + { + "type": "Microsoft.Cdn/profiles/ruleSets/rules", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}/{2}', parameters('frontDoorProfileName'), variables('ruleSetName'), 'jpeg')]", + "properties": { + "order": 1, + "conditions": [ + { + "name": "UrlFileExtension", + "parameters": { + "typeName": "DeliveryRuleUrlFileExtensionMatchConditionParameters", + "operator": "Equal", + "negateCondition": false, + "matchValues": [ + "jpg", + "jpeg" + ], + "transforms": [ + "Lowercase" + ] + } + } + ], + "actions": [ + { + "name": "ModifyResponseHeader", + "parameters": { + "typeName": "DeliveryRuleHeaderActionParameters", + "headerAction": "Overwrite", + "headerName": "Content-Type", + "value": "image/jpeg" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Cdn/profiles/ruleSets', parameters('frontDoorProfileName'), variables('ruleSetName'))]" + ] + }, + { + "type": "Microsoft.Cdn/profiles/ruleSets/rules", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}/{2}', parameters('frontDoorProfileName'), variables('ruleSetName'), 'png')]", + "properties": { + "order": 2, + "conditions": [ + { + "name": "UrlFileExtension", + "parameters": { + "typeName": "DeliveryRuleUrlFileExtensionMatchConditionParameters", + "operator": "Equal", + "negateCondition": false, + "matchValues": [ + "png" + ], + "transforms": [ + "Lowercase" + ] + } + } + ], + "actions": [ + { + "name": "ModifyResponseHeader", + "parameters": { + "typeName": "DeliveryRuleHeaderActionParameters", + "headerAction": "Overwrite", + "headerName": "Content-Type", + "value": "image/png" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Cdn/profiles/ruleSets', parameters('frontDoorProfileName'), variables('ruleSetName'))]" + ] + }, + { + "type": "Microsoft.Cdn/profiles/ruleSets/rules", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}/{2}', parameters('frontDoorProfileName'), variables('ruleSetName'), 'svg')]", + "properties": { + "order": 3, + "conditions": [ + { + "name": "UrlFileExtension", + "parameters": { + "typeName": "DeliveryRuleUrlFileExtensionMatchConditionParameters", + "operator": "Equal", + "negateCondition": false, + "matchValues": [ + "svg" + ], + "transforms": [ + "Lowercase" + ] + } + } + ], + "actions": [ + { + "name": "ModifyResponseHeader", + "parameters": { + "typeName": "DeliveryRuleHeaderActionParameters", + "headerAction": "Overwrite", + "headerName": "Content-Type", + "value": "image/svg+xml" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Cdn/profiles/ruleSets', parameters('frontDoorProfileName'), variables('ruleSetName'))]" + ] + }, + { + "type": "Microsoft.Cdn/profiles/ruleSets/rules", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}/{2}', parameters('frontDoorProfileName'), variables('ruleSetName'), 'gif')]", + "properties": { + "order": 4, + "conditions": [ + { + "name": "UrlFileExtension", + "parameters": { + "typeName": "DeliveryRuleUrlFileExtensionMatchConditionParameters", + "operator": "Equal", + "negateCondition": false, + "matchValues": [ + "gif" + ], + "transforms": [ + "Lowercase" + ] + } + } + ], + "actions": [ + { + "name": "ModifyResponseHeader", + "parameters": { + "typeName": "DeliveryRuleHeaderActionParameters", + "headerAction": "Overwrite", + "headerName": "Content-Type", + "value": "image/gif" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Cdn/profiles/ruleSets', parameters('frontDoorProfileName'), variables('ruleSetName'))]" + ] + }, + { + "type": "Microsoft.Cdn/profiles/ruleSets/rules", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}/{2}', parameters('frontDoorProfileName'), variables('ruleSetName'), 'webp')]", + "properties": { + "order": 5, + "conditions": [ + { + "name": "UrlFileExtension", + "parameters": { + "typeName": "DeliveryRuleUrlFileExtensionMatchConditionParameters", + "operator": "Equal", + "negateCondition": false, + "matchValues": [ + "webp" + ], + "transforms": [ + "Lowercase" + ] + } + } + ], + "actions": [ + { + "name": "ModifyResponseHeader", + "parameters": { + "typeName": "DeliveryRuleHeaderActionParameters", + "headerAction": "Overwrite", + "headerName": "Content-Type", + "value": "image/webp" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Cdn/profiles/ruleSets', parameters('frontDoorProfileName'), variables('ruleSetName'))]" + ] + }, + { + "type": "Microsoft.Cdn/profiles/ruleSets/rules", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}/{2}', parameters('frontDoorProfileName'), variables('ruleSetName'), 'avif')]", + "properties": { + "order": 6, + "conditions": [ + { + "name": "UrlFileExtension", + "parameters": { + "typeName": "DeliveryRuleUrlFileExtensionMatchConditionParameters", + "operator": "Equal", + "negateCondition": false, + "matchValues": [ + "avif" + ], + "transforms": [ + "Lowercase" + ] + } + } + ], + "actions": [ + { + "name": "ModifyResponseHeader", + "parameters": { + "typeName": "DeliveryRuleHeaderActionParameters", + "headerAction": "Overwrite", + "headerName": "Content-Type", + "value": "image/avif" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Cdn/profiles/ruleSets', parameters('frontDoorProfileName'), variables('ruleSetName'))]" + ] + }, + { + "type": "Microsoft.Cdn/profiles/originGroups", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}', parameters('frontDoorProfileName'), variables('frontDoorOriginGroupName'))]", + "properties": { + "loadBalancingSettings": { + "sampleSize": 4, + "successfulSamplesRequired": 3 + }, + "healthProbeSettings": { + "probePath": "/media/", + "probeRequestType": "HEAD", + "probeProtocol": "Https", + "probeIntervalInSeconds": 100 + } + } + }, + { + "type": "Microsoft.Cdn/profiles/originGroups/origins", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}/{2}', parameters('frontDoorProfileName'), variables('frontDoorOriginGroupName'), variables('frontDoorOriginName'))]", + "properties": { + "hostName": "[split(reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('storageAccountResourceGroup')), 'Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').primaryEndpoints.blob, '/')[2]]", + "httpPort": 80, + "httpsPort": 443, + "originHostHeader": "[split(reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('storageAccountResourceGroup')), 'Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').primaryEndpoints.blob, '/')[2]]", + "priority": 1, + "weight": 1000 + }, + "dependsOn": [ + "[resourceId('Microsoft.Cdn/profiles/originGroups', parameters('frontDoorProfileName'), variables('frontDoorOriginGroupName'))]" + ] + }, + { + "type": "Microsoft.Cdn/profiles/afdEndpoints/routes", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}/{2}', parameters('frontDoorProfileName'), variables('frontDoorEndpointName'), variables('frontDoorRouteName'))]", + "properties": { + "customDomains": [ + { + "id": "[resourceId('Microsoft.Cdn/profiles/customDomains', parameters('frontDoorProfileName'), variables('customDomainResourceName'))]" + } + ], + "originGroup": { + "id": "[resourceId('Microsoft.Cdn/profiles/originGroups', parameters('frontDoorProfileName'), variables('frontDoorOriginGroupName'))]" + }, + "ruleSets": [ + { + "id": "[resourceId('Microsoft.Cdn/profiles/ruleSets', parameters('frontDoorProfileName'), variables('ruleSetName'))]" + } + ], + "supportedProtocols": [ + "Http", + "Https" + ], + "patternsToMatch": [ + "/uploads/*" + ], + "forwardingProtocol": "HttpsOnly", + "linkToDefaultDomain": "Enabled", + "httpsRedirect": "Enabled", + "originPath": "/media" + }, + "dependsOn": [ + "[resourceId('Microsoft.Cdn/profiles/customDomains', parameters('frontDoorProfileName'), variables('customDomainResourceName'))]", + "[resourceId('Microsoft.Cdn/profiles/afdEndpoints', parameters('frontDoorProfileName'), variables('frontDoorEndpointName'))]", + "[resourceId('Microsoft.Cdn/profiles/originGroups/origins', parameters('frontDoorProfileName'), variables('frontDoorOriginGroupName'), variables('frontDoorOriginName'))]", + "[resourceId('Microsoft.Cdn/profiles/originGroups', parameters('frontDoorProfileName'), variables('frontDoorOriginGroupName'))]", + "[resourceId('Microsoft.Cdn/profiles/ruleSets', parameters('frontDoorProfileName'), variables('ruleSetName'))]" + ] + }, + { + "type": "Microsoft.Cdn/profiles/customDomains", + "apiVersion": "2024-02-01", + "name": "[format('{0}/{1}', parameters('frontDoorProfileName'), variables('customDomainResourceName'))]", + "properties": { + "hostName": "[variables('customDomainHost')]", + "tlsSettings": { + "certificateType": "ManagedCertificate", + "minimumTlsVersion": "TLS12" + } + } + } + ], + "outputs": { + "frontDoorCustomDomainValidationToken": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Cdn/profiles/customDomains', parameters('frontDoorProfileName'), variables('customDomainResourceName')), '2024-02-01').validationProperties.validationToken]" + }, + "frontDoorHostname": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Cdn/profiles/afdEndpoints', parameters('frontDoorProfileName'), variables('frontDoorEndpointName')), '2024-02-01').hostName]" + }, + "frontDoorEndpointId": { + "type": "string", + "value": "[resourceId('Microsoft.Cdn/profiles/afdEndpoints', parameters('frontDoorProfileName'), variables('frontDoorEndpointName'))]" + } + } + } + }, + "dependsOn": [ + "storageAccountModule", + "websiteResourceGroup" + ] + }, + "imagesDnsSettings": { + "condition": "[variables('isProd')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('imagesDnsSettingsDeploy-{0}', parameters('application'))]", + "subscriptionId": "[variables('sharedValues').subscriptionIds.xprtz]", + "resourceGroup": "[variables('sharedValues').resourceGroups.infrastructure]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "hostname": { + "value": "[parameters('imageHostname')]" + }, + "frontDoorEndpointId": { + "value": "[reference('imagesFrontDoorSettings').outputs.frontDoorEndpointId.value]" + }, + "frontDoorHostname": { + "value": "[reference('imagesFrontDoorSettings').outputs.frontDoorHostname.value]" + }, + "validationToken": { + "value": { + "hostname": "[parameters('imageHostname')]", + "validationToken": "[reference('imagesFrontDoorSettings').outputs.frontDoorCustomDomainValidationToken.value]" + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "9607504885755172534" + } + }, + "definitions": { + "hostnameType": { + "type": "object", + "properties": { + "dnsZoneName": { + "type": "string" + }, + "hostname": { + "type": "string" + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "../types.bicep" + } + } + }, + "validationTokenType": { + "type": "object", + "properties": { + "hostname": { + "$ref": "#/definitions/hostnameType" + }, + "validationToken": { + "type": "string" + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "../types.bicep" + } + } + } + }, + "parameters": { + "hostname": { + "$ref": "#/definitions/hostnameType", + "metadata": { + "description": "The dns record details" + } + }, + "frontDoorEndpointId": { + "type": "string", + "metadata": { + "description": "The ID of the Front Door endpoint to link the DNS records to." + } + }, + "frontDoorHostname": { + "type": "string", + "metadata": { + "description": "The frontdoor hostname to be used for the DNS CNAME records." + } + }, + "validationToken": { + "$ref": "#/definitions/validationTokenType", + "metadata": { + "description": "The validation token for DNS verification." + } + } + }, + "resources": { + "dnsZone": { + "existing": true, + "type": "Microsoft.Network/dnsZones", + "apiVersion": "2023-07-01-preview", + "name": "[parameters('hostname').dnsZoneName]" + }, + "apexRecord": { + "condition": "[empty(parameters('hostname').hostname)]", + "type": "Microsoft.Network/dnsZones/A", + "apiVersion": "2023-07-01-preview", + "name": "[format('{0}/{1}', parameters('hostname').dnsZoneName, '@')]", + "properties": { + "TTL": 3600, + "targetResource": { + "id": "[parameters('frontDoorEndpointId')]" + } + } + }, + "cnameRecord": { + "condition": "[not(empty(parameters('hostname').hostname))]", + "type": "Microsoft.Network/dnsZones/CNAME", + "apiVersion": "2023-07-01-preview", + "name": "[format('{0}/{1}', parameters('hostname').dnsZoneName, parameters('hostname').hostname)]", + "properties": { + "TTL": 3600, + "CNAMERecord": { + "cname": "[parameters('frontDoorHostname')]" + } + } + }, + "validationTxtRecordApex": { + "type": "Microsoft.Network/dnsZones/TXT", + "apiVersion": "2023-07-01-preview", + "name": "[format('{0}/{1}', parameters('hostname').dnsZoneName, if(empty(parameters('hostname').hostname), '_dnsauth', format('_dnsauth.{0}', parameters('hostname').hostname)))]", + "properties": { + "TTL": 3600, + "TXTRecords": [ + { + "value": [ + "[parameters('validationToken').validationToken]" + ] + } + ] + } + } + } + } + }, + "dependsOn": [ + "imagesFrontDoorSettings" + ] + } + }, + "outputs": { + "storageAccountName": { + "type": "string", + "value": "[reference('storageAccountModule').outputs.storageAccountName.value]" + }, + "resourceGroupName": { + "type": "string", + "value": "[variables('resourceGroupName')]" + }, + "applicationFqdn": { + "type": "string", + "value": "[if(variables('isProd'), format('https://{0}/', parameters('hostnames')[0].dnsZoneName), reference('storageAccountModule').outputs.storageAccountFqdn.value)]" + } + } +} \ No newline at end of file diff --git a/infrastructure/websiteDeploy.bicep b/infrastructure/websiteDeploy.bicep deleted file mode 100644 index 690e693..0000000 --- a/infrastructure/websiteDeploy.bicep +++ /dev/null @@ -1,135 +0,0 @@ -import { domainsType } from './types.bicep' - -targetScope = 'subscription' - -param resourceGroupSuffix string -param application string = 'dotnet' - -var isProd = endsWith(resourceGroupSuffix, 'main') -param previewDomains domainsType[] = [ - { - rootDomain: 'xprtz.dev' - subDomain: '' - fullDomain: 'xprtz.dev' - } - { - rootDomain: 'xprtz.dev' - subDomain: 'www' - fullDomain: 'www.xprtz.dev' - } - { - rootDomain: 'xprtz.dev' - subDomain: 'dotnet' - fullDomain: 'dotnet.xprtz.dev' - } -] -param prodDomains domainsType[] = [ - { - rootDomain: 'xprtz.nl' - subDomain: '' - fullDomain: 'xprtz.nl' - } - { - rootDomain: 'xprtz.nl' - subDomain: 'www' - fullDomain: 'www.xprtz.nl' - } - { - rootDomain: 'xprtz.cloud' - subDomain: '' - fullDomain: 'xprtz.cloud' - } -] - -var domains = isProd ? prodDomains : previewDomains -var rootDomain = domains[0].rootDomain -var resourceGroupPrefix = 'rg-xprtzbv-website-${application}' -var resourceGroupName = isProd ? resourceGroupPrefix : '${resourceGroupPrefix}-${resourceGroupSuffix}' - -var sharedValues = json(loadTextContent('shared-values.json')) -var infrastructureResourceGroup = resourceGroup( - sharedValues.subscriptionIds.xprtz, - sharedValues.resourceGroups.infrastructure -) - -var frontDoorProfileName = 'afd-xprtzbv-websites' -var imagesDomain = 'images' - -resource websiteResourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { - location: deployment().location - name: resourceGroupName -} - -module storageAccountModule 'modules/storageAccount.bicep' = { - scope: websiteResourceGroup - name: 'StorageAccountDeploy-${application}' - params: { - app: application - } -} - -module frontDoorSettings 'modules/frontdoor.bicep' = if (isProd) { - scope: infrastructureResourceGroup - name: 'frontDoorSettingsDeploy-${application}' - params: { - frontDoorOriginHost: storageAccountModule.outputs.storageAccountHost - frontDoorProfileName: frontDoorProfileName - application: application - domains: domains - } -} - -module dnsSettings 'modules/dns.bicep' = if (isProd) { - scope: infrastructureResourceGroup - name: 'dnsSettingsDeploy-${application}' - params: { - origin: frontDoorSettings!.outputs.frontDoorCustomDomainHost - frontDoorEndpointId: frontDoorSettings!.outputs.frontDoorEndpointId - domains: domains - validationTokens: frontDoorSettings!.outputs.frontDoorCustomDomainValidationTokens - } -} - -module imagesFrontDoorSettings 'modules/frontdoor-images.bicep' = if (isProd) { - scope: infrastructureResourceGroup - name: 'imagesFrontDoorSettingsDeploy-${application}' - params: { - storageAccountName: storageAccountModule.outputs.storageAccountName - storageAccountResourceGroup: websiteResourceGroup.name - frontDoorProfileName: frontDoorProfileName - application: application - rootDomain: rootDomain - subDomain: imagesDomain - } -} - -module imagesDnsSettings 'modules/dns.bicep' = if (isProd) { - scope: infrastructureResourceGroup - name: 'imagesDnsSettingsDeploy-${application}' - params: { - origin: imagesFrontDoorSettings!.outputs.frontDoorCustomDomainHost - frontDoorEndpointId: imagesFrontDoorSettings!.outputs.frontDoorEndpointId - deployApexRecord: false - domains: [ - { - rootDomain: rootDomain - subDomain: imagesDomain - fullDomain: '${imagesDomain}.${rootDomain}' - } - ] - validationTokens: [ - { - domain: { - rootDomain: rootDomain - subDomain: imagesDomain - fullDomain: '${imagesDomain}.${rootDomain}' - } - validationToken: imagesFrontDoorSettings!.outputs.frontDoorCustomDomainValidationToken - } - ] - } -} - -output storageAccountName string = storageAccountModule.outputs.storageAccountName -output resourceGroupName string = websiteResourceGroup.name -output applicationFqdn string = isProd ? 'https://${rootDomain}/' : storageAccountModule.outputs.storageAccountFqdn