Skip to content

Main

Main #278

Workflow file for this run

name: Main
on:
push:
branches:
- main
paths:
- "application/*"
- "application/shared-kernel/**"
- "application/shared-webapp/**"
- "application/main/**"
- ".github/workflows/main.yml"
- ".github/workflows/_deploy-container.yml"
- ".github/workflows/_migrate-database.yml"
- "!**.md"
pull_request:
paths:
- "application/*"
- "application/shared-kernel/**"
- "application/shared-webapp/**"
- "application/main/**"
- ".github/workflows/main.yml"
- ".github/workflows/_deploy-container.yml"
- ".github/workflows/_migrate-database.yml"
- "!**.md"
workflow_dispatch:
concurrency:
group: main-${{ github.ref }}
cancel-in-progress: true
permissions:
id-token: write
contents: read
pull-requests: write
jobs:
build-and-test:
name: Build and Test
runs-on: ubuntu-24.04
outputs:
version: ${{ steps.generate_version.outputs.version }}
deploy_staging: ${{ steps.determine_deployment.outputs.deploy_staging }}
deploy_production: ${{ steps.determine_deployment.outputs.deploy_production }}
migrations_changed: ${{ steps.detect_migrations.outputs.migrations_changed }}
steps:
- name: Checkout Code
uses: actions/checkout@v7
with:
# Full history is required to diff base..head for migration-change detection.
fetch-depth: 0
- name: Detect Migration Changes
id: detect_migrations
env:
BASE_REF: ${{ github.event.pull_request.base.sha || format('{0}~1', github.sha) }}
HEAD_REF: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
if git diff --name-only "$BASE_REF" "$HEAD_REF" | grep -q "^application/main/Core/Database/Migrations/"; then
echo "migrations_changed=true" >> $GITHUB_OUTPUT
echo "Migration changes detected — Database Staging will run."
else
echo "migrations_changed=false" >> $GITHUB_OUTPUT
echo "No migration changes — Database Staging will be skipped on pull requests."
fi
- name: Generate Version
id: generate_version
run: |
# Zero-padded so versions sort lexicographically and clock-time is unambiguous (e.g. 0904, not 94)
VERSION=$(date +"%Y.%m.%d.%H%M")
echo "Generated version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Determine Deployment Conditions
id: determine_deployment
run: |
deploy_staging="${{ github.ref == 'refs/heads/main' && vars.STAGING_CLUSTER_ENABLED == 'true' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'Deploy to Staging')) }}"
echo "deploy_staging=$deploy_staging" >> $GITHUB_OUTPUT
deploy_production="${{ github.ref == 'refs/heads/main' && vars.PRODUCTION_CLUSTER1_ENABLED == 'true' }}"
echo "deploy_production=$deploy_production" >> $GITHUB_OUTPUT
- name: Setup Node.js Environment
uses: actions/setup-node@v6
with:
node-version: 24
- name: Install Node Modules
working-directory: application
run: npm ci
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v5
with:
global-json-file: application/global.json
- name: Restore .NET Tools
working-directory: application
run: dotnet tool restore
- name: Restore .NET Dependencies
working-directory: application
run: dotnet restore
- name: Generate and Set User Secret for Token Signing Key
working-directory: application/shared-kernel/SharedKernel
run: |
# Extract UserSecretsId from the .csproj file
USER_SECRETS_ID=$(grep -oP '(?<=<UserSecretsId>).*?(?=</UserSecretsId>)' SharedKernel.csproj)
# Generate a 512-bit key and set it as a user secret that can be use for token signing when running tests
dotnet user-secrets set "authentication-token-signing-key" "$(openssl rand -base64 64)" --id $USER_SECRETS_ID
- name: Build Email Templates
working-directory: application
run: npx turbo run build --filter=@repo/emails
- name: Run Tests
working-directory: application
run: |
dotnet build main/Main.slnf --no-restore /p:Version=${{ steps.generate_version.outputs.version }} /p:DeploymentCommitHash=${{ github.event.pull_request.head.sha || github.sha }} /p:DeploymentGithubActionId=${{ github.run_id }} &&
dotnet test main/Main.slnf --no-build
- name: Save Backend Build Artifacts for Migration Plan
if: ${{ vars.STAGING_CLUSTER_ENABLED == 'true' && (github.event_name != 'pull_request' || steps.detect_migrations.outputs.migrations_changed == 'true') }}
uses: actions/upload-artifact@v7
with:
name: main-build
path: |
application/**/bin
application/**/obj
retention-days: 1
- name: Build Frontend Artifacts
if: ${{ steps.determine_deployment.outputs.deploy_staging == 'true' }}
working-directory: application
run: npm run build
- name: Publish Frontend Artifacts
if: ${{ steps.determine_deployment.outputs.deploy_staging == 'true' }}
working-directory: application/main/WebApp
run: npm run publish
- name: Publish API Build
if: ${{ steps.determine_deployment.outputs.deploy_staging == 'true' }}
working-directory: application/main
run: |
dotnet publish ./Api/Main.Api.csproj --configuration Release --no-restore --output ./Api/publish /p:Version=${{ steps.generate_version.outputs.version }} /p:DeploymentCommitHash=${{ github.event.pull_request.head.sha || github.sha }} /p:DeploymentGithubActionId=${{ github.run_id }}
- name: Save API Artifacts
if: ${{ steps.determine_deployment.outputs.deploy_staging == 'true' }}
uses: actions/upload-artifact@v7
with:
name: main-api
path: application/main/Api/publish/**/*
- name: Publish Workers Build
if: ${{ steps.determine_deployment.outputs.deploy_staging == 'true' }}
working-directory: application/main
run: |
dotnet publish ./Workers/Main.Workers.csproj --configuration Release --no-restore --output ./Workers/publish /p:Version=${{ steps.generate_version.outputs.version }} /p:DeploymentCommitHash=${{ github.event.pull_request.head.sha || github.sha }} /p:DeploymentGithubActionId=${{ github.run_id }}
- name: Save Workers Artifacts
if: ${{ steps.determine_deployment.outputs.deploy_staging == 'true' }}
uses: actions/upload-artifact@v7
with:
name: main-workers
path: application/main/Workers/publish/**/*
database-migrations-stage:
name: Database Staging
if: ${{ vars.STAGING_CLUSTER_ENABLED == 'true' && (github.event_name != 'pull_request' || needs.build-and-test.outputs.migrations_changed == 'true') }}
needs: build-and-test
uses: ./.github/workflows/_migrate-database.yml
secrets: inherit
with:
azure_environment: "stage"
cluster_location_acronym: ${{ vars.STAGING_CLUSTER_LOCATION_ACRONYM }}
service_principal_id: ${{ vars.STAGING_SERVICE_PRINCIPAL_ID }}
subscription_id: ${{ vars.STAGING_SUBSCRIPTION_ID }}
database_name: main
relative_project_path: main/Core/Main.csproj
relative_startup_project: main/Api/Main.Api.csproj
db_context: MainDbContext
apply_migrations: ${{ needs.build-and-test.outputs.deploy_staging == 'true' }}
build_artifact_name: main-build
api-stage:
name: API Staging
# !cancelled() keeps this from auto-skipping when database-migrations-stage is skipped (it is, on
# pull requests without migration changes); the result guards still block on a real failure.
if: ${{ !cancelled() && needs.build-and-test.result == 'success' && needs.database-migrations-stage.result != 'failure' && needs.build-and-test.outputs.deploy_staging == 'true' }}
needs: [build-and-test, database-migrations-stage]
uses: ./.github/workflows/_deploy-container.yml
secrets: inherit
with:
azure_environment: "stage"
cluster_location_acronym: ${{ vars.STAGING_CLUSTER_LOCATION_ACRONYM }}
service_principal_id: ${{ vars.STAGING_SERVICE_PRINCIPAL_ID }}
subscription_id: ${{ vars.STAGING_SUBSCRIPTION_ID }}
image_name: main-api
version: ${{ needs.build-and-test.outputs.version }}
artifacts_name: main-api
artifacts_path: application/main/Api/publish
docker_context: ./application/main
docker_file: ./Api/Dockerfile
workers-stage:
name: Workers Staging
# !cancelled() keeps this from auto-skipping when database-migrations-stage is skipped (it is, on
# pull requests without migration changes); the result guards still block on a real failure.
if: ${{ !cancelled() && needs.build-and-test.result == 'success' && needs.database-migrations-stage.result != 'failure' && needs.build-and-test.outputs.deploy_staging == 'true' }}
needs: [build-and-test, database-migrations-stage]
uses: ./.github/workflows/_deploy-container.yml
secrets: inherit
with:
azure_environment: "stage"
cluster_location_acronym: ${{ vars.STAGING_CLUSTER_LOCATION_ACRONYM }}
service_principal_id: ${{ vars.STAGING_SERVICE_PRINCIPAL_ID }}
subscription_id: ${{ vars.STAGING_SUBSCRIPTION_ID }}
image_name: main-workers
version: ${{ needs.build-and-test.outputs.version }}
artifacts_name: main-workers
artifacts_path: application/main/Workers/publish
docker_context: ./application/main
docker_file: ./Workers/Dockerfile
database-migrations-prod1:
name: Database Production
if: ${{ needs.build-and-test.outputs.deploy_production == 'true' }}
needs: [build-and-test, api-stage, workers-stage]
uses: ./.github/workflows/_migrate-database.yml
secrets: inherit
with:
azure_environment: "prod"
cluster_location_acronym: ${{ vars.PRODUCTION_CLUSTER1_LOCATION_ACRONYM }}
service_principal_id: ${{ vars.PRODUCTION_SERVICE_PRINCIPAL_ID }}
subscription_id: ${{ vars.PRODUCTION_SUBSCRIPTION_ID }}
database_name: main
relative_project_path: main/Core/Main.csproj
relative_startup_project: main/Api/Main.Api.csproj
db_context: MainDbContext
apply_migrations: true
build_artifact_name: main-build
api-prod1:
name: API Production
if: ${{ needs.build-and-test.outputs.deploy_production == 'true' }}
needs: [build-and-test, database-migrations-prod1]
uses: ./.github/workflows/_deploy-container.yml
secrets: inherit
with:
azure_environment: "prod"
cluster_location_acronym: ${{ vars.PRODUCTION_CLUSTER1_LOCATION_ACRONYM }}
service_principal_id: ${{ vars.PRODUCTION_SERVICE_PRINCIPAL_ID }}
subscription_id: ${{ vars.PRODUCTION_SUBSCRIPTION_ID }}
image_name: main-api
version: ${{ needs.build-and-test.outputs.version }}
artifacts_name: main-api
artifacts_path: application/main/Api/publish
docker_context: ./application/main
docker_file: ./Api/Dockerfile
workers-prod1:
name: Workers Production
if: ${{ needs.build-and-test.outputs.deploy_production == 'true' }}
needs: [build-and-test, database-migrations-prod1]
uses: ./.github/workflows/_deploy-container.yml
secrets: inherit
with:
azure_environment: "prod"
cluster_location_acronym: ${{ vars.PRODUCTION_CLUSTER1_LOCATION_ACRONYM }}
service_principal_id: ${{ vars.PRODUCTION_SERVICE_PRINCIPAL_ID }}
subscription_id: ${{ vars.PRODUCTION_SUBSCRIPTION_ID }}
image_name: main-workers
version: ${{ needs.build-and-test.outputs.version }}
artifacts_name: main-workers
artifacts_path: application/main/Workers/publish
docker_context: ./application/main
docker_file: ./Workers/Dockerfile