From 5fccedef42692d0bd0e719091ebf318fdeb542aa Mon Sep 17 00:00:00 2001 From: Martyn de Vries Date: Sun, 3 Aug 2025 02:06:41 +0200 Subject: [PATCH 1/7] Deployment van de dns records wat netter opgezet --- .../reusable-apply-infrastructure.yaml | 6 +- infrastructure/dotnet.bicepparam | 34 +++++ infrastructure/modules/dns.bicep | 36 ++--- infrastructure/modules/frontdoor.bicep | 51 ++++--- infrastructure/shared-values.json | 3 +- infrastructure/types.bicep | 12 +- infrastructure/website.bicep | 93 ++++++++++++ infrastructure/websiteDeploy.bicep | 135 ------------------ 8 files changed, 184 insertions(+), 186 deletions(-) create mode 100644 infrastructure/dotnet.bicepparam create mode 100644 infrastructure/website.bicep delete mode 100644 infrastructure/websiteDeploy.bicep 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..6d23f8f 100644 --- a/infrastructure/modules/dns.bicep +++ b/infrastructure/modules/dns.bicep @@ -1,18 +1,17 @@ -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 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 +22,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: hostname.dnsZoneName } } - 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 +46,4 @@ resource validationTxtRecord 'Microsoft.Network/dnsZones/TXT@2023-07-01-preview' } ] } -}] +} diff --git a/infrastructure/modules/frontdoor.bicep b/infrastructure/modules/frontdoor.bicep index 73af615..7a6c769 100644 --- a/infrastructure/modules/frontdoor.bicep +++ b/infrastructure/modules/frontdoor.bicep @@ -1,9 +1,13 @@ -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)}' @@ -11,7 +15,7 @@ var frontDoorOriginGroupName = 'xprtz-website-${application}' var frontDoorRouteName = 'inbound' resource dnsZone 'Microsoft.Network/dnsZones@2018-05-01' existing = { - name: domains[0].rootDomain + name: hostnames[0].dnsZoneName } resource frontDoorProfile 'Microsoft.Cdn/profiles@2024-02-01' existing = { @@ -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}'] + +// Gebruik de berekende waarden in de resource +resource frontDoorCustomDomains 'Microsoft.Cdn/profiles/customDomains@2024-02-01' = [for domainName in domainNames: { + name: replace(domainName, '.', '-') + parent: frontDoorProfile + properties: { + hostName: domainName + tlsSettings: { + certificateType: 'ManagedCertificate' + minimumTlsVersion: 'TLS12' + } + azureDnsZone: { + id: dnsZone.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,23 +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: { + dnsZoneName: hostname.dnsZoneName + hostname: hostname.hostname validationToken: frontDoorCustomDomains[index].properties.validationProperties.validationToken }] output frontDoorCustomDomainHost string = frontDoorEndpoint.properties.hostName 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..423ee4a 100644 --- a/infrastructure/types.bicep +++ b/infrastructure/types.bicep @@ -1,12 +1,14 @@ @export() -type domainsType = { - rootDomain: string - subDomain: string - fullDomain: string +type hostnameType = { + dnsZoneName: string + hostname: string +// subDomain: string +// fullDomain: string } @export() type validationTokenType = { - domain: domainsType + dnsZoneName: string + hostname: string validationToken: string } diff --git a/infrastructure/website.bicep b/infrastructure/website.bicep new file mode 100644 index 0000000..5b8d4db --- /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 + validationToken: filter( + frontDoorSettings.outputs.frontDoorCustomDomainValidationTokens, + validation => validation.hostname == hostname.hostname && validation.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 + validationToken: { + dnsZoneName: imageHostname.dnsZoneName + hostname: imageHostname.hostname + 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/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 From ce4f0ce3c948f68002c6f187d4af7c24a08bf056 Mon Sep 17 00:00:00 2001 From: Martyn de Vries Date: Sun, 3 Aug 2025 02:38:07 +0200 Subject: [PATCH 2/7] CNames gefixt --- infrastructure/modules/dns.bicep | 4 +++- infrastructure/modules/frontdoor-images.bicep | 2 +- infrastructure/modules/frontdoor.bicep | 2 +- infrastructure/website.bicep | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/infrastructure/modules/dns.bicep b/infrastructure/modules/dns.bicep index 6d23f8f..fb9058e 100644 --- a/infrastructure/modules/dns.bicep +++ b/infrastructure/modules/dns.bicep @@ -4,6 +4,8 @@ import { hostnameType, validationTokenType } from '../types.bicep' param hostname hostnameType @description('The ID of the Front Door endpoint to link the DNS records to.') param frontDoorEndpointId string +@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 @@ -28,7 +30,7 @@ resource cnameRecord 'Microsoft.Network/dnsZones/CNAME@2023-07-01-preview' = if properties: { TTL: 3600 CNAMERecord: { - cname: hostname.dnsZoneName + cname: frontDoorHostname } } } 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 7a6c769..291182f 100644 --- a/infrastructure/modules/frontdoor.bicep +++ b/infrastructure/modules/frontdoor.bicep @@ -110,5 +110,5 @@ output frontDoorCustomDomainValidationTokens validationTokenType[] = [for (hostn hostname: 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/website.bicep b/infrastructure/website.bicep index 5b8d4db..06214b4 100644 --- a/infrastructure/website.bicep +++ b/infrastructure/website.bicep @@ -53,6 +53,7 @@ module dnsSettings 'modules/dns.bicep' = [for hostname in hostnames: if (isProd) params: { hostname: hostname frontDoorEndpointId: frontDoorSettings.outputs.frontDoorEndpointId + frontDoorHostname: frontDoorSettings.outputs.frontDoorHostname validationToken: filter( frontDoorSettings.outputs.frontDoorCustomDomainValidationTokens, validation => validation.hostname == hostname.hostname && validation.dnsZoneName == hostname.dnsZoneName @@ -80,6 +81,7 @@ module imagesDnsSettings 'modules/dns.bicep' = if (isProd) { params: { hostname: imageHostname frontDoorEndpointId: imagesFrontDoorSettings.outputs.frontDoorEndpointId + frontDoorHostname: imagesFrontDoorSettings.outputs.frontDoorHostname validationToken: { dnsZoneName: imageHostname.dnsZoneName hostname: imageHostname.hostname From 1c89fc10960ba4bc82698b255f26134ae98c823e Mon Sep 17 00:00:00 2001 From: Martyn de Vries Date: Sun, 3 Aug 2025 13:16:30 +0200 Subject: [PATCH 3/7] Null checks toegvoegd --- infrastructure/website.bicep | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/infrastructure/website.bicep b/infrastructure/website.bicep index 06214b4..b839d93 100644 --- a/infrastructure/website.bicep +++ b/infrastructure/website.bicep @@ -52,10 +52,10 @@ module dnsSettings 'modules/dns.bicep' = [for hostname in hostnames: if (isProd) name: 'dnsSettingsDeploy-${hostname.hostname}-${hostname.dnsZoneName}-${application}' params: { hostname: hostname - frontDoorEndpointId: frontDoorSettings.outputs.frontDoorEndpointId - frontDoorHostname: frontDoorSettings.outputs.frontDoorHostname + frontDoorEndpointId: frontDoorSettings!.outputs.frontDoorEndpointId + frontDoorHostname: frontDoorSettings!.outputs.frontDoorHostname validationToken: filter( - frontDoorSettings.outputs.frontDoorCustomDomainValidationTokens, + frontDoorSettings!.outputs.frontDoorCustomDomainValidationTokens, validation => validation.hostname == hostname.hostname && validation.dnsZoneName == hostname.dnsZoneName )[0] } @@ -66,7 +66,7 @@ module imagesFrontDoorSettings 'modules/frontdoor-images.bicep' = if (isProd) { scope: infrastructureResourceGroup name: 'imagesFrontDoorSettingsDeploy-${application}' params: { - storageAccountName: storageAccountModule.outputs.storageAccountName + storageAccountName: storageAccountModule!.outputs.storageAccountName storageAccountResourceGroup: websiteResourceGroup.name frontDoorProfileName: sharedValues.frontDoorProfileName application: application @@ -80,12 +80,12 @@ module imagesDnsSettings 'modules/dns.bicep' = if (isProd) { name: 'imagesDnsSettingsDeploy-${application}' params: { hostname: imageHostname - frontDoorEndpointId: imagesFrontDoorSettings.outputs.frontDoorEndpointId - frontDoorHostname: imagesFrontDoorSettings.outputs.frontDoorHostname + frontDoorEndpointId: imagesFrontDoorSettings!.outputs.frontDoorEndpointId + frontDoorHostname: imagesFrontDoorSettings!.outputs.frontDoorHostname validationToken: { dnsZoneName: imageHostname.dnsZoneName hostname: imageHostname.hostname - validationToken: imagesFrontDoorSettings.outputs.frontDoorCustomDomainValidationToken + validationToken: imagesFrontDoorSettings!.outputs.frontDoorCustomDomainValidationToken } } } From abdac7fdcb007dbc443a7bdbafd14c10048f9880 Mon Sep 17 00:00:00 2001 From: Martyn de Vries Date: Fri, 12 Dec 2025 17:39:12 +0100 Subject: [PATCH 4/7] Batch size toegevoegd aan frontdoor --- infrastructure/modules/frontdoor.bicep | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/modules/frontdoor.bicep b/infrastructure/modules/frontdoor.bicep index 291182f..12dde72 100644 --- a/infrastructure/modules/frontdoor.bicep +++ b/infrastructure/modules/frontdoor.bicep @@ -64,6 +64,7 @@ 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}'] // Gebruik de berekende waarden in de resource +@batchSize(1) resource frontDoorCustomDomains 'Microsoft.Cdn/profiles/customDomains@2024-02-01' = [for domainName in domainNames: { name: replace(domainName, '.', '-') parent: frontDoorProfile From 9d2c983802eee44932e5c495e6a50db4f8c3324f Mon Sep 17 00:00:00 2001 From: Martyn de Vries Date: Fri, 12 Dec 2025 19:05:12 +0100 Subject: [PATCH 5/7] DNS zones per domeinnaam in frontdoor zetten --- infrastructure/modules/frontdoor.bicep | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/infrastructure/modules/frontdoor.bicep b/infrastructure/modules/frontdoor.bicep index 12dde72..f428b48 100644 --- a/infrastructure/modules/frontdoor.bicep +++ b/infrastructure/modules/frontdoor.bicep @@ -14,9 +14,9 @@ var frontDoorEndpointName = 'fde-${application}-${uniqueString(resourceGroup().i var frontDoorOriginGroupName = 'xprtz-website-${application}' var frontDoorRouteName = 'inbound' -resource dnsZone 'Microsoft.Network/dnsZones@2018-05-01' existing = { - name: hostnames[0].dnsZoneName -} +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 @@ -63,9 +63,8 @@ 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}'] -// Gebruik de berekende waarden in de resource @batchSize(1) -resource frontDoorCustomDomains 'Microsoft.Cdn/profiles/customDomains@2024-02-01' = [for domainName in domainNames: { +resource frontDoorCustomDomains 'Microsoft.Cdn/profiles/customDomains@2024-02-01' = [for (domainName, index) in domainNames: { name: replace(domainName, '.', '-') parent: frontDoorProfile properties: { @@ -75,7 +74,7 @@ resource frontDoorCustomDomains 'Microsoft.Cdn/profiles/customDomains@2024-02-01 minimumTlsVersion: 'TLS12' } azureDnsZone: { - id: dnsZone.id + id: dnsZones[index].id } } }] From 00148caaf272aca34ac208eeb9b4dd78f14b3c8d Mon Sep 17 00:00:00 2001 From: Martyn de Vries Date: Sat, 13 Dec 2025 10:08:36 +0100 Subject: [PATCH 6/7] Comments van Alex gefixt --- infrastructure/types.bicep | 2 -- infrastructure/website.bicep | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/infrastructure/types.bicep b/infrastructure/types.bicep index 423ee4a..ead6b35 100644 --- a/infrastructure/types.bicep +++ b/infrastructure/types.bicep @@ -2,8 +2,6 @@ type hostnameType = { dnsZoneName: string hostname: string -// subDomain: string -// fullDomain: string } @export() diff --git a/infrastructure/website.bicep b/infrastructure/website.bicep index b839d93..f78badc 100644 --- a/infrastructure/website.bicep +++ b/infrastructure/website.bicep @@ -13,7 +13,6 @@ param imageHostname hostnameType var isProd = endsWith(resourceGroupSuffix, 'main') - var sharedValues = json(loadTextContent('shared-values.json')) var infrastructureResourceGroup = resourceGroup( sharedValues.subscriptionIds.xprtz, @@ -29,7 +28,7 @@ resource websiteResourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = module storageAccountModule 'modules/storageAccount.bicep' = { scope: websiteResourceGroup - name: 'StorageAccountDeploy-${application}' + name: 'storageAccountDeploy-${application}' params: { app: application } From fddbaf1b401d89a86b82284d3625995e10da6436 Mon Sep 17 00:00:00 2001 From: Martyn de Vries Date: Sat, 13 Dec 2025 10:14:32 +0100 Subject: [PATCH 7/7] Hostname bijgwerkt naar een type --- infrastructure/modules/frontdoor.bicep | 3 +- infrastructure/types.bicep | 3 +- infrastructure/website.bicep | 5 +- infrastructure/website.json | 1190 ++++++++++++++++++++++++ 4 files changed, 1194 insertions(+), 7 deletions(-) create mode 100644 infrastructure/website.json diff --git a/infrastructure/modules/frontdoor.bicep b/infrastructure/modules/frontdoor.bicep index f428b48..fa3868d 100644 --- a/infrastructure/modules/frontdoor.bicep +++ b/infrastructure/modules/frontdoor.bicep @@ -106,8 +106,7 @@ resource frontDoorRoute 'Microsoft.Cdn/profiles/afdEndpoints/routes@2024-02-01' } output frontDoorCustomDomainValidationTokens validationTokenType[] = [for (hostname, index) in hostnames: { - dnsZoneName: hostname.dnsZoneName - hostname: hostname.hostname + hostname: hostname validationToken: frontDoorCustomDomains[index].properties.validationProperties.validationToken }] output frontDoorHostname string = frontDoorEndpoint.properties.hostName diff --git a/infrastructure/types.bicep b/infrastructure/types.bicep index ead6b35..6e937a6 100644 --- a/infrastructure/types.bicep +++ b/infrastructure/types.bicep @@ -6,7 +6,6 @@ type hostnameType = { @export() type validationTokenType = { - dnsZoneName: string - hostname: string + hostname: hostnameType validationToken: string } diff --git a/infrastructure/website.bicep b/infrastructure/website.bicep index f78badc..ef30845 100644 --- a/infrastructure/website.bicep +++ b/infrastructure/website.bicep @@ -55,7 +55,7 @@ module dnsSettings 'modules/dns.bicep' = [for hostname in hostnames: if (isProd) frontDoorHostname: frontDoorSettings!.outputs.frontDoorHostname validationToken: filter( frontDoorSettings!.outputs.frontDoorCustomDomainValidationTokens, - validation => validation.hostname == hostname.hostname && validation.dnsZoneName == hostname.dnsZoneName + validation => validation.hostname.hostname == hostname.hostname && validation.hostname.dnsZoneName == hostname.dnsZoneName )[0] } } @@ -82,8 +82,7 @@ module imagesDnsSettings 'modules/dns.bicep' = if (isProd) { frontDoorEndpointId: imagesFrontDoorSettings!.outputs.frontDoorEndpointId frontDoorHostname: imagesFrontDoorSettings!.outputs.frontDoorHostname validationToken: { - dnsZoneName: imageHostname.dnsZoneName - hostname: imageHostname.hostname + hostname: imageHostname validationToken: imagesFrontDoorSettings!.outputs.frontDoorCustomDomainValidationToken } } 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