Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/_deploy-container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/_deploy-infrastructure.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
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 $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 $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
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' }}
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/_migrate-database.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -190,15 +190,15 @@ 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,
repo: context.repo.repo,
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)) {
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion cloud-infrastructure/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
80 changes: 37 additions & 43 deletions cloud-infrastructure/cluster/deploy-cluster.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,56 +19,64 @@ 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
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

export CONTAINER_REGISTRY_NAME=$UNIQUE_PREFIX$ENVIRONMENT
export GLOBAL_RESOURCE_GROUP_NAME="$UNIQUE_PREFIX-$ENVIRONMENT-global"
export CLUSTER_RESOURCE_GROUP_NAME="$UNIQUE_PREFIX-$ENVIRONMENT-$CLUSTER_LOCATION_ACRONYM"

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 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
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 $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

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"
export REVISION_SUFFIX=$(printf "%04x" $RANDOM | head -c 4)

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-$CLUSTER_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):
# 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.<domain> with the customDomainVerificationId value
# - A CNAME record: <domain> pointing to the container app's default domain
if [[ "$*" == *"--apply"* ]]
then
RED='\033[0;31m'
RESET='\033[0m' # Reset formatting

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.
env_details=$(az containerapp env show --name $RESOURCE_GROUP_NAME --resource-group $RESOURCE_GROUP_NAME)
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 $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')
default_domain=$(echo "$env_details" | jq -r '.properties.defaultDomain')
Expand All @@ -83,20 +91,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"
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"
. ../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')
Expand Down
4 changes: 2 additions & 2 deletions cloud-infrastructure/cluster/firewall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading
Loading