Skip to content

chore(deps): bump vue from 3.5.35 to 3.5.38 in /EssentialCSharp.Web (… #2293

chore(deps): bump vue from 3.5.35 to 3.5.38 in /EssentialCSharp.Web (…

chore(deps): bump vue from 3.5.35 to 3.5.38 in /EssentialCSharp.Web (… #2293

name: Build, Test, and Deploy EssentialCSharp.Web
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
id-token: write
contents: read
jobs:
build-and-test:
runs-on: ubuntu-latest
environment: "BuildAndUploadImage"
steps:
- uses: actions/checkout@v6
- name: Set up .NET Core
uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
source-url: https://pkgs.dev.azure.com/intelliTect/_packaging/EssentialCSharp/nuget/v3/index.json
env:
NUGET_AUTH_TOKEN: ${{ secrets.AZURE_DEVOPS_PAT }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
cache-dependency-path: EssentialCSharp.Web/package-lock.json
- name: Set up dependency caching for faster builds
uses: actions/cache@v5
id: nuget-cache
with:
path: |
~/.nuget/packages
${{ github.workspace }}/**/obj/project.assets.json
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
${{ runner.os }}-nuget-
- name: Restore with dotnet
run: dotnet restore
- name: Build with dotnet
run: dotnet build -p:ContinuousIntegrationBuild=True -p:ReleaseDateAttribute=True --configuration Release --no-restore
- name: Expose GitHub Actions Runtime
uses: actions/github-script@v9
with:
script: |
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN']);
core.exportVariable('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL']);
- name: Run .NET Tests
run: dotnet test --no-build --configuration Release
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
id: buildx
- name: Restore BuildKit cache mounts
uses: actions/cache@v5
id: buildkit-cache
with:
path: buildkit-cache
key: buildkit-cache-${{ hashFiles('EssentialCSharp.Web/Dockerfile', 'Directory.Packages.props', '**/*.csproj', 'EssentialCSharp.Web/package-lock.json') }}
restore-keys: buildkit-cache-
- name: Inject BuildKit cache mounts
uses: reproducible-containers/buildkit-cache-dance@v3
with:
builder: ${{ steps.buildx.outputs.name }}
dockerfile: EssentialCSharp.Web/Dockerfile
cache-dir: buildkit-cache
skip-extraction: false
- name: Generate Docker NuGet credentials config
env:
NUGET_FEED_TOKEN: ${{ secrets.AZURE_DEVOPS_PAT }}
run: |
mkdir -p "${RUNNER_TEMP}/nuget"
bash scripts/nuget/emit-creds-config.sh > "${RUNNER_TEMP}/nuget/NuGet.Config"
chmod 600 "${RUNNER_TEMP}/nuget/NuGet.Config"
# Only build for dev registry — prod gets the image via az acr import in deploy-production
- name: Build Container Image
if: github.event_name != 'pull_request_target' && github.event_name != 'pull_request'
uses: docker/build-push-action@v7
with:
tags: ${{ vars.DEVCONTAINER_REGISTRY }}/essentialcsharpweb:${{ github.sha }},${{ vars.DEVCONTAINER_REGISTRY }}/essentialcsharpweb:latest
file: ./EssentialCSharp.Web/Dockerfile
context: .
secret-files: |
"nuget_config=${{ runner.temp }}/nuget/NuGet.Config"
outputs: type=docker,dest=${{ github.workspace }}/essentialcsharpwebimage.tar
cache-from: type=gha,scope=essentialcsharpweb-main
cache-to: type=gha,mode=max,scope=essentialcsharpweb-main
- name: Remove Docker NuGet credentials config
if: always()
run: rm -f "${RUNNER_TEMP}/nuget/NuGet.Config"
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: essentialcsharpwebimage
path: ${{ github.workspace }}/essentialcsharpwebimage.tar
deploy-development:
if: github.event_name != 'pull_request_target' && github.event_name != 'pull_request'
runs-on: ubuntu-latest
needs: build-and-test
concurrency:
group: deploy-development
cancel-in-progress: false
environment:
name: "Development"
permissions:
id-token: write
contents: read
steps:
- name: Azure Login
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Download artifact
uses: actions/download-artifact@v8
with:
name: essentialcsharpwebimage
path: ${{ github.workspace }}
- name: Load image
run: |
docker load --input ${{ github.workspace }}/essentialcsharpwebimage.tar
docker image ls -a
- name: Log in to container registry
run: |
REGISTRY="${{ vars.DEVCONTAINER_REGISTRY }}"
az acr login --name "${REGISTRY%.azurecr.io}"
- name: Push Image to Dev Container Registry
run: docker push --all-tags ${{ vars.DEVCONTAINER_REGISTRY }}/essentialcsharpweb
- name: Deploy to Container App
env:
CONTAINER_APP_NAME: ${{ vars.CONTAINER_APP_NAME }}
RESOURCEGROUP: ${{ vars.RESOURCEGROUP }}
run: |
az extension add --name containerapp --upgrade --only-show-errors
az containerapp update \
--name "$CONTAINER_APP_NAME" \
--resource-group "$RESOURCEGROUP" \
--image "${{ vars.DEVCONTAINER_REGISTRY }}/essentialcsharpweb:${{ github.sha }}"
- name: Logout of Azure CLI
if: always()
uses: azure/CLI@v3
with:
inlineScript: |
az logout
az cache purge
az account clear
deploy-production:
if: github.event_name != 'pull_request_target' && github.event_name != 'pull_request'
runs-on: ubuntu-latest
needs: [deploy-development]
concurrency:
group: deploy-production
cancel-in-progress: true
environment:
name: "Production"
permissions:
id-token: write
contents: write # needed for git deploy tag
actions: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Azure Login
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
# Server-side copy from dev ACR to prod ACR — no artifact download needed.
# PREREQUISITE: prod OIDC identity must have AcrPull on the dev ACR (Terraform RBAC).
- name: Import image from dev ACR to prod ACR
id: import
run: |
DEV_ACR="${{ vars.DEVCONTAINER_REGISTRY }}"
PROD_ACR="${{ vars.PRODCONTAINER_REGISTRY }}"
az acr import \
--name "${PROD_ACR%.azurecr.io}" \
--source "${DEV_ACR}/essentialcsharpweb:${{ github.sha }}" \
--image "essentialcsharpweb:${{ github.sha }}" \
--image "essentialcsharpweb:latest" \
--force
DIGEST=$(az acr repository show \
--name "${PROD_ACR%.azurecr.io}" \
--image "essentialcsharpweb:${{ github.sha }}" \
--query "digest" -o tsv)
if [ -z "$DIGEST" ]; then
echo "::error::Failed to capture image digest from prod ACR after import"
exit 1
fi
echo "digest=$DIGEST" >> $GITHUB_OUTPUT
- name: Deploy to Container App
env:
CONTAINER_APP_NAME: ${{ vars.CONTAINER_APP_NAME }}
RESOURCEGROUP: ${{ vars.RESOURCEGROUP }}
run: |
az extension add --name containerapp --upgrade --only-show-errors
az containerapp update \
--name "$CONTAINER_APP_NAME" \
--resource-group "$RESOURCEGROUP" \
--image "${{ vars.PRODCONTAINER_REGISTRY }}/essentialcsharpweb@${{ steps.import.outputs.digest }}"
- name: Verify deployed image
env:
CONTAINER_APP_NAME: ${{ vars.CONTAINER_APP_NAME }}
RESOURCEGROUP: ${{ vars.RESOURCEGROUP }}
run: |
DEPLOYED=$(az containerapp show \
--name "$CONTAINER_APP_NAME" \
--resource-group "$RESOURCEGROUP" \
--query "properties.template.containers[0].image" -o tsv)
EXPECTED="${{ vars.PRODCONTAINER_REGISTRY }}/essentialcsharpweb@${{ steps.import.outputs.digest }}"
if [ "$DEPLOYED" != "$EXPECTED" ]; then
echo "::error::Image mismatch! Expected $EXPECTED but found $DEPLOYED"
exit 1
fi
echo "Deployed image verified: $DEPLOYED"
- name: Smoke test
env:
CONTAINER_APP_NAME: ${{ vars.CONTAINER_APP_NAME }}
RESOURCEGROUP: ${{ vars.RESOURCEGROUP }}
run: |
FQDN=$(az containerapp show \
--name "$CONTAINER_APP_NAME" \
--resource-group "$RESOURCEGROUP" \
--query "properties.configuration.ingress.fqdn" -o tsv)
# --retry-all-errors ensures HTTP 5xx (cold-start 503s) also trigger retries
curl --fail --retry 10 --retry-delay 15 --retry-all-errors --max-time 30 "https://$FQDN/health"
- name: Cancel pending deployment workflow runs
uses: actions/github-script@v9
with:
script: |
// Cancel pending workflow runs queued from earlier commits.
// Uses context.workflow instead of hardcoded filename for resilience.
let totalCancelledCount = 0;
let page = 1;
try {
while (true) {
const { data } = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: context.workflow,
status: 'queued',
per_page: 100,
page: page
});
const runs = data.workflow_runs;
if (runs.length === 0) {
break;
}
for (const run of runs) {
if (run.id === context.runId) {
core.debug(`Skipping current run #${run.id}`);
continue;
}
try {
core.info(`Cancelling queued run #${run.id} (commit: ${run.head_sha.substring(0, 7)}, branch: ${run.head_branch}, created: ${run.created_at})`);
await github.rest.actions.cancelWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: run.id
});
totalCancelledCount++;
} catch (error) {
core.warning(`Failed to cancel run #${run.id}: ${error.message}`);
}
}
// Last page has fewer results than requested (pagination)
if (runs.length < 100) {
break;
}
page++;
}
} catch (error) {
core.warning(`Failed to list workflow runs: ${error.message}`);
core.info('Continuing with deployment despite cancellation script error');
}
core.info(`Cancelled ${totalCancelledCount} pending workflow run(s)`);
- name: Tag commit as deployed
run: |
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
# -f allows re-tagging the same SHA on workflow re-runs
git tag -f "deployed/prod/${{ github.sha }}"
git push origin "deployed/prod/${{ github.sha }}" --force
- name: Logout of Azure CLI
if: always()
uses: azure/CLI@v3
with:
inlineScript: |
az logout
az cache purge
az account clear