pipeline { agent { label 'appdev' } options { disableConcurrentBuilds() timestamps() } environment { PATH = "C:\\Program Files\\Microsoft SDKs\\Azure\\CLI2\\wbin;${env.PATH}" } stages { stage('Checkout') { steps { checkout scm } } stage('Restore & Build') { steps { bat 'dotnet restore PowderCoatingApp.sln' bat 'dotnet build PowderCoatingApp.sln -c Release --no-restore' } } stage('Test') { steps { bat 'dotnet test tests\\PowderCoating.UnitTests --no-build -c Release --logger "trx;LogFileName=results.trx" --results-directory TestResults' } post { always { junit testResults: 'TestResults/*.trx', allowEmptyResults: true } } } stage('Run Migrations') { steps { bat 'dotnet tool install --global dotnet-ef 2>nul || dotnet tool update --global dotnet-ef 2>nul' withCredentials([string(credentialsId: 'pcl-prod-sql', variable: 'SQL_CONN')]) { bat '"%USERPROFILE%\\.dotnet\\tools\\dotnet-ef.exe" database update --project src\\PowderCoating.Infrastructure --startup-project src\\PowderCoating.Web --configuration Release --no-build --context ApplicationDbContext --connection "%SQL_CONN%"' } } } stage('Publish') { steps { bat 'dotnet publish src\\PowderCoating.Web\\PowderCoating.Web.csproj -c Release -r linux-x64 --self-contained false -o publish' bat 'xcopy /E /Y /I src\\PowderCoating.Web\\wwwroot publish\\wwwroot\\' } } stage('Deploy to Azure') { steps { powershell ''' Add-Type -Assembly System.IO.Compression.FileSystem if (Test-Path deploy.zip) { Remove-Item deploy.zip } $publishDir = (Resolve-Path "publish").Path $zip = [System.IO.Compression.ZipFile]::Open("deploy.zip", "Create") Get-ChildItem -Path $publishDir -Recurse -File | ForEach-Object { $entryName = $_.FullName.Substring($publishDir.Length + 1).Replace("\\", "/") [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip, $_.FullName, $entryName, "Optimal") | Out-Null } $zip.Dispose() Write-Host "deploy.zip created with forward-slash entry paths" ''' withCredentials([azureServicePrincipal( credentialsId: 'azure-pcl', subscriptionIdVariable: 'AZ_SUB_ID', clientIdVariable: 'AZ_CLIENT_ID', clientSecretVariable: 'AZ_CLIENT_SECRET', tenantIdVariable: 'AZ_TENANT_ID' )]) { bat 'az login --service-principal -u "%AZ_CLIENT_ID%" -p "%AZ_CLIENT_SECRET%" --tenant "%AZ_TENANT_ID%" --output none' bat 'az account set --subscription "%AZ_SUB_ID%"' bat 'az webapp deploy --resource-group rg-powdercoatinglogix-prod --name linuxpcl --src-path deploy.zip --type zip --async true' bat 'az logout' } } } stage('Health Check') { steps { powershell ''' $url = "https://app.powdercoatinglogix.com/" $timeout = 180 $elapsed = 0 Write-Host "Polling $url for up to $timeout seconds..." do { Start-Sleep -Seconds 10 $elapsed += 10 try { $r = Invoke-WebRequest $url -UseBasicParsing -TimeoutSec 10 if ($r.StatusCode -lt 400) { Write-Host "App responded HTTP $($r.StatusCode) after ${elapsed}s" exit 0 } } catch { Write-Host "[${elapsed}s] Not yet responding: $_" } } while ($elapsed -lt $timeout) Write-Error "App did not come healthy within $timeout seconds" exit 1 ''' } } } post { success { echo "Production deployment #${BUILD_NUMBER} completed successfully." } failure { echo "Pipeline #${BUILD_NUMBER} FAILED — review the stage logs above." } always { cleanWs() } } }