From 6865eaf788e974976eee38f657ac7dfc3cbd6990 Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Tue, 19 Aug 2025 10:33:49 +0200 Subject: [PATCH 01/12] Introduce BicepParam file to pass values from GitHub actions to Bicep instead of using batch --- .gitignore | 3 ++ .../cluster/deploy-cluster.sh | 39 +++++++++++++------ .../cluster/main-cluster.bicepparam | 15 +++++++ 3 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 cloud-infrastructure/cluster/main-cluster.bicepparam diff --git a/.gitignore b/.gitignore index f281134a18..102330c931 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +# Bicep generated parameter files +cloud-infrastructure/cluster/main-cluster.parameters.json + # User-specific files *.rsuser *.suo diff --git a/cloud-infrastructure/cluster/deploy-cluster.sh b/cloud-infrastructure/cluster/deploy-cluster.sh index 553ea25267..f9e69523d4 100755 --- a/cloud-infrastructure/cluster/deploy-cluster.sh +++ b/cloud-infrastructure/cluster/deploy-cluster.sh @@ -35,23 +35,34 @@ if [[ "$DOMAIN_NAME" == "-" ]]; then DOMAIN_NAME="" fi -CONTAINER_REGISTRY_NAME=$UNIQUE_PREFIX$ENVIRONMENT -ENVIRONMENT_RESOURCE_GROUP_NAME="$UNIQUE_PREFIX-$ENVIRONMENT" -RESOURCE_GROUP_NAME="$ENVIRONMENT_RESOURCE_GROUP_NAME-$CLUSTER_LOCATION_ACRONYM" -IS_DOMAIN_CONFIGURED=$(is_domain_configured "app-gateway" "$RESOURCE_GROUP_NAME") +export UNIQUE_PREFIX +export ENVIRONMENT +export LOCATION=$CLUSTER_LOCATION +export DOMAIN_NAME +export SQL_ADMIN_OBJECT_ID -APP_GATEWAY_VERSION=$(get_active_version "app-gateway" $RESOURCE_GROUP_NAME) -ACTIVE_ACCOUNT_MANAGEMENT_VERSION=$(get_active_version "account-management-api" $RESOURCE_GROUP_NAME) # The version from the API is use for both API and Workers -ACTIVE_BACK_OFFICE_VERSION=$(get_active_version "back-office-api" $RESOURCE_GROUP_NAME) # The version from the API is use for both API and Workers +export CONTAINER_REGISTRY_NAME=$UNIQUE_PREFIX$ENVIRONMENT +export ENVIRONMENT_RESOURCE_GROUP_NAME="$UNIQUE_PREFIX-$ENVIRONMENT" +export RESOURCE_GROUP_NAME="$ENVIRONMENT_RESOURCE_GROUP_NAME-$CLUSTER_LOCATION_ACRONYM" +export IS_DOMAIN_CONFIGURED=$(is_domain_configured "app-gateway" "$RESOURCE_GROUP_NAME") + +export APP_GATEWAY_VERSION=$(get_active_version "app-gateway" $RESOURCE_GROUP_NAME) +export ACCOUNT_MANAGEMENT_VERSION=$(get_active_version "account-management-api" $RESOURCE_GROUP_NAME) # The version from the API is use for both API and Workers +export BACK_OFFICE_VERSION=$(get_active_version "back-office-api" $RESOURCE_GROUP_NAME) # The version from the API is use for both API and Workers az extension add --name application-insights --allow-preview true -APPLICATIONINSIGHTS_CONNECTION_STRING=$(az monitor app-insights component show --app $UNIQUE_PREFIX-$ENVIRONMENT --resource-group $UNIQUE_PREFIX-$ENVIRONMENT --query connectionString --output tsv) +export APPLICATIONINSIGHTS_CONNECTION_STRING=$(az monitor app-insights component show --app $UNIQUE_PREFIX-$ENVIRONMENT --resource-group $UNIQUE_PREFIX-$ENVIRONMENT --query connectionString --output tsv) CURRENT_DATE=$(date +'%Y-%m-%dT%H-%M') -DEPLOYMENT_COMMAND="az deployment sub create" -DEPLOYMENT_PARAMETERS="-l $CLUSTER_LOCATION -n $CURRENT_DATE-$RESOURCE_GROUP_NAME --output json -f ./main-cluster.bicep -p resourceGroupName=$RESOURCE_GROUP_NAME environmentResourceGroupName=$ENVIRONMENT_RESOURCE_GROUP_NAME environment=$ENVIRONMENT containerRegistryName=$CONTAINER_REGISTRY_NAME domainName=$DOMAIN_NAME isDomainConfigured=$IS_DOMAIN_CONFIGURED sqlAdminObjectId=$SQL_ADMIN_OBJECT_ID appGatewayVersion=$APP_GATEWAY_VERSION accountManagementVersion=$ACTIVE_ACCOUNT_MANAGEMENT_VERSION backOfficeVersion=$ACTIVE_BACK_OFFICE_VERSION applicationInsightsConnectionString=$APPLICATIONINSIGHTS_CONNECTION_STRING" cd "$(dirname "${BASH_SOURCE[0]}")" + +# Build the .bicepparam file to generate parameters.json +bicep build-params ./main-cluster.bicepparam --outfile ./main-cluster.parameters.json + +DEPLOYMENT_COMMAND="az deployment sub create" +DEPLOYMENT_PARAMETERS="-l $CLUSTER_LOCATION -n $CURRENT_DATE-$RESOURCE_GROUP_NAME --output json -f ./main-cluster.bicep -p ./main-cluster.parameters.json" + . ../deploy.sh # When initially creating the Azure Container App with SSL and a custom domain, we need to run the deployment three times (see https://github.com/microsoft/azure-container-apps/tree/main/docs/templates/bicep/managedCertificates): @@ -86,8 +97,12 @@ then # If the domain was not configured during the first run and we didn't receive any warnings about missing DNS entries, we trigger the deployment again to complete the binding of the SSL Certificate to the domain. if [[ "$IS_DOMAIN_CONFIGURED" == "false" ]] && [[ "$DOMAIN_NAME" != "" ]]; then echo "Running deployment again to finalize setting up SSL certificate for $DOMAIN_NAME" - IS_DOMAIN_CONFIGURED=$(is_domain_configured "app-gateway" $RESOURCE_GROUP_NAME) - DEPLOYMENT_PARAMETERS="-l $CLUSTER_LOCATION -n $CURRENT_DATE-$RESOURCE_GROUP_NAME --output json -f ./main-cluster.bicep -p resourceGroupName=$RESOURCE_GROUP_NAME environmentResourceGroupName=$ENVIRONMENT_RESOURCE_GROUP_NAME environment=$ENVIRONMENT containerRegistryName=$CONTAINER_REGISTRY_NAME domainName=$DOMAIN_NAME isDomainConfigured=$IS_DOMAIN_CONFIGURED sqlAdminObjectId=$SQL_ADMIN_OBJECT_ID appGatewayVersion=$APP_GATEWAY_VERSION accountManagementVersion=$ACTIVE_ACCOUNT_MANAGEMENT_VERSION backOfficeVersion=$ACTIVE_BACK_OFFICE_VERSION applicationInsightsConnectionString=$APPLICATIONINSIGHTS_CONNECTION_STRING" + export IS_DOMAIN_CONFIGURED=$(is_domain_configured "app-gateway" $RESOURCE_GROUP_NAME) + + # Rebuild parameters with updated IS_DOMAIN_CONFIGURED + bicep build-params ./main-cluster.bicepparam --outfile ./main-cluster.parameters.json + + DEPLOYMENT_PARAMETERS="-l $CLUSTER_LOCATION -n $CURRENT_DATE-$RESOURCE_GROUP_NAME --output json -f ./main-cluster.bicep -p ./main-cluster.parameters.json" . ../deploy.sh cleaned_output=$(echo "$output" | sed '/^WARNING/d' | sed '/^\/home\/runner\/work\//d') diff --git a/cloud-infrastructure/cluster/main-cluster.bicepparam b/cloud-infrastructure/cluster/main-cluster.bicepparam new file mode 100644 index 0000000000..5ff6c3e597 --- /dev/null +++ b/cloud-infrastructure/cluster/main-cluster.bicepparam @@ -0,0 +1,15 @@ +using './main-cluster.bicep' + +// All parameters read from environment variables - nothing hardcoded +param location = readEnvironmentVariable('LOCATION') +param resourceGroupName = readEnvironmentVariable('RESOURCE_GROUP_NAME') +param environmentResourceGroupName = readEnvironmentVariable('ENVIRONMENT_RESOURCE_GROUP_NAME') +param environment = readEnvironmentVariable('ENVIRONMENT') +param containerRegistryName = readEnvironmentVariable('CONTAINER_REGISTRY_NAME') +param domainName = readEnvironmentVariable('DOMAIN_NAME', '') +param isDomainConfigured = bool(readEnvironmentVariable('IS_DOMAIN_CONFIGURED', 'false')) +param sqlAdminObjectId = readEnvironmentVariable('SQL_ADMIN_OBJECT_ID') +param appGatewayVersion = readEnvironmentVariable('APP_GATEWAY_VERSION') +param accountManagementVersion = readEnvironmentVariable('ACCOUNT_MANAGEMENT_VERSION') +param backOfficeVersion = readEnvironmentVariable('BACK_OFFICE_VERSION') +param applicationInsightsConnectionString = readEnvironmentVariable('APPLICATIONINSIGHTS_CONNECTION_STRING') From 6e629e182071c61f76a365b2b2dd614ead152229 Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Tue, 9 Sep 2025 08:47:07 +0200 Subject: [PATCH 02/12] Resolve unreliable initial deployments of microsoft-sql-server-diagnostic when SQL Server is not fully provisioned --- cloud-infrastructure/cluster/main-cluster.bicep | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud-infrastructure/cluster/main-cluster.bicep b/cloud-infrastructure/cluster/main-cluster.bicep index f71cd01c56..4379c4c972 100644 --- a/cloud-infrastructure/cluster/main-cluster.bicep +++ b/cloud-infrastructure/cluster/main-cluster.bicep @@ -123,6 +123,7 @@ module microsoftSqlDerverDiagnosticConfiguration '../modules/microsoft-sql-serve dianosticStorageAccountBlobEndpoint: diagnosticStorageAccount.outputs.blobEndpoint dianosticStorageAccountSubscriptionId: subscription().subscriptionId } + dependsOn: [microsoftSqlServer] } var isCustomDomainSet = domainName != '' From 61fb28bd8a507b5a65786ddf185a02c3652fe15e Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Fri, 21 Nov 2025 18:45:12 +0100 Subject: [PATCH 03/12] Upgrade Azure Container Apps API to version 2025-07-01 and leverage 'Auto' bindingType for single-deployment custom domain --- .../cluster/deploy-cluster.sh | 44 ++++--------------- .../cluster/main-cluster.bicep | 2 - .../cluster/main-cluster.bicepparam | 1 - .../modules/container-app.bicep | 34 ++++---------- .../modules/managed-certificate.bicep | 4 +- 5 files changed, 19 insertions(+), 66 deletions(-) diff --git a/cloud-infrastructure/cluster/deploy-cluster.sh b/cloud-infrastructure/cluster/deploy-cluster.sh index f9e69523d4..5bcf19d8ca 100755 --- a/cloud-infrastructure/cluster/deploy-cluster.sh +++ b/cloud-infrastructure/cluster/deploy-cluster.sh @@ -19,16 +19,6 @@ get_active_version() fi } -function is_domain_configured() { - # Get details about the container apps - local app_details=$(az containerapp show --name "$1" --resource-group "$2" 2>&1) - if [[ "$app_details" == *"ResourceNotFound"* ]] || [[ "$app_details" == *"ResourceGroupNotFound"* ]] || [[ "$app_details" == *"ERROR"* ]] ; then - echo "false" - else - local result=$(echo "$app_details" | jq -r '.properties.configuration.ingress.customDomains') - [[ "$result" != "null" ]] && echo "true" || echo "false" - fi -} if [[ "$DOMAIN_NAME" == "-" ]]; then # "-" is used to indicate that the domain is not configured @@ -44,7 +34,6 @@ export SQL_ADMIN_OBJECT_ID export CONTAINER_REGISTRY_NAME=$UNIQUE_PREFIX$ENVIRONMENT export ENVIRONMENT_RESOURCE_GROUP_NAME="$UNIQUE_PREFIX-$ENVIRONMENT" export RESOURCE_GROUP_NAME="$ENVIRONMENT_RESOURCE_GROUP_NAME-$CLUSTER_LOCATION_ACRONYM" -export IS_DOMAIN_CONFIGURED=$(is_domain_configured "app-gateway" "$RESOURCE_GROUP_NAME") export APP_GATEWAY_VERSION=$(get_active_version "app-gateway" $RESOURCE_GROUP_NAME) export ACCOUNT_MANAGEMENT_VERSION=$(get_active_version "account-management-api" $RESOURCE_GROUP_NAME) # The version from the API is use for both API and Workers @@ -65,10 +54,11 @@ DEPLOYMENT_PARAMETERS="-l $CLUSTER_LOCATION -n $CURRENT_DATE-$RESOURCE_GROUP_NAM . ../deploy.sh -# When initially creating the Azure Container App with SSL and a custom domain, we need to run the deployment three times (see https://github.com/microsoft/azure-container-apps/tree/main/docs/templates/bicep/managedCertificates): -# 1. On the initial run, the deployment will fail, providing instructions on how to manually create DNS TXT and CNAME records. After doing so, the workflow must be run again. -# 2. The second time, the DNS will be configured, and a certificate will be created. However, they will not be bound together, as this is a two-step process and they cannot be created in a single deployment. -# 3. The third deployment will bind the SSL Certificate to the Domain. This step will be triggered automatically. +# When initially creating the Azure Container App with SSL and a custom domain, the deployment may fail if DNS records are not configured. +# With bindingType: 'Auto' (API version 2025-07-01), certificates are created and bound in a single deployment. +# If the deployment fails, ensure DNS records are properly configured: +# - A TXT record: asuid. with the customDomainVerificationId value +# - A CNAME record: pointing to the container app's default domain if [[ "$*" == *"--apply"* ]] then RED='\033[0;31m' @@ -76,10 +66,10 @@ then cleaned_output=$(echo "$output" | sed '/^WARNING/d' | sed '/^\/home\/runner\/work\//d') # Check for the specific error message indicating that DNS Records are missing - if [[ $cleaned_output == *"InvalidCustomHostNameValidation"* ]] || [[ $cleaned_output == *"FailedCnameValidation"* ]] || [[ $cleaned_output == *"-certificate' under resource group '$RESOURCE_GROUP_NAME' was not found"* ]]; then - # Get details about the container apps environment. Although the creation of the container app fails, the verification ID on the container apps environment is consistent across all container apps. + if [[ $cleaned_output == *"InvalidCustomHostNameValidation"* ]] || [[ $cleaned_output == *"FailedCnameValidation"* ]]; then + # Get details about the container apps environment to provide DNS configuration instructions env_details=$(az containerapp env show --name $RESOURCE_GROUP_NAME --resource-group $RESOURCE_GROUP_NAME) - + # Extract the customDomainVerificationId and defaultDomain from the container apps environment custom_domain_verification_id=$(echo "$env_details" | jq -r '.properties.customDomainConfiguration.customDomainVerificationId') default_domain=$(echo "$env_details" | jq -r '.properties.defaultDomain') @@ -94,24 +84,6 @@ then exit 1 fi - # If the domain was not configured during the first run and we didn't receive any warnings about missing DNS entries, we trigger the deployment again to complete the binding of the SSL Certificate to the domain. - if [[ "$IS_DOMAIN_CONFIGURED" == "false" ]] && [[ "$DOMAIN_NAME" != "" ]]; then - echo "Running deployment again to finalize setting up SSL certificate for $DOMAIN_NAME" - export IS_DOMAIN_CONFIGURED=$(is_domain_configured "app-gateway" $RESOURCE_GROUP_NAME) - - # Rebuild parameters with updated IS_DOMAIN_CONFIGURED - bicep build-params ./main-cluster.bicepparam --outfile ./main-cluster.parameters.json - - DEPLOYMENT_PARAMETERS="-l $CLUSTER_LOCATION -n $CURRENT_DATE-$RESOURCE_GROUP_NAME --output json -f ./main-cluster.bicep -p ./main-cluster.parameters.json" - . ../deploy.sh - - cleaned_output=$(echo "$output" | sed '/^WARNING/d' | sed '/^\/home\/runner\/work\//d') - if [[ $cleaned_output == "ERROR:"* ]]; then - echo -e "${RED}$output" - exit 1 - fi - fi - # Extract the ID of the Managed Identities, which can be used to grant access to SQL Database ACCOUNT_MANAGEMENT_IDENTITY_CLIENT_ID=$(echo "$cleaned_output" | jq -r '.properties.outputs.accountManagementIdentityClientId.value') BACK_OFFICE_IDENTITY_CLIENT_ID=$(echo "$cleaned_output" | jq -r '.properties.outputs.backOfficeIdentityClientId.value') diff --git a/cloud-infrastructure/cluster/main-cluster.bicep b/cloud-infrastructure/cluster/main-cluster.bicep index 4379c4c972..f2d9bdfa8a 100644 --- a/cloud-infrastructure/cluster/main-cluster.bicep +++ b/cloud-infrastructure/cluster/main-cluster.bicep @@ -6,7 +6,6 @@ param environmentResourceGroupName string param environment string param containerRegistryName string param domainName string -param isDomainConfigured bool param sqlAdminObjectId string param appGatewayVersion string param accountManagementVersion string @@ -433,7 +432,6 @@ module appGateway '../modules/container-app.bicep' = { ingress: true hasProbesEndpoint: false domainName: domainName == '' ? '' : domainName - isDomainConfigured: domainName != '' && isDomainConfigured external: true environmentVariables: [ { diff --git a/cloud-infrastructure/cluster/main-cluster.bicepparam b/cloud-infrastructure/cluster/main-cluster.bicepparam index 5ff6c3e597..f1ec099250 100644 --- a/cloud-infrastructure/cluster/main-cluster.bicepparam +++ b/cloud-infrastructure/cluster/main-cluster.bicepparam @@ -7,7 +7,6 @@ param environmentResourceGroupName = readEnvironmentVariable('ENVIRONMENT_RESOUR param environment = readEnvironmentVariable('ENVIRONMENT') param containerRegistryName = readEnvironmentVariable('CONTAINER_REGISTRY_NAME') param domainName = readEnvironmentVariable('DOMAIN_NAME', '') -param isDomainConfigured = bool(readEnvironmentVariable('IS_DOMAIN_CONFIGURED', 'false')) param sqlAdminObjectId = readEnvironmentVariable('SQL_ADMIN_OBJECT_ID') param appGatewayVersion = readEnvironmentVariable('APP_GATEWAY_VERSION') param accountManagementVersion = readEnvironmentVariable('ACCOUNT_MANAGEMENT_VERSION') diff --git a/cloud-infrastructure/modules/container-app.bicep b/cloud-infrastructure/modules/container-app.bicep index 16bd44071c..347c595f95 100644 --- a/cloud-infrastructure/modules/container-app.bicep +++ b/cloud-infrastructure/modules/container-app.bicep @@ -15,7 +15,6 @@ param userAssignedIdentityName string param ingress bool param hasProbesEndpoint bool param domainName string = '' -param isDomainConfigured bool = false param external bool = false param environmentVariables object[] = [] param uniqueSuffix string = substring(newGuid(), 0, 4) @@ -28,9 +27,14 @@ resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@ var certificateName = '${domainName}-certificate' // Note: The `-certificate` is used to detect if a certificate in deploy-cluster.sh var isCustomDomainSet = domainName != '' -resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-05-02-preview' existing = if (isCustomDomainSet) { - name: containerAppsEnvironmentName -} +var customDomainConfiguration = isCustomDomainSet + ? [ + { + name: domainName + bindingType: 'Auto' + } + ] + : [] module newManagedCertificate './managed-certificate.bicep' = if (isCustomDomainSet) { name: '${resourceGroupName}-${name}-managed-certificate' @@ -45,21 +49,6 @@ module newManagedCertificate './managed-certificate.bicep' = if (isCustomDomainS } } -resource existingManagedCertificate 'Microsoft.App/managedEnvironments/managedCertificates@2023-05-02-preview' existing = if (isDomainConfigured) { - name: certificateName - parent: containerAppsEnvironment -} - -var customDomainConfiguration = isCustomDomainSet - ? [ - { - bindingType: isDomainConfigured ? 'SniEnabled' : 'Disabled' - name: domainName - certificateId: isDomainConfigured ? existingManagedCertificate.id : null - } - ] - : [] - // For the initial revision, we use the container image hello world quickstart image. // This allows for the container app to be created before the container image is pushed to the registry. var useQuickStartImage = containerImageTag == 'initial' @@ -71,7 +60,7 @@ var image = useQuickStartImage // Create a revisionSuffix that contains the version but is be unique for each deployment. E.g. "2024-4-24-1557-tzyb" var revisionSuffix = '${replace(containerImageTag, '.', '-')}-${substring(uniqueSuffix, 0, 4)}' -resource containerApp 'Microsoft.App/containerApps@2024-02-02-preview' = { +resource containerApp 'Microsoft.App/containerApps@2025-07-01' = { name: name location: location tags: tags @@ -160,11 +149,6 @@ resource containerApp 'Microsoft.App/containerApps@2024-02-02-preview' = { identity: userAssignedIdentity.id } ] - runtime: { - dotnet: { - autoConfigureDataProtection: true - } - } ingress: ingress ? { external: external diff --git a/cloud-infrastructure/modules/managed-certificate.bicep b/cloud-infrastructure/modules/managed-certificate.bicep index 27fc6af2b2..a6509ed4d9 100644 --- a/cloud-infrastructure/modules/managed-certificate.bicep +++ b/cloud-infrastructure/modules/managed-certificate.bicep @@ -4,11 +4,11 @@ param tags object param containerAppsEnvironmentName string param domainName string -resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2024-03-01' existing = { +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2025-07-01' existing = { name: containerAppsEnvironmentName } -resource managedCertificate 'Microsoft.App/managedEnvironments/managedCertificates@2024-03-01' = { +resource managedCertificate 'Microsoft.App/managedEnvironments/managedCertificates@2025-07-01' = { name: name parent: containerAppsEnvironment location: location From 1a8a1a7352461c4a41d8c501d58063279a95b452 Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Fri, 21 Nov 2025 20:39:14 +0100 Subject: [PATCH 04/12] Fix spelling error in appGateway module name --- cloud-infrastructure/cluster/main-cluster.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud-infrastructure/cluster/main-cluster.bicep b/cloud-infrastructure/cluster/main-cluster.bicep index f2d9bdfa8a..39b3b3514b 100644 --- a/cloud-infrastructure/cluster/main-cluster.bicep +++ b/cloud-infrastructure/cluster/main-cluster.bicep @@ -462,7 +462,7 @@ module appGateway '../modules/container-app.bicep' = { } } -module appGatwayAccountManagementStorageBlobDataReaderRoleAssignment '../modules/role-assignments-storage-blob-data-reader.bicep' = { +module appGatewayAccountManagementStorageBlobDataReaderRoleAssignment '../modules/role-assignments-storage-blob-data-reader.bicep' = { scope: clusterResourceGroup name: '${resourceGroupName}-app-gateway-account-management-blob-reader' params: { From dc6c4bd7b2758a121a7690dca21e11e6c9c126e2 Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Fri, 21 Nov 2025 20:43:22 +0100 Subject: [PATCH 05/12] Fix spelling errors and incorrect URLs in GitHub issue templates --- .github/ISSUE_TEMPLATE/config.yml | 6 +++--- .github/ISSUE_TEMPLATE/feature_request.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 8af6c68698..e02ed01418 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: false contact_links: - name: Ask a question - url: https://github.com/platform/platform/discussions/new/choose + url: https://github.com/platformplatform/PlatformPlatform/discussions/new/choose about: Start a new discussion on our GitHub Community pages - - name: Browse exisitng discussions - url: https://github.com/platform/platform/discussions + - name: Browse existing discussions + url: https://github.com/platformplatform/PlatformPlatform/discussions about: Visit our GitHub Community pages to view existing discussions (or click Discussions in the menu) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 5832e3a793..06ce88d5a0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: | - Thanks for taking the time to fill out this bug report! + Thanks for taking the time to fill out this feature request! - type: input id: contact attributes: @@ -19,7 +19,7 @@ body: id: detailed-description attributes: label: Detailed description? - description: Also tell us more about the bug, and what you were expecting. + description: Tell us more about the feature request and what you would like to see. value: | **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. From 32b7fe8563bb352eafb40d8ed6f832316e06f773 Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Fri, 21 Nov 2025 20:57:45 +0100 Subject: [PATCH 06/12] Show DNS configuration instructions during Plan phase for custom domains --- .github/workflows/_deploy-infrastructure.yml | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/_deploy-infrastructure.yml b/.github/workflows/_deploy-infrastructure.yml index ea56acaf75..268b944d6e 100644 --- a/.github/workflows/_deploy-infrastructure.yml +++ b/.github/workflows/_deploy-infrastructure.yml @@ -80,6 +80,33 @@ jobs: id: deploy_cluster run: bash ./cloud-infrastructure/cluster/deploy-cluster.sh ${{ inputs.unique_prefix }} ${{ inputs.azure_environment }} ${{ inputs.cluster_location }} ${{ inputs.cluster_location_acronym }} ${{ inputs.sql_admin_object_id }} ${{ inputs.domain_name }} --plan + - name: Show DNS Configuration + if: ${{ inputs.domain_name != '' && inputs.domain_name != '-' }} + run: | + RESOURCE_GROUP_NAME="${{ inputs.unique_prefix }}-${{ inputs.azure_environment }}-${{ inputs.cluster_location_acronym }}" + + # Try to get the Container Apps Environment details + env_details=$(az containerapp env show --name $RESOURCE_GROUP_NAME --resource-group $RESOURCE_GROUP_NAME 2>&1 || echo "") + + if [[ "$env_details" != "" ]] && [[ "$env_details" != *"ResourceNotFound"* ]] && [[ "$env_details" != *"ResourceGroupNotFound"* ]]; then + custom_domain_verification_id=$(echo "$env_details" | jq -r '.properties.customDomainConfiguration.customDomainVerificationId') + default_domain=$(echo "$env_details" | jq -r '.properties.defaultDomain') + + # Check if app-gateway already has the custom domain configured + app_gateway_details=$(az containerapp show --name app-gateway --resource-group $RESOURCE_GROUP_NAME 2>&1 || echo "") + custom_domains=$(echo "$app_gateway_details" | jq -r '.properties.configuration.ingress.customDomains // []') + + if [[ "$custom_domains" != "[]" ]] && [[ "$custom_domains" != "null" ]]; then + echo "$(date +"%Y-%m-%dT%H:%M:%S") Custom domain '${{ inputs.domain_name }}' is already configured correctly." + else + echo "$(date +"%Y-%m-%dT%H:%M:%S") Please add the following DNS entries and then retry:" + echo "- A TXT record with the name 'asuid.${{ inputs.domain_name }}' and the value '$custom_domain_verification_id'." + echo "- A CNAME record with the Host name '${{ inputs.domain_name }}' that points to address 'app-gateway.$default_domain'." + fi + else + echo "$(date +"%Y-%m-%dT%H:%M:%S") DNS configuration instructions will be shown after the Container Apps Environment is created." + fi + deploy: name: Deploy if: ${{ needs.plan.outputs.should_deploy == 'true' }} From 8e931ecab0965e818450f462b992c9c47d458e4e Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Fri, 21 Nov 2025 21:22:44 +0100 Subject: [PATCH 07/12] Upgrade all Bicep modules to latest stable API versions --- cloud-infrastructure/cluster/main-cluster.bicep | 2 +- cloud-infrastructure/environment/main-environment.bicep | 2 +- cloud-infrastructure/modules/communication-services.bicep | 4 ++-- cloud-infrastructure/modules/container-app.bicep | 2 +- .../modules/container-apps-environment.bicep | 4 ++-- cloud-infrastructure/modules/container-registry.bicep | 2 +- .../modules/log-analytics-workspace.bicep | 2 +- cloud-infrastructure/modules/microsoft-sql-database.bicep | 2 +- .../modules/microsoft-sql-server-diagnostic.bicep | 8 ++++---- cloud-infrastructure/modules/microsoft-sql-server.bicep | 6 +++--- .../role-assignments-container-registry-acr-pull.bicep | 2 +- ...ole-assignments-container-registry-data-importer.bicep | 2 +- .../role-assignments-storage-blob-data-contributor.bicep | 4 ++-- .../role-assignments-storage-blob-data-reader.bicep | 4 ++-- cloud-infrastructure/modules/storage-account.bicep | 4 ++-- .../modules/user-assigned-managed-identity.bicep | 2 +- cloud-infrastructure/modules/virtual-network.bicep | 2 +- 17 files changed, 27 insertions(+), 27 deletions(-) diff --git a/cloud-infrastructure/cluster/main-cluster.bicep b/cloud-infrastructure/cluster/main-cluster.bicep index 39b3b3514b..ce4aba9d9f 100644 --- a/cloud-infrastructure/cluster/main-cluster.bicep +++ b/cloud-infrastructure/cluster/main-cluster.bicep @@ -17,7 +17,7 @@ param mailSenderDisplayName string = 'PlatformPlatform' var storageAccountUniquePrefix = replace(resourceGroupName, '-', '') var tags = { environment: environment, 'managed-by': 'bicep' } -resource clusterResourceGroup 'Microsoft.Resources/resourceGroups@2024-11-01' = { +resource clusterResourceGroup 'Microsoft.Resources/resourceGroups@2025-04-01' = { name: resourceGroupName location: location tags: tags diff --git a/cloud-infrastructure/environment/main-environment.bicep b/cloud-infrastructure/environment/main-environment.bicep index 0300e5a9aa..2754037044 100644 --- a/cloud-infrastructure/environment/main-environment.bicep +++ b/cloud-infrastructure/environment/main-environment.bicep @@ -8,7 +8,7 @@ param productionServicePrincipalObjectId string = '' var tags = { environment: environment, 'managed-by': 'bicep' } -resource environmentResourceGroup 'Microsoft.Resources/resourceGroups@2024-11-01' = { +resource environmentResourceGroup 'Microsoft.Resources/resourceGroups@2025-04-01' = { name: resourceGroupName location: location tags: tags diff --git a/cloud-infrastructure/modules/communication-services.bicep b/cloud-infrastructure/modules/communication-services.bicep index 63cd152a1c..f03f7a4f0d 100644 --- a/cloud-infrastructure/modules/communication-services.bicep +++ b/cloud-infrastructure/modules/communication-services.bicep @@ -43,11 +43,11 @@ resource communicationServices 'Microsoft.Communication/communicationServices@20 } } -resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = { +resource keyVault 'Microsoft.KeyVault/vaults@2025-05-01' existing = { name: keyVaultName } -resource communicationServiceConnectionStringSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = { +resource communicationServiceConnectionStringSecret 'Microsoft.KeyVault/vaults/secrets@2025-05-01' = { parent: keyVault name: 'communication-services-connection-string' properties: { diff --git a/cloud-infrastructure/modules/container-app.bicep b/cloud-infrastructure/modules/container-app.bicep index 347c595f95..decbbd1f28 100644 --- a/cloud-infrastructure/modules/container-app.bicep +++ b/cloud-infrastructure/modules/container-app.bicep @@ -19,7 +19,7 @@ param external bool = false param environmentVariables object[] = [] param uniqueSuffix string = substring(newGuid(), 0, 4) -resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = { +resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' existing = { scope: resourceGroup(resourceGroupName) name: userAssignedIdentityName } diff --git a/cloud-infrastructure/modules/container-apps-environment.bicep b/cloud-infrastructure/modules/container-apps-environment.bicep index 34543714fa..38e8cab8ce 100644 --- a/cloud-infrastructure/modules/container-apps-environment.bicep +++ b/cloud-infrastructure/modules/container-apps-environment.bicep @@ -4,7 +4,7 @@ param tags object param subnetId string param environmentResourceGroupName string -resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' existing = { +resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2025-07-01' existing = { scope: resourceGroup('${environmentResourceGroupName}') name: environmentResourceGroupName } @@ -12,7 +12,7 @@ resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces var logAnalyticsCustomerId = existingLogAnalyticsWorkspace.properties.customerId var logAnalyticsSharedKey = existingLogAnalyticsWorkspace.listKeys().primarySharedKey -resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2024-03-01' = { +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2025-07-01' = { name: name location: location tags: tags diff --git a/cloud-infrastructure/modules/container-registry.bicep b/cloud-infrastructure/modules/container-registry.bicep index dacadf3ed4..3cce6af407 100644 --- a/cloud-infrastructure/modules/container-registry.bicep +++ b/cloud-infrastructure/modules/container-registry.bicep @@ -2,7 +2,7 @@ param name string param location string param tags object -resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-08-01-preview' = { +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2025-11-01' = { name: name location: location tags: tags diff --git a/cloud-infrastructure/modules/log-analytics-workspace.bicep b/cloud-infrastructure/modules/log-analytics-workspace.bicep index e7aa652c6e..ed1ac3a41c 100644 --- a/cloud-infrastructure/modules/log-analytics-workspace.bicep +++ b/cloud-infrastructure/modules/log-analytics-workspace.bicep @@ -2,7 +2,7 @@ param name string param location string param tags object -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2025-07-01' = { name: name location: location tags: tags diff --git a/cloud-infrastructure/modules/microsoft-sql-database.bicep b/cloud-infrastructure/modules/microsoft-sql-database.bicep index e3c2d66833..af14ed51c4 100644 --- a/cloud-infrastructure/modules/microsoft-sql-database.bicep +++ b/cloud-infrastructure/modules/microsoft-sql-database.bicep @@ -3,7 +3,7 @@ param databaseName string param location string param tags object -resource sqlDatabase 'Microsoft.Sql/servers/databases@2023-05-01-preview' = { +resource sqlDatabase 'Microsoft.Sql/servers/databases@2023-08-01' = { name: '${sqlServerName}/${databaseName}' location: location tags: tags diff --git a/cloud-infrastructure/modules/microsoft-sql-server-diagnostic.bicep b/cloud-infrastructure/modules/microsoft-sql-server-diagnostic.bicep index cc573c810f..385ecfe394 100644 --- a/cloud-infrastructure/modules/microsoft-sql-server-diagnostic.bicep +++ b/cloud-infrastructure/modules/microsoft-sql-server-diagnostic.bicep @@ -3,7 +3,7 @@ param microsoftSqlServerName string param dianosticStorageAccountSubscriptionId string param dianosticStorageAccountBlobEndpoint string -resource existingMicrosoftSqlServer 'Microsoft.Sql/servers@2023-05-01-preview' existing = { +resource existingMicrosoftSqlServer 'Microsoft.Sql/servers@2023-08-01' existing = { name: microsoftSqlServerName } @@ -17,13 +17,13 @@ module diagnosticStorageBlobDataContributorRoleAssignment './role-assignments-st } } -resource microsoftSqlServerOutboundFirewallRules 'Microsoft.Sql/servers/outboundFirewallRules@2023-05-01-preview' = { +resource microsoftSqlServerOutboundFirewallRules 'Microsoft.Sql/servers/outboundFirewallRules@2023-08-01' = { parent: existingMicrosoftSqlServer name: replace(replace(dianosticStorageAccountBlobEndpoint, 'https:', ''), '/', '') dependsOn: [diagnosticStorageBlobDataContributorRoleAssignment] } -resource microsoftSqlServerAuditingSettings 'Microsoft.Sql/servers/auditingSettings@2023-05-01-preview' = { +resource microsoftSqlServerAuditingSettings 'Microsoft.Sql/servers/auditingSettings@2023-08-01' = { parent: existingMicrosoftSqlServer name: 'default' properties: { @@ -42,7 +42,7 @@ resource microsoftSqlServerAuditingSettings 'Microsoft.Sql/servers/auditingSetti dependsOn: [microsoftSqlServerOutboundFirewallRules] } -resource microsoftSqlServerVulnerabilityAssessment 'Microsoft.Sql/servers/vulnerabilityAssessments@2023-05-01-preview' = { +resource microsoftSqlServerVulnerabilityAssessment 'Microsoft.Sql/servers/vulnerabilityAssessments@2023-08-01' = { name: 'default' parent: existingMicrosoftSqlServer properties: { diff --git a/cloud-infrastructure/modules/microsoft-sql-server.bicep b/cloud-infrastructure/modules/microsoft-sql-server.bicep index ffed094daa..27db6c328a 100644 --- a/cloud-infrastructure/modules/microsoft-sql-server.bicep +++ b/cloud-infrastructure/modules/microsoft-sql-server.bicep @@ -5,7 +5,7 @@ param subnetId string param tenantId string param sqlAdminObjectId string -resource microsoftSqlServer 'Microsoft.Sql/servers@2023-05-01-preview' = { +resource microsoftSqlServer 'Microsoft.Sql/servers@2023-08-01' = { name: name location: location tags: tags @@ -27,7 +27,7 @@ resource microsoftSqlServer 'Microsoft.Sql/servers@2023-05-01-preview' = { } } -resource sqlServerVirtualNetworkRule 'Microsoft.Sql/servers/virtualNetworkRules@2023-05-01-preview' = { +resource sqlServerVirtualNetworkRule 'Microsoft.Sql/servers/virtualNetworkRules@2023-08-01' = { name: 'sql-server-virtual-network-rule' parent: microsoftSqlServer properties: { @@ -36,7 +36,7 @@ resource sqlServerVirtualNetworkRule 'Microsoft.Sql/servers/virtualNetworkRules@ } } -resource microsoftSqlServerSecurityAlertPolicies 'Microsoft.Sql/servers/securityAlertPolicies@2023-05-01-preview' = { +resource microsoftSqlServerSecurityAlertPolicies 'Microsoft.Sql/servers/securityAlertPolicies@2023-08-01' = { parent: microsoftSqlServer name: 'Default' properties: { diff --git a/cloud-infrastructure/modules/role-assignments-container-registry-acr-pull.bicep b/cloud-infrastructure/modules/role-assignments-container-registry-acr-pull.bicep index a0ff961c9d..31e7e8a95a 100644 --- a/cloud-infrastructure/modules/role-assignments-container-registry-acr-pull.bicep +++ b/cloud-infrastructure/modules/role-assignments-container-registry-acr-pull.bicep @@ -1,7 +1,7 @@ param containerRegistryName string param principalId string -resource containerRegistryResource 'Microsoft.ContainerRegistry/registries@2023-08-01-preview' existing = { +resource containerRegistryResource 'Microsoft.ContainerRegistry/registries@2025-11-01' existing = { name: containerRegistryName } diff --git a/cloud-infrastructure/modules/role-assignments-container-registry-data-importer.bicep b/cloud-infrastructure/modules/role-assignments-container-registry-data-importer.bicep index 3870ea8d16..c8415fb2bd 100644 --- a/cloud-infrastructure/modules/role-assignments-container-registry-data-importer.bicep +++ b/cloud-infrastructure/modules/role-assignments-container-registry-data-importer.bicep @@ -1,7 +1,7 @@ param containerRegistryName string param principalId string -resource containerRegistryResource 'Microsoft.ContainerRegistry/registries@2023-08-01-preview' existing = { +resource containerRegistryResource 'Microsoft.ContainerRegistry/registries@2025-11-01' existing = { name: containerRegistryName } diff --git a/cloud-infrastructure/modules/role-assignments-storage-blob-data-contributor.bicep b/cloud-infrastructure/modules/role-assignments-storage-blob-data-contributor.bicep index 121915c64b..3f319e5f4e 100644 --- a/cloud-infrastructure/modules/role-assignments-storage-blob-data-contributor.bicep +++ b/cloud-infrastructure/modules/role-assignments-storage-blob-data-contributor.bicep @@ -2,12 +2,12 @@ param storageAccountName string param userAssignedIdentityName string = '' param principalId string = '' -resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (userAssignedIdentityName != '') { +resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' existing = if (userAssignedIdentityName != '') { scope: resourceGroup() name: userAssignedIdentityName ?? principalId } -resource existingStorageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = { +resource existingStorageAccount 'Microsoft.Storage/storageAccounts@2025-06-01' existing = { scope: resourceGroup() name: storageAccountName } diff --git a/cloud-infrastructure/modules/role-assignments-storage-blob-data-reader.bicep b/cloud-infrastructure/modules/role-assignments-storage-blob-data-reader.bicep index 604fc08c02..931a09c66c 100644 --- a/cloud-infrastructure/modules/role-assignments-storage-blob-data-reader.bicep +++ b/cloud-infrastructure/modules/role-assignments-storage-blob-data-reader.bicep @@ -2,12 +2,12 @@ param storageAccountName string param userAssignedIdentityName string = '' param principalId string = '' -resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (userAssignedIdentityName != '') { +resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' existing = if (userAssignedIdentityName != '') { scope: resourceGroup() name: userAssignedIdentityName ?? principalId } -resource existingStorageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = { +resource existingStorageAccount 'Microsoft.Storage/storageAccounts@2025-06-01' existing = { scope: resourceGroup() name: storageAccountName } diff --git a/cloud-infrastructure/modules/storage-account.bicep b/cloud-infrastructure/modules/storage-account.bicep index a55c50f161..06a08f3f2e 100644 --- a/cloud-infrastructure/modules/storage-account.bicep +++ b/cloud-infrastructure/modules/storage-account.bicep @@ -11,7 +11,7 @@ param containers array = [ } ] -resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = { +resource storageAccount 'Microsoft.Storage/storageAccounts@2025-06-01' = { name: name location: location tags: tags @@ -46,7 +46,7 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = { } } -resource blobContainers 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = [ +resource blobContainers 'Microsoft.Storage/storageAccounts/blobServices/containers@2025-06-01' = [ for container in containers: { name: '${name}/default/${container.name}' properties: { diff --git a/cloud-infrastructure/modules/user-assigned-managed-identity.bicep b/cloud-infrastructure/modules/user-assigned-managed-identity.bicep index 8708c2a020..628361a79f 100644 --- a/cloud-infrastructure/modules/user-assigned-managed-identity.bicep +++ b/cloud-infrastructure/modules/user-assigned-managed-identity.bicep @@ -6,7 +6,7 @@ param environmentResourceGroupName string param keyVaultName string param grantKeyVaultWritePermissions bool = false -resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { +resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = { name: name location: location tags: tags diff --git a/cloud-infrastructure/modules/virtual-network.bicep b/cloud-infrastructure/modules/virtual-network.bicep index 3630770dee..13740def21 100644 --- a/cloud-infrastructure/modules/virtual-network.bicep +++ b/cloud-infrastructure/modules/virtual-network.bicep @@ -3,7 +3,7 @@ param location string param tags object param address string -resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-05-01' = { +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2025-01-01' = { name: name location: location tags: tags From 0d6280ce4a6fcf993d0ded18b81f25843d419214 Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Fri, 21 Nov 2025 23:35:39 +0100 Subject: [PATCH 08/12] Remove unsupported softDeletePolicy and simplify SQL firewall rule name to eliminate what-if warnings --- cloud-infrastructure/modules/container-registry.bicep | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cloud-infrastructure/modules/container-registry.bicep b/cloud-infrastructure/modules/container-registry.bicep index 3cce6af407..aa7b532819 100644 --- a/cloud-infrastructure/modules/container-registry.bicep +++ b/cloud-infrastructure/modules/container-registry.bicep @@ -25,10 +25,6 @@ resource containerRegistry 'Microsoft.ContainerRegistry/registries@2025-11-01' = azureADAuthenticationAsArmPolicy: { status: 'Enabled' } - softDeletePolicy: { - retentionDays: 7 - status: 'disabled' - } } } } From 5b331d4fd988b4633f2d00947f0d8df715192ec1 Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Sat, 22 Nov 2025 00:07:00 +0100 Subject: [PATCH 09/12] Suppress false positive Bicep warnings and application-insights preview warning --- cloud-infrastructure/cluster/deploy-cluster.sh | 10 ++++++++-- ...ole-assignments-storage-blob-data-contributor.bicep | 1 + .../role-assignments-storage-blob-data-reader.bicep | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cloud-infrastructure/cluster/deploy-cluster.sh b/cloud-infrastructure/cluster/deploy-cluster.sh index 5bcf19d8ca..a17a0337ae 100755 --- a/cloud-infrastructure/cluster/deploy-cluster.sh +++ b/cloud-infrastructure/cluster/deploy-cluster.sh @@ -39,8 +39,14 @@ export APP_GATEWAY_VERSION=$(get_active_version "app-gateway" $RESOURCE_GROUP_NA export ACCOUNT_MANAGEMENT_VERSION=$(get_active_version "account-management-api" $RESOURCE_GROUP_NAME) # The version from the API is use for both API and Workers export BACK_OFFICE_VERSION=$(get_active_version "back-office-api" $RESOURCE_GROUP_NAME) # The version from the API is use for both API and Workers -az extension add --name application-insights --allow-preview true -export APPLICATIONINSIGHTS_CONNECTION_STRING=$(az monitor app-insights component show --app $UNIQUE_PREFIX-$ENVIRONMENT --resource-group $UNIQUE_PREFIX-$ENVIRONMENT --query connectionString --output tsv) +az extension add --name application-insights --allow-preview true --only-show-errors + +# Check if Application Insights exists before trying to get connection string +if az group exists --name $UNIQUE_PREFIX-$ENVIRONMENT 2>/dev/null | grep -q "true"; then + export APPLICATIONINSIGHTS_CONNECTION_STRING=$(az monitor app-insights component show --app $UNIQUE_PREFIX-$ENVIRONMENT --resource-group $UNIQUE_PREFIX-$ENVIRONMENT --query connectionString --output tsv) +else + export APPLICATIONINSIGHTS_CONNECTION_STRING="" +fi CURRENT_DATE=$(date +'%Y-%m-%dT%H-%M') diff --git a/cloud-infrastructure/modules/role-assignments-storage-blob-data-contributor.bicep b/cloud-infrastructure/modules/role-assignments-storage-blob-data-contributor.bicep index 3f319e5f4e..ac636a6ec8 100644 --- a/cloud-infrastructure/modules/role-assignments-storage-blob-data-contributor.bicep +++ b/cloud-infrastructure/modules/role-assignments-storage-blob-data-contributor.bicep @@ -16,6 +16,7 @@ var storageBlobDataContributorRoleDefinitionId = 'ba92f5b4-2d11-453d-a403-e96b00 resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(existingStorageAccount.id, userAssignedIdentityName, storageBlobDataContributorRoleDefinitionId) properties: { + #disable-next-line BCP318 principalId: userAssignedIdentityName == '' ? principalId : userAssignedIdentity.properties.principalId principalType: 'ServicePrincipal' roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', storageBlobDataContributorRoleDefinitionId) diff --git a/cloud-infrastructure/modules/role-assignments-storage-blob-data-reader.bicep b/cloud-infrastructure/modules/role-assignments-storage-blob-data-reader.bicep index 931a09c66c..3f26fcb80f 100644 --- a/cloud-infrastructure/modules/role-assignments-storage-blob-data-reader.bicep +++ b/cloud-infrastructure/modules/role-assignments-storage-blob-data-reader.bicep @@ -16,6 +16,7 @@ var storageBlobDataReaderRoleDefinitionId = '2a2b9908-6ea1-4ae2-8e65-a410df84e7d resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(existingStorageAccount.id, userAssignedIdentityName, storageBlobDataReaderRoleDefinitionId) properties: { + #disable-next-line BCP318 principalId: userAssignedIdentityName == '' ? principalId : userAssignedIdentity.properties.principalId principalType: 'ServicePrincipal' roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', storageBlobDataReaderRoleDefinitionId) From 735e50720eace94c74ebf27b8d41284801974ae3 Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Sat, 22 Nov 2025 11:56:27 +0100 Subject: [PATCH 10/12] Rename resource group variables to explicitly distinguish cluster and global resource groups --- .github/workflows/_deploy-container.yml | 7 +- .github/workflows/_deploy-infrastructure.yml | 6 +- .github/workflows/_migrate-database.yml | 12 +-- cloud-infrastructure/README.md | 3 +- .../cluster/deploy-cluster.sh | 18 ++-- cloud-infrastructure/cluster/firewall.sh | 4 +- .../cluster/grant-database-permissions.sh | 8 +- .../cluster/main-cluster.bicep | 101 +++++++++--------- .../cluster/main-cluster.bicepparam | 6 +- .../environment/deploy-environment.sh | 7 +- .../environment/main-environment.bicep | 28 ++--- .../modules/container-app.bicep | 8 +- .../modules/container-apps-environment.bicep | 7 +- .../user-assigned-managed-identity.bicep | 4 +- 14 files changed, 116 insertions(+), 103 deletions(-) diff --git a/.github/workflows/_deploy-container.yml b/.github/workflows/_deploy-container.yml index d71099c01b..5f2c254795 100644 --- a/.github/workflows/_deploy-container.yml +++ b/.github/workflows/_deploy-container.yml @@ -100,15 +100,16 @@ jobs: - name: Deploy Container run: | + CLUSTER_RESOURCE_GROUP_NAME="${{ env.UNIQUE_PREFIX }}-${{ env.ENVIRONMENT }}-${{ env.CLUSTER_LOCATION_ACRONYM }}" SUFFIX=$(echo "${{ inputs.version }}" | sed 's/\./-/g') - az containerapp update --name ${{ inputs.image_name }} --resource-group "${{ env.UNIQUE_PREFIX }}-${{ env.ENVIRONMENT }}-${{ env.CLUSTER_LOCATION_ACRONYM }}" --image "${{ env.UNIQUE_PREFIX }}${{ env.ENVIRONMENT }}.azurecr.io/${{ inputs.image_name }}:${{ inputs.version }}" --revision-suffix $SUFFIX + az containerapp update --name ${{ inputs.image_name }} --resource-group "$CLUSTER_RESOURCE_GROUP_NAME" --image "${{ env.UNIQUE_PREFIX }}${{ env.ENVIRONMENT }}.azurecr.io/${{ inputs.image_name }}:${{ inputs.version }}" --revision-suffix $SUFFIX echo "Waiting for the new revision to be active..." for i in {1..10}; do sleep 15 - RUNNING_STATUS=$(az containerapp revision list --name ${{ inputs.image_name }} --resource-group "${{ env.UNIQUE_PREFIX }}-${{ env.ENVIRONMENT }}-${{ env.CLUSTER_LOCATION_ACRONYM }}" --query "[?contains(name, '$SUFFIX')].properties.runningState" --output tsv) - HEALTH_STATUS=$(az containerapp revision list --name ${{ inputs.image_name }} --resource-group "${{ env.UNIQUE_PREFIX }}-${{ env.ENVIRONMENT }}-${{ env.CLUSTER_LOCATION_ACRONYM }}" --query "[?contains(name, '$SUFFIX')].properties.healthState" --output tsv) + RUNNING_STATUS=$(az containerapp revision list --name ${{ inputs.image_name }} --resource-group "$CLUSTER_RESOURCE_GROUP_NAME" --query "[?contains(name, '$SUFFIX')].properties.runningState" --output tsv) + HEALTH_STATUS=$(az containerapp revision list --name ${{ inputs.image_name }} --resource-group "$CLUSTER_RESOURCE_GROUP_NAME" --query "[?contains(name, '$SUFFIX')].properties.healthState" --output tsv) if [[ "$HEALTH_STATUS" == "Healthy" ]]; then echo "New revision is healthy. Running state: $RUNNING_STATUS" exit 0 diff --git a/.github/workflows/_deploy-infrastructure.yml b/.github/workflows/_deploy-infrastructure.yml index 268b944d6e..52cd14ce6d 100644 --- a/.github/workflows/_deploy-infrastructure.yml +++ b/.github/workflows/_deploy-infrastructure.yml @@ -83,17 +83,17 @@ jobs: - name: Show DNS Configuration if: ${{ inputs.domain_name != '' && inputs.domain_name != '-' }} run: | - RESOURCE_GROUP_NAME="${{ inputs.unique_prefix }}-${{ inputs.azure_environment }}-${{ inputs.cluster_location_acronym }}" + CLUSTER_RESOURCE_GROUP_NAME="${{ inputs.unique_prefix }}-${{ inputs.azure_environment }}-${{ inputs.cluster_location_acronym }}" # Try to get the Container Apps Environment details - env_details=$(az containerapp env show --name $RESOURCE_GROUP_NAME --resource-group $RESOURCE_GROUP_NAME 2>&1 || echo "") + env_details=$(az containerapp env show --name $CLUSTER_RESOURCE_GROUP_NAME --resource-group $CLUSTER_RESOURCE_GROUP_NAME 2>&1 || echo "") if [[ "$env_details" != "" ]] && [[ "$env_details" != *"ResourceNotFound"* ]] && [[ "$env_details" != *"ResourceGroupNotFound"* ]]; then custom_domain_verification_id=$(echo "$env_details" | jq -r '.properties.customDomainConfiguration.customDomainVerificationId') default_domain=$(echo "$env_details" | jq -r '.properties.defaultDomain') # Check if app-gateway already has the custom domain configured - app_gateway_details=$(az containerapp show --name app-gateway --resource-group $RESOURCE_GROUP_NAME 2>&1 || echo "") + app_gateway_details=$(az containerapp show --name app-gateway --resource-group $CLUSTER_RESOURCE_GROUP_NAME 2>&1 || echo "") custom_domains=$(echo "$app_gateway_details" | jq -r '.properties.configuration.ingress.customDomains // []') if [[ "$custom_domains" != "[]" ]] && [[ "$custom_domains" != "null" ]]; then diff --git a/.github/workflows/_migrate-database.yml b/.github/workflows/_migrate-database.yml index faaba53992..98c754bc80 100644 --- a/.github/workflows/_migrate-database.yml +++ b/.github/workflows/_migrate-database.yml @@ -51,7 +51,7 @@ jobs: env: UNIQUE_PREFIX: ${{ vars.UNIQUE_PREFIX }} TENANT_ID: ${{ vars.TENANT_ID }} - RESOURCE_GROUP_NAME: ${{ vars.UNIQUE_PREFIX }}-${{ inputs.azure_environment }}-${{ inputs.cluster_location_acronym }} + CLUSTER_RESOURCE_GROUP_NAME: ${{ vars.UNIQUE_PREFIX }}-${{ inputs.azure_environment }}-${{ inputs.cluster_location_acronym }} SQL_SERVER_NAME: ${{ vars.UNIQUE_PREFIX }}-${{ inputs.azure_environment }}-${{ inputs.cluster_location_acronym }} SQL_SERVER_FQDN: ${{ vars.UNIQUE_PREFIX }}-${{ inputs.azure_environment }}-${{ inputs.cluster_location_acronym }}.database.windows.net @@ -82,7 +82,7 @@ jobs: - name: Open Firewall working-directory: cloud-infrastructure/cluster env: - RESOURCE_GROUP_NAME: ${{ env.RESOURCE_GROUP_NAME }} + CLUSTER_RESOURCE_GROUP_NAME: ${{ env.CLUSTER_RESOURCE_GROUP_NAME }} SQL_SERVER_NAME: ${{ env.SQL_SERVER_NAME }} SQL_DATABASE_NAME: ${{ inputs.database_name }} run: bash ./firewall.sh open @@ -142,7 +142,7 @@ jobs: if: always() working-directory: cloud-infrastructure/cluster env: - RESOURCE_GROUP_NAME: ${{ env.RESOURCE_GROUP_NAME }} + CLUSTER_RESOURCE_GROUP_NAME: ${{ env.CLUSTER_RESOURCE_GROUP_NAME }} SQL_SERVER_NAME: ${{ env.SQL_SERVER_NAME }} SQL_DATABASE_NAME: ${{ inputs.database_name }} run: bash ./firewall.sh close @@ -238,7 +238,7 @@ jobs: env: UNIQUE_PREFIX: ${{ vars.UNIQUE_PREFIX }} TENANT_ID: ${{ vars.TENANT_ID }} - RESOURCE_GROUP_NAME: ${{ vars.UNIQUE_PREFIX }}-${{ inputs.azure_environment }}-${{ inputs.cluster_location_acronym }} + CLUSTER_RESOURCE_GROUP_NAME: ${{ vars.UNIQUE_PREFIX }}-${{ inputs.azure_environment }}-${{ inputs.cluster_location_acronym }} SQL_SERVER_NAME: ${{ vars.UNIQUE_PREFIX }}-${{ inputs.azure_environment }}-${{ inputs.cluster_location_acronym }} SQL_SERVER_FQDN: ${{ vars.UNIQUE_PREFIX }}-${{ inputs.azure_environment }}-${{ inputs.cluster_location_acronym }}.database.windows.net @@ -269,7 +269,7 @@ jobs: - name: Open Firewall working-directory: cloud-infrastructure/cluster env: - RESOURCE_GROUP_NAME: ${{ env.RESOURCE_GROUP_NAME }} + CLUSTER_RESOURCE_GROUP_NAME: ${{ env.CLUSTER_RESOURCE_GROUP_NAME }} SQL_SERVER_NAME: ${{ env.SQL_SERVER_NAME }} SQL_DATABASE_NAME: ${{ inputs.database_name }} run: bash ./firewall.sh open @@ -289,7 +289,7 @@ jobs: if: always() working-directory: cloud-infrastructure/cluster env: - RESOURCE_GROUP_NAME: ${{ env.RESOURCE_GROUP_NAME }} + CLUSTER_RESOURCE_GROUP_NAME: ${{ env.CLUSTER_RESOURCE_GROUP_NAME }} SQL_SERVER_NAME: ${{ env.SQL_SERVER_NAME }} SQL_DATABASE_NAME: ${{ inputs.database_name }} run: bash ./firewall.sh close diff --git a/cloud-infrastructure/README.md b/cloud-infrastructure/README.md index 21196ae70a..a70ad6d4e6 100644 --- a/cloud-infrastructure/README.md +++ b/cloud-infrastructure/README.md @@ -58,7 +58,8 @@ Examples of cluster-specific resources: - Communication Service: `ppdemo-stage-weu`, `ppdemo-prod-eus2` - Storage Accounts: `ppdemostageweuacctmgmt`, `ppdemoprodweudiagnostic` -Examples of environment-specific resources: +Examples of global resources (shared across all clusters in an environment): +- Resource Group: `ppdemo-stage-global`, `ppdemo-prod-global` - Application Insights: `ppdemo-stage`, `ppdemo-prod` - Log Analytics workspace: `ppdemo-stage`, `ppdemo-prod` - Container Registry: `ppdemostage`, `ppdemoprod` diff --git a/cloud-infrastructure/cluster/deploy-cluster.sh b/cloud-infrastructure/cluster/deploy-cluster.sh index a17a0337ae..2c4b5394cf 100755 --- a/cloud-infrastructure/cluster/deploy-cluster.sh +++ b/cloud-infrastructure/cluster/deploy-cluster.sh @@ -32,18 +32,18 @@ export DOMAIN_NAME export SQL_ADMIN_OBJECT_ID export CONTAINER_REGISTRY_NAME=$UNIQUE_PREFIX$ENVIRONMENT -export ENVIRONMENT_RESOURCE_GROUP_NAME="$UNIQUE_PREFIX-$ENVIRONMENT" -export RESOURCE_GROUP_NAME="$ENVIRONMENT_RESOURCE_GROUP_NAME-$CLUSTER_LOCATION_ACRONYM" +export GLOBAL_RESOURCE_GROUP_NAME="$UNIQUE_PREFIX-$ENVIRONMENT-global" +export CLUSTER_RESOURCE_GROUP_NAME="$UNIQUE_PREFIX-$ENVIRONMENT-$CLUSTER_LOCATION_ACRONYM" -export APP_GATEWAY_VERSION=$(get_active_version "app-gateway" $RESOURCE_GROUP_NAME) -export ACCOUNT_MANAGEMENT_VERSION=$(get_active_version "account-management-api" $RESOURCE_GROUP_NAME) # The version from the API is use for both API and Workers -export BACK_OFFICE_VERSION=$(get_active_version "back-office-api" $RESOURCE_GROUP_NAME) # The version from the API is use for both API and Workers +export APP_GATEWAY_VERSION=$(get_active_version "app-gateway" $CLUSTER_RESOURCE_GROUP_NAME) +export ACCOUNT_MANAGEMENT_VERSION=$(get_active_version "account-management-api" $CLUSTER_RESOURCE_GROUP_NAME) # The version from the API is use for both API and Workers +export BACK_OFFICE_VERSION=$(get_active_version "back-office-api" $CLUSTER_RESOURCE_GROUP_NAME) # The version from the API is use for both API and Workers az extension add --name application-insights --allow-preview true --only-show-errors # Check if Application Insights exists before trying to get connection string -if az group exists --name $UNIQUE_PREFIX-$ENVIRONMENT 2>/dev/null | grep -q "true"; then - export APPLICATIONINSIGHTS_CONNECTION_STRING=$(az monitor app-insights component show --app $UNIQUE_PREFIX-$ENVIRONMENT --resource-group $UNIQUE_PREFIX-$ENVIRONMENT --query connectionString --output tsv) +if az group exists --name $GLOBAL_RESOURCE_GROUP_NAME 2>/dev/null | grep -q "true"; then + export APPLICATIONINSIGHTS_CONNECTION_STRING=$(az monitor app-insights component show --app $UNIQUE_PREFIX-$ENVIRONMENT --resource-group $GLOBAL_RESOURCE_GROUP_NAME --query connectionString --output tsv) else export APPLICATIONINSIGHTS_CONNECTION_STRING="" fi @@ -56,7 +56,7 @@ cd "$(dirname "${BASH_SOURCE[0]}")" bicep build-params ./main-cluster.bicepparam --outfile ./main-cluster.parameters.json DEPLOYMENT_COMMAND="az deployment sub create" -DEPLOYMENT_PARAMETERS="-l $CLUSTER_LOCATION -n $CURRENT_DATE-$RESOURCE_GROUP_NAME --output json -f ./main-cluster.bicep -p ./main-cluster.parameters.json" +DEPLOYMENT_PARAMETERS="-l $CLUSTER_LOCATION -n $CURRENT_DATE-$CLUSTER_RESOURCE_GROUP_NAME --output json -f ./main-cluster.bicep -p ./main-cluster.parameters.json" . ../deploy.sh @@ -74,7 +74,7 @@ then # Check for the specific error message indicating that DNS Records are missing if [[ $cleaned_output == *"InvalidCustomHostNameValidation"* ]] || [[ $cleaned_output == *"FailedCnameValidation"* ]]; then # Get details about the container apps environment to provide DNS configuration instructions - env_details=$(az containerapp env show --name $RESOURCE_GROUP_NAME --resource-group $RESOURCE_GROUP_NAME) + env_details=$(az containerapp env show --name $CLUSTER_RESOURCE_GROUP_NAME --resource-group $CLUSTER_RESOURCE_GROUP_NAME) # Extract the customDomainVerificationId and defaultDomain from the container apps environment custom_domain_verification_id=$(echo "$env_details" | jq -r '.properties.customDomainConfiguration.customDomainVerificationId') diff --git a/cloud-infrastructure/cluster/firewall.sh b/cloud-infrastructure/cluster/firewall.sh index f06fe8c2e2..7f3c9d2c7e 100644 --- a/cloud-infrastructure/cluster/firewall.sh +++ b/cloud-infrastructure/cluster/firewall.sh @@ -4,8 +4,8 @@ FIREWALL_RULE_NAME="GitHub Action Workflows - ${SQL_DATABASE_NAME} - Only active if [[ "$1" == "open" ]] then echo "$(date +"%Y-%m-%dT%H:%M:%S") Add the IP $IP_ADDRESS to the SQL Server firewall on server $SQL_SERVER_NAME for database $SQL_DATABASE_NAME" - az sql server firewall-rule create --resource-group $RESOURCE_GROUP_NAME --server $SQL_SERVER_NAME --name "$FIREWALL_RULE_NAME" --start-ip-address $IP_ADDRESS --end-ip-address $IP_ADDRESS + az sql server firewall-rule create --resource-group $CLUSTER_RESOURCE_GROUP_NAME --server $SQL_SERVER_NAME --name "$FIREWALL_RULE_NAME" --start-ip-address $IP_ADDRESS --end-ip-address $IP_ADDRESS else echo "$(date +"%Y-%m-%dT%H:%M:%S") Delete the IP $IP_ADDRESS from the SQL Server firewall on server $SQL_SERVER_NAME for database $SQL_DATABASE_NAME" - az sql server firewall-rule delete --resource-group $RESOURCE_GROUP_NAME --server $SQL_SERVER_NAME --name "$FIREWALL_RULE_NAME" + az sql server firewall-rule delete --resource-group $CLUSTER_RESOURCE_GROUP_NAME --server $SQL_SERVER_NAME --name "$FIREWALL_RULE_NAME" fi diff --git a/cloud-infrastructure/cluster/grant-database-permissions.sh b/cloud-infrastructure/cluster/grant-database-permissions.sh index 2cb491f69a..3aeab581d6 100755 --- a/cloud-infrastructure/cluster/grant-database-permissions.sh +++ b/cloud-infrastructure/cluster/grant-database-permissions.sh @@ -4,9 +4,9 @@ CLUSTER_LOCATION_ACRONYM=$3 SQL_DATABASE_NAME=$4 MANAGEMENT_IDENTITY_CLIENT_ID=$5 -RESOURCE_GROUP_NAME=$UNIQUE_PREFIX-$ENVIRONMENT-$CLUSTER_LOCATION_ACRONYM -MANAGED_IDENTITY_NAME=$RESOURCE_GROUP_NAME-$4 -SQL_SERVER_NAME=$RESOURCE_GROUP_NAME +CLUSTER_RESOURCE_GROUP_NAME=$UNIQUE_PREFIX-$ENVIRONMENT-$CLUSTER_LOCATION_ACRONYM +MANAGED_IDENTITY_NAME=$CLUSTER_RESOURCE_GROUP_NAME-$4 +SQL_SERVER_NAME=$CLUSTER_RESOURCE_GROUP_NAME SQL_SERVER=$SQL_SERVER_NAME.database.windows.net cd "$(dirname "${BASH_SOURCE[0]}")" @@ -26,7 +26,7 @@ SID=$(awk -v id="$SID" 'BEGIN { substr(id,17) }') # Reverse the byte order for the first three sections of the GUID and concatenate -echo "$(date +"%Y-%m-%dT%H:%M:%S") Granting $MANAGED_IDENTITY_NAME (ID: $SID) in Recource group $RESOURCE_GROUP_NAME permissions on $SQL_SERVER/$SQL_DATABASE_NAME database" +echo "$(date +"%Y-%m-%dT%H:%M:%S") Granting $MANAGED_IDENTITY_NAME (ID: $SID) in Resource group $CLUSTER_RESOURCE_GROUP_NAME permissions on $SQL_SERVER/$SQL_DATABASE_NAME database" # Execute the SQL script using mssql-scripter. Pass the script as a heredoc to sqlcmd to allow for complex SQL. sqlcmd -S $SQL_SERVER -d $SQL_DATABASE_NAME --authentication-method=ActiveDirectoryDefault --exit-on-error << EOF diff --git a/cloud-infrastructure/cluster/main-cluster.bicep b/cloud-infrastructure/cluster/main-cluster.bicep index ce4aba9d9f..281c063738 100644 --- a/cloud-infrastructure/cluster/main-cluster.bicep +++ b/cloud-infrastructure/cluster/main-cluster.bicep @@ -1,8 +1,8 @@ targetScope = 'subscription' param location string = deployment().location -param resourceGroupName string -param environmentResourceGroupName string +param clusterResourceGroupName string +param globalResourceGroupName string param environment string param containerRegistryName string param domainName string @@ -14,32 +14,36 @@ param applicationInsightsConnectionString string param communicationServicesDataLocation string = 'europe' param mailSenderDisplayName string = 'PlatformPlatform' -var storageAccountUniquePrefix = replace(resourceGroupName, '-', '') +var storageAccountUniquePrefix = replace(clusterResourceGroupName, '-', '') var tags = { environment: environment, 'managed-by': 'bicep' } resource clusterResourceGroup 'Microsoft.Resources/resourceGroups@2025-04-01' = { - name: resourceGroupName + name: clusterResourceGroupName location: location tags: tags } +// Derive the resource name prefix by removing location suffix from cluster resource group name +// e.g., "pxp-stage-eu" -> "pxp-stage" +var resourceNamePrefix = substring(clusterResourceGroupName, 0, lastIndexOf(clusterResourceGroupName, '-')) + resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' existing = { - scope: resourceGroup('${environmentResourceGroupName}') - name: environmentResourceGroupName + scope: resourceGroup('${globalResourceGroupName}') + name: resourceNamePrefix } var subnetId = resourceId( subscription().subscriptionId, - resourceGroupName, + clusterResourceGroupName, 'Microsoft.Network/virtualNetworks/subnets', - resourceGroupName, + clusterResourceGroupName, 'subnet' ) var diagnosticStorageAccountName = '${storageAccountUniquePrefix}diagnostic' module diagnosticStorageAccount '../modules/storage-account.bicep' = { scope: clusterResourceGroup - name: '${resourceGroupName}-diagnostic-storage-account' + name: '${clusterResourceGroupName}-diagnostic-storage-account' params: { location: location name: diagnosticStorageAccountName @@ -50,10 +54,10 @@ module diagnosticStorageAccount '../modules/storage-account.bicep' = { module virtualNetwork '../modules/virtual-network.bicep' = { scope: clusterResourceGroup - name: '${resourceGroupName}-virtual-network' + name: '${clusterResourceGroupName}-virtual-network' params: { location: location - name: resourceGroupName + name: clusterResourceGroupName tags: tags address: '10.0.0.0' } @@ -61,23 +65,24 @@ module virtualNetwork '../modules/virtual-network.bicep' = { module containerAppsEnvironment '../modules/container-apps-environment.bicep' = { scope: clusterResourceGroup - name: '${resourceGroupName}-container-apps-environment' + name: '${clusterResourceGroupName}-container-apps-environment' params: { location: location - name: resourceGroupName + name: clusterResourceGroupName tags: tags subnetId: subnetId - environmentResourceGroupName: environmentResourceGroupName + globalResourceGroupName: globalResourceGroupName + logAnalyticsWorkspaceName: resourceNamePrefix } dependsOn: [virtualNetwork] } module keyVault '../modules/key-vault.bicep' = { scope: clusterResourceGroup - name: '${resourceGroupName}-key-vault' + name: '${clusterResourceGroupName}-key-vault' params: { location: location - name: resourceGroupName + name: clusterResourceGroupName tags: tags tenantId: subscription().tenantId subnetId: subnetId @@ -89,9 +94,9 @@ module keyVault '../modules/key-vault.bicep' = { module communicationService '../modules/communication-services.bicep' = { scope: clusterResourceGroup - name: '${resourceGroupName}-communication-services' + name: '${clusterResourceGroupName}-communication-services' params: { - name: resourceGroupName + name: clusterResourceGroupName tags: tags dataLocation: communicationServicesDataLocation mailSenderDisplayName: mailSenderDisplayName @@ -101,10 +106,10 @@ module communicationService '../modules/communication-services.bicep' = { module microsoftSqlServer '../modules/microsoft-sql-server.bicep' = { scope: clusterResourceGroup - name: '${resourceGroupName}-microsoft-sql-server' + name: '${clusterResourceGroupName}-microsoft-sql-server' params: { location: location - name: resourceGroupName + name: clusterResourceGroupName tags: tags subnetId: subnetId tenantId: subscription().tenantId @@ -115,10 +120,10 @@ module microsoftSqlServer '../modules/microsoft-sql-server.bicep' = { module microsoftSqlDerverDiagnosticConfiguration '../modules/microsoft-sql-server-diagnostic.bicep' = { scope: clusterResourceGroup - name: '${resourceGroupName}-microsoft-sql-server-diagnostic' + name: '${clusterResourceGroupName}-microsoft-sql-server-diagnostic' params: { diagnosticStorageAccountName: diagnosticStorageAccountName - microsoftSqlServerName: resourceGroupName + microsoftSqlServerName: clusterResourceGroupName dianosticStorageAccountBlobEndpoint: diagnosticStorageAccount.outputs.blobEndpoint dianosticStorageAccountSubscriptionId: subscription().subscriptionId } @@ -133,26 +138,26 @@ var cdnUrl = publicUrl // Account Management -var accountManagementIdentityName = '${resourceGroupName}-account-management' +var accountManagementIdentityName = '${clusterResourceGroupName}-account-management' module accountManagementIdentity '../modules/user-assigned-managed-identity.bicep' = { - name: '${resourceGroupName}-account-management-managed-identity' + name: '${clusterResourceGroupName}-account-management-managed-identity' scope: clusterResourceGroup params: { name: accountManagementIdentityName location: location tags: tags containerRegistryName: containerRegistryName - environmentResourceGroupName: environmentResourceGroupName + globalResourceGroupName: globalResourceGroupName keyVaultName: keyVault.outputs.name grantKeyVaultWritePermissions: true } } module accountManagementDatabase '../modules/microsoft-sql-database.bicep' = { - name: '${resourceGroupName}-account-management-sql-database' + name: '${clusterResourceGroupName}-account-management-sql-database' scope: clusterResourceGroup params: { - sqlServerName: resourceGroupName + sqlServerName: clusterResourceGroupName databaseName: 'account-management' location: location tags: tags @@ -163,7 +168,7 @@ module accountManagementDatabase '../modules/microsoft-sql-database.bicep' = { var accountManagementStorageAccountName = '${storageAccountUniquePrefix}acctmgmt' module accountManagementStorageAccount '../modules/storage-account.bicep' = { scope: clusterResourceGroup - name: '${resourceGroupName}-account-management-storage-account' + name: '${clusterResourceGroupName}-account-management-storage-account' params: { location: location name: accountManagementStorageAccountName @@ -220,13 +225,13 @@ var accountManagementEnvironmentVariables = [ ] module accountManagementWorkers '../modules/container-app.bicep' = { - name: '${resourceGroupName}-account-management-workers-container-app' + name: '${clusterResourceGroupName}-account-management-workers-container-app' scope: clusterResourceGroup params: { name: 'account-management-workers' location: location tags: tags - resourceGroupName: resourceGroupName + clusterResourceGroupName: clusterResourceGroupName containerAppsEnvironmentId: containerAppsEnvironment.outputs.environmentId containerAppsEnvironmentName: containerAppsEnvironment.outputs.name containerRegistryName: containerRegistryName @@ -244,13 +249,13 @@ module accountManagementWorkers '../modules/container-app.bicep' = { } module accountManagementApi '../modules/container-app.bicep' = { - name: '${resourceGroupName}-account-management-api-container-app' + name: '${clusterResourceGroupName}-account-management-api-container-app' scope: clusterResourceGroup params: { name: 'account-management-api' location: location tags: tags - resourceGroupName: resourceGroupName + clusterResourceGroupName: clusterResourceGroupName containerAppsEnvironmentId: containerAppsEnvironment.outputs.environmentId containerAppsEnvironmentName: containerAppsEnvironment.outputs.name containerRegistryName: containerRegistryName @@ -270,25 +275,25 @@ module accountManagementApi '../modules/container-app.bicep' = { // Back Office -var backOfficeIdentityName = '${resourceGroupName}-back-office' +var backOfficeIdentityName = '${clusterResourceGroupName}-back-office' module backOfficeIdentity '../modules/user-assigned-managed-identity.bicep' = { - name: '${resourceGroupName}-back-office-managed-identity' + name: '${clusterResourceGroupName}-back-office-managed-identity' scope: clusterResourceGroup params: { name: backOfficeIdentityName location: location tags: tags containerRegistryName: containerRegistryName - environmentResourceGroupName: environmentResourceGroupName + globalResourceGroupName: globalResourceGroupName keyVaultName: keyVault.outputs.name } } module backOfficeDatabase '../modules/microsoft-sql-database.bicep' = { - name: '${resourceGroupName}-back-office-sql-database' + name: '${clusterResourceGroupName}-back-office-sql-database' scope: clusterResourceGroup params: { - sqlServerName: resourceGroupName + sqlServerName: clusterResourceGroupName databaseName: 'back-office' location: location tags: tags @@ -299,7 +304,7 @@ module backOfficeDatabase '../modules/microsoft-sql-database.bicep' = { var backOfficeStorageAccountName = '${storageAccountUniquePrefix}backoffice' module backOfficeStorageAccount '../modules/storage-account.bicep' = { scope: clusterResourceGroup - name: '${resourceGroupName}-back-office-storage-account' + name: '${clusterResourceGroupName}-back-office-storage-account' params: { location: location name: backOfficeStorageAccountName @@ -346,13 +351,13 @@ var backOfficeEnvironmentVariables = [ ] module backOfficeWorkers '../modules/container-app.bicep' = { - name: '${resourceGroupName}-back-office-workers-container-app' + name: '${clusterResourceGroupName}-back-office-workers-container-app' scope: clusterResourceGroup params: { name: 'back-office-workers' location: location tags: tags - resourceGroupName: resourceGroupName + clusterResourceGroupName: clusterResourceGroupName containerAppsEnvironmentId: containerAppsEnvironment.outputs.environmentId containerAppsEnvironmentName: containerAppsEnvironment.outputs.name containerRegistryName: containerRegistryName @@ -370,13 +375,13 @@ module backOfficeWorkers '../modules/container-app.bicep' = { } module backOfficeApi '../modules/container-app.bicep' = { - name: '${resourceGroupName}-back-office-api-container-app' + name: '${clusterResourceGroupName}-back-office-api-container-app' scope: clusterResourceGroup params: { name: 'back-office-api' location: location tags: tags - resourceGroupName: resourceGroupName + clusterResourceGroupName: clusterResourceGroupName containerAppsEnvironmentId: containerAppsEnvironment.outputs.environmentId containerAppsEnvironmentName: containerAppsEnvironment.outputs.name containerRegistryName: containerRegistryName @@ -396,29 +401,29 @@ module backOfficeApi '../modules/container-app.bicep' = { // App Gateway -var appGatewayIdentityName = '${resourceGroupName}-app-gateway' +var appGatewayIdentityName = '${clusterResourceGroupName}-app-gateway' module appGatewayIdentity '../modules/user-assigned-managed-identity.bicep' = { - name: '${resourceGroupName}-app-gateway-managed-identity' + name: '${clusterResourceGroupName}-app-gateway-managed-identity' scope: clusterResourceGroup params: { name: appGatewayIdentityName location: location tags: tags containerRegistryName: containerRegistryName - environmentResourceGroupName: environmentResourceGroupName + globalResourceGroupName: globalResourceGroupName keyVaultName: keyVault.outputs.name } } var appGatewayContainerAppName = 'app-gateway' module appGateway '../modules/container-app.bicep' = { - name: '${resourceGroupName}-app-gateway-container-app' + name: '${clusterResourceGroupName}-app-gateway-container-app' scope: clusterResourceGroup params: { name: appGatewayContainerAppName location: location tags: tags - resourceGroupName: resourceGroupName + clusterResourceGroupName: clusterResourceGroupName containerAppsEnvironmentId: containerAppsEnvironment.outputs.environmentId containerAppsEnvironmentName: containerAppsEnvironment.outputs.name containerRegistryName: containerRegistryName @@ -464,7 +469,7 @@ module appGateway '../modules/container-app.bicep' = { module appGatewayAccountManagementStorageBlobDataReaderRoleAssignment '../modules/role-assignments-storage-blob-data-reader.bicep' = { scope: clusterResourceGroup - name: '${resourceGroupName}-app-gateway-account-management-blob-reader' + name: '${clusterResourceGroupName}-app-gateway-account-management-blob-reader' params: { storageAccountName: accountManagementStorageAccountName userAssignedIdentityName: appGatewayIdentityName diff --git a/cloud-infrastructure/cluster/main-cluster.bicepparam b/cloud-infrastructure/cluster/main-cluster.bicepparam index f1ec099250..7ed94bde6b 100644 --- a/cloud-infrastructure/cluster/main-cluster.bicepparam +++ b/cloud-infrastructure/cluster/main-cluster.bicepparam @@ -1,9 +1,9 @@ using './main-cluster.bicep' -// All parameters read from environment variables - nothing hardcoded +// Read all parameters from environment variables param location = readEnvironmentVariable('LOCATION') -param resourceGroupName = readEnvironmentVariable('RESOURCE_GROUP_NAME') -param environmentResourceGroupName = readEnvironmentVariable('ENVIRONMENT_RESOURCE_GROUP_NAME') +param clusterResourceGroupName = readEnvironmentVariable('CLUSTER_RESOURCE_GROUP_NAME') +param globalResourceGroupName = readEnvironmentVariable('GLOBAL_RESOURCE_GROUP_NAME') param environment = readEnvironmentVariable('ENVIRONMENT') param containerRegistryName = readEnvironmentVariable('CONTAINER_REGISTRY_NAME') param domainName = readEnvironmentVariable('DOMAIN_NAME', '') diff --git a/cloud-infrastructure/environment/deploy-environment.sh b/cloud-infrastructure/environment/deploy-environment.sh index abd3b7ef51..a72d24424e 100755 --- a/cloud-infrastructure/environment/deploy-environment.sh +++ b/cloud-infrastructure/environment/deploy-environment.sh @@ -5,11 +5,14 @@ ENVIRONMENT=$2 LOCATION_SHARED=$3 PRODUCTION_SERVICE_PRINCIPAL_OBJECT_ID=$4 -RESOURCE_GROUP_NAME=$UNIQUE_PREFIX-$ENVIRONMENT +export UNIQUE_PREFIX +export ENVIRONMENT + +GLOBAL_RESOURCE_GROUP_NAME=$UNIQUE_PREFIX-$ENVIRONMENT-global CONTAINER_REGISTRY_NAME=$UNIQUE_PREFIX$ENVIRONMENT CURRENT_DATE=$(date +'%Y-%m-%dT%H-%M') DEPLOYMENT_COMMAND="az deployment sub create" -DEPLOYMENT_PARAMETERS="-l $LOCATION_SHARED -n $CURRENT_DATE-$UNIQUE_PREFIX-$ENVIRONMENT --output table -f ./main-environment.bicep -p resourceGroupName=$RESOURCE_GROUP_NAME environment=$ENVIRONMENT containerRegistryName=$CONTAINER_REGISTRY_NAME" +DEPLOYMENT_PARAMETERS="-l $LOCATION_SHARED -n $CURRENT_DATE-$UNIQUE_PREFIX-$ENVIRONMENT --output table -f ./main-environment.bicep -p globalResourceGroupName=$GLOBAL_RESOURCE_GROUP_NAME uniquePrefix=$UNIQUE_PREFIX environment=$ENVIRONMENT containerRegistryName=$CONTAINER_REGISTRY_NAME" # Add production service principal object ID parameter if provided for ACR Pull role assignment to staging Container Registry if [[ "$PRODUCTION_SERVICE_PRINCIPAL_OBJECT_ID" != "-" ]]; then diff --git a/cloud-infrastructure/environment/main-environment.bicep b/cloud-infrastructure/environment/main-environment.bicep index 2754037044..e707317755 100644 --- a/cloud-infrastructure/environment/main-environment.bicep +++ b/cloud-infrastructure/environment/main-environment.bicep @@ -1,22 +1,24 @@ targetScope = 'subscription' param location string = deployment().location -param resourceGroupName string +param globalResourceGroupName string +param uniquePrefix string param environment string param containerRegistryName string param productionServicePrincipalObjectId string = '' var tags = { environment: environment, 'managed-by': 'bicep' } +var resourceNamePrefix = '${uniquePrefix}-${environment}' -resource environmentResourceGroup 'Microsoft.Resources/resourceGroups@2025-04-01' = { - name: resourceGroupName +resource globalResourceGroup 'Microsoft.Resources/resourceGroups@2025-04-01' = { + name: globalResourceGroupName location: location tags: tags } module containerRegistry '../modules/container-registry.bicep' = { - name: '${resourceGroupName}-container-registry' - scope: resourceGroup(environmentResourceGroup.name) + name: '${globalResourceGroupName}-container-registry' + scope: resourceGroup(globalResourceGroup.name) params: { name: containerRegistryName location: location @@ -26,8 +28,8 @@ module containerRegistry '../modules/container-registry.bicep' = { // Grant production service principal Container Registry Data Importer access to registry if specified module productionServicePrincipalDataImporter '../modules/role-assignments-container-registry-data-importer.bicep' = if (!empty(productionServicePrincipalObjectId)) { - name: '${resourceGroupName}-production-sp-data-importer' - scope: resourceGroup(environmentResourceGroup.name) + name: '${globalResourceGroupName}-production-sp-data-importer' + scope: resourceGroup(globalResourceGroup.name) params: { containerRegistryName: containerRegistryName principalId: productionServicePrincipalObjectId @@ -36,20 +38,20 @@ module productionServicePrincipalDataImporter '../modules/role-assignments-conta } module logAnalyticsWorkspace '../modules/log-analytics-workspace.bicep' = { - name: '${resourceGroupName}-log-analytics-workspace' - scope: resourceGroup(environmentResourceGroup.name) + name: '${globalResourceGroupName}-log-analytics-workspace' + scope: resourceGroup(globalResourceGroup.name) params: { - name: resourceGroupName + name: resourceNamePrefix location: location tags: tags } } module applicationInsights '../modules/application-insights.bicep' = { - name: '${resourceGroupName}-application-insights' - scope: resourceGroup(environmentResourceGroup.name) + name: '${globalResourceGroupName}-application-insights' + scope: resourceGroup(globalResourceGroup.name) params: { - name: resourceGroupName + name: resourceNamePrefix location: location tags: tags logAnalyticsWorkspaceId: logAnalyticsWorkspace.outputs.workspaceId diff --git a/cloud-infrastructure/modules/container-app.bicep b/cloud-infrastructure/modules/container-app.bicep index decbbd1f28..7e29f3820d 100644 --- a/cloud-infrastructure/modules/container-app.bicep +++ b/cloud-infrastructure/modules/container-app.bicep @@ -1,7 +1,7 @@ param name string param location string param tags object -param resourceGroupName string +param clusterResourceGroupName string param containerAppsEnvironmentId string param containerAppsEnvironmentName string param containerRegistryName string @@ -20,7 +20,7 @@ param environmentVariables object[] = [] param uniqueSuffix string = substring(newGuid(), 0, 4) resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' existing = { - scope: resourceGroup(resourceGroupName) + scope: resourceGroup(clusterResourceGroupName) name: userAssignedIdentityName } @@ -37,8 +37,8 @@ var customDomainConfiguration = isCustomDomainSet : [] module newManagedCertificate './managed-certificate.bicep' = if (isCustomDomainSet) { - name: '${resourceGroupName}-${name}-managed-certificate' - scope: resourceGroup(resourceGroupName) + name: '${clusterResourceGroupName}-${name}-managed-certificate' + scope: resourceGroup(clusterResourceGroupName) dependsOn: [containerApp] params: { name: certificateName diff --git a/cloud-infrastructure/modules/container-apps-environment.bicep b/cloud-infrastructure/modules/container-apps-environment.bicep index 38e8cab8ce..a89ace1134 100644 --- a/cloud-infrastructure/modules/container-apps-environment.bicep +++ b/cloud-infrastructure/modules/container-apps-environment.bicep @@ -2,11 +2,12 @@ param name string param location string param tags object param subnetId string -param environmentResourceGroupName string +param globalResourceGroupName string +param logAnalyticsWorkspaceName string resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2025-07-01' existing = { - scope: resourceGroup('${environmentResourceGroupName}') - name: environmentResourceGroupName + scope: resourceGroup('${globalResourceGroupName}') + name: logAnalyticsWorkspaceName } var logAnalyticsCustomerId = existingLogAnalyticsWorkspace.properties.customerId diff --git a/cloud-infrastructure/modules/user-assigned-managed-identity.bicep b/cloud-infrastructure/modules/user-assigned-managed-identity.bicep index 628361a79f..116979fce4 100644 --- a/cloud-infrastructure/modules/user-assigned-managed-identity.bicep +++ b/cloud-infrastructure/modules/user-assigned-managed-identity.bicep @@ -2,7 +2,7 @@ param name string param location string param tags object param containerRegistryName string -param environmentResourceGroupName string +param globalResourceGroupName string param keyVaultName string param grantKeyVaultWritePermissions bool = false @@ -14,7 +14,7 @@ resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@ module containerRegistryPermission './role-assignments-container-registry-acr-pull.bicep' = { name: '${name}-permission' - scope: resourceGroup(subscription().subscriptionId, environmentResourceGroupName) + scope: resourceGroup(subscription().subscriptionId, globalResourceGroupName) params: { containerRegistryName: containerRegistryName principalId: userAssignedIdentity.properties.principalId From 3e3cb4391167bca02ca4de2a1ac21e5fbc520136 Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Sat, 22 Nov 2025 17:40:10 +0100 Subject: [PATCH 11/12] Use uniqueString(deployment().name) for revision suffix to ensure unique revisions per deployment --- cloud-infrastructure/cluster/deploy-cluster.sh | 1 + cloud-infrastructure/cluster/main-cluster.bicep | 6 ++++++ cloud-infrastructure/cluster/main-cluster.bicepparam | 1 + cloud-infrastructure/modules/container-app.bicep | 8 ++++---- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cloud-infrastructure/cluster/deploy-cluster.sh b/cloud-infrastructure/cluster/deploy-cluster.sh index 2c4b5394cf..c96495d1c6 100755 --- a/cloud-infrastructure/cluster/deploy-cluster.sh +++ b/cloud-infrastructure/cluster/deploy-cluster.sh @@ -49,6 +49,7 @@ else fi CURRENT_DATE=$(date +'%Y-%m-%dT%H-%M') +export REVISION_SUFFIX=$(printf "%04x" $RANDOM | head -c 4) cd "$(dirname "${BASH_SOURCE[0]}")" diff --git a/cloud-infrastructure/cluster/main-cluster.bicep b/cloud-infrastructure/cluster/main-cluster.bicep index 281c063738..17425901b9 100644 --- a/cloud-infrastructure/cluster/main-cluster.bicep +++ b/cloud-infrastructure/cluster/main-cluster.bicep @@ -13,6 +13,7 @@ param backOfficeVersion string param applicationInsightsConnectionString string param communicationServicesDataLocation string = 'europe' param mailSenderDisplayName string = 'PlatformPlatform' +param revisionSuffix string var storageAccountUniquePrefix = replace(clusterResourceGroupName, '-', '') var tags = { environment: environment, 'managed-by': 'bicep' } @@ -244,6 +245,7 @@ module accountManagementWorkers '../modules/container-app.bicep' = { userAssignedIdentityName: accountManagementIdentityName ingress: true hasProbesEndpoint: false + revisionSuffix: revisionSuffix environmentVariables: accountManagementEnvironmentVariables } } @@ -268,6 +270,7 @@ module accountManagementApi '../modules/container-app.bicep' = { userAssignedIdentityName: accountManagementIdentityName ingress: true hasProbesEndpoint: true + revisionSuffix: revisionSuffix environmentVariables: accountManagementEnvironmentVariables } dependsOn: [accountManagementWorkers] @@ -370,6 +373,7 @@ module backOfficeWorkers '../modules/container-app.bicep' = { userAssignedIdentityName: backOfficeIdentityName ingress: true hasProbesEndpoint: false + revisionSuffix: revisionSuffix environmentVariables: backOfficeEnvironmentVariables } } @@ -394,6 +398,7 @@ module backOfficeApi '../modules/container-app.bicep' = { userAssignedIdentityName: backOfficeIdentityName ingress: true hasProbesEndpoint: true + revisionSuffix: revisionSuffix environmentVariables: backOfficeEnvironmentVariables } dependsOn: [backOfficeWorkers] @@ -438,6 +443,7 @@ module appGateway '../modules/container-app.bicep' = { hasProbesEndpoint: false domainName: domainName == '' ? '' : domainName external: true + revisionSuffix: revisionSuffix environmentVariables: [ { name: 'AZURE_CLIENT_ID' diff --git a/cloud-infrastructure/cluster/main-cluster.bicepparam b/cloud-infrastructure/cluster/main-cluster.bicepparam index 7ed94bde6b..c727ffd2ae 100644 --- a/cloud-infrastructure/cluster/main-cluster.bicepparam +++ b/cloud-infrastructure/cluster/main-cluster.bicepparam @@ -12,3 +12,4 @@ param appGatewayVersion = readEnvironmentVariable('APP_GATEWAY_VERSION') param accountManagementVersion = readEnvironmentVariable('ACCOUNT_MANAGEMENT_VERSION') param backOfficeVersion = readEnvironmentVariable('BACK_OFFICE_VERSION') param applicationInsightsConnectionString = readEnvironmentVariable('APPLICATIONINSIGHTS_CONNECTION_STRING') +param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') diff --git a/cloud-infrastructure/modules/container-app.bicep b/cloud-infrastructure/modules/container-app.bicep index 7e29f3820d..6449c38b6b 100644 --- a/cloud-infrastructure/modules/container-app.bicep +++ b/cloud-infrastructure/modules/container-app.bicep @@ -17,7 +17,7 @@ param hasProbesEndpoint bool param domainName string = '' param external bool = false param environmentVariables object[] = [] -param uniqueSuffix string = substring(newGuid(), 0, 4) +param revisionSuffix string resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' existing = { scope: resourceGroup(clusterResourceGroupName) @@ -57,8 +57,8 @@ var image = useQuickStartImage ? 'ghcr.io/platformplatform/quickstart:latest' : '${containerRegistryServerUrl}/${containerImageName}:${containerImageTag}' -// Create a revisionSuffix that contains the version but is be unique for each deployment. E.g. "2024-4-24-1557-tzyb" -var revisionSuffix = '${replace(containerImageTag, '.', '-')}-${substring(uniqueSuffix, 0, 4)}' +// Create a revisionSuffix that contains the version and random suffix. E.g. "2025-11-19-756-a3f2" +var fullRevisionSuffix = '${replace(containerImageTag, '.', '-')}-${revisionSuffix}' resource containerApp 'Microsoft.App/containerApps@2025-07-01' = { name: name @@ -136,7 +136,7 @@ resource containerApp 'Microsoft.App/containerApps@2025-07-01' = { ] } ] - revisionSuffix: revisionSuffix + revisionSuffix: fullRevisionSuffix scale: { minReplicas: minReplicas maxReplicas: maxReplicas From 6e2476e0af952f43e4e1e1cec9cb5fe649fc8d00 Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Sun, 23 Nov 2025 00:59:58 +0100 Subject: [PATCH 12/12] Scope migration pull request comment deletion to specific database and environment --- .github/workflows/_migrate-database.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_migrate-database.yml b/.github/workflows/_migrate-database.yml index 98c754bc80..af18c0a5f0 100644 --- a/.github/workflows/_migrate-database.yml +++ b/.github/workflows/_migrate-database.yml @@ -190,7 +190,7 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - // Delete previous migration info comments + // Delete previous migration info comments for this specific database const comments = await github.rest.issues.listComments({ issue_number: context.issue.number, owner: context.repo.owner, @@ -198,7 +198,7 @@ jobs: per_page: 100 }); - const MIGRATION_HEADER = '## Approve Database Migration'; + const MIGRATION_HEADER = '## Approve Database Migration `${{ inputs.database_name }}` database on `${{ inputs.azure_environment }}`'; for (const comment of comments.data) { if (comment.body && comment.body.startsWith(MIGRATION_HEADER)) {