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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ trigger:
- main
paths:
include:
- pipelines/gtc-agent-standalone-web-api-sql.yml
- .azuredevops/pipelines/gtc-agent-standalone-web-api-sql-aoai.yml
- src/**

pr:
Expand All @@ -14,7 +14,7 @@ pr:
- main
paths:
include:
- pipelines/gtc-agent-standalone-web-api-sql.yml
- .azuredevops/pipelines/gtc-agent-standalone-web-api-sql-aoai.yml
- src/**

pool:
Expand All @@ -37,6 +37,29 @@ parameters:
default: AZURE-DEVOPS-SERVICE-CONNECTION-NAME

variables:
# External values are expected from variable groups or pipeline variable overrides.
- name: apiClientId
value: ''
- name: webClientId
value: ''
- name: webClientSecret
value: ''
- name: entraExternalIdTenantId
value: ''
- name: entraExternalIdInstance
value: ''
- name: azureOpenAiKey
value: ''
- name: azureOpenAiEndpoint
value: ''
- name: sqlUser
value: ''
- name: sqlPassword
value: ''
- ${{ if eq(parameters.Environment, 'development') }}:
- group: gtc-agent-dev
- ${{ if eq(parameters.Environment, 'production') }}:
- group: gtc-agent-prod
- name: API_ARTIFACT_OUTPUT
value: publish_output
- name: WEB_ARTIFACT_OUTPUT
Expand All @@ -51,11 +74,11 @@ variables:
value: ef-migrations.sql

- name: scriptsPath
value: $(System.DefaultWorkingDirectory)/scripts/ci
value: $(System.DefaultWorkingDirectory)/.azuredevops/scripts/ci
- name: srcPath
value: $(System.DefaultWorkingDirectory)/src
- name: srcSolution
value: $(srcPath)/Goodtocode.AgentFramework.Web.slnx
value: $(System.DefaultWorkingDirectory)/Goodtocode.AgentFramework.Web.slnx
- name: apiPath
value: Presentation.Api
- name: apiProject
Expand All @@ -78,6 +101,25 @@ variables:
value: Development
- name: buildConfiguration
value: Release
- name: agentProviderKind
value: AzureOpenAI
- name: azureOpenAiChatCompletionModelId
value: openai-fast
- name: entraExternalIdValidateAuthority
value: true
# Manual environment resource mapping for CD (update if IaC names change)
- name: appiName
value: agent-dev-wus2-001-appi
- name: azureApiAppName
value: agent-dev-wus2-001-api
- name: azureWebAppName
value: agent-dev-wus2-001-web
- name: azureRgName
value: gtc-agent-dev-wus2-001-rg
- name: sqlName
value: agent-dev-wus2-001-sql
- name: sqlDbName
value: agent-dev-wus2-001-sqldb
- name: versionMajor
value: '2'
- name: versionMinor
Expand Down Expand Up @@ -134,33 +176,6 @@ stages:
script: |
Write-Host "##vso[task.setvariable variable=ASPNETCORE_ENVIRONMENT]$(runtimeEnv)"

- task: PowerShell@2
displayName: App Settings Variable Substitution
inputs:
targetType: inline
pwsh: true
script: |
function Set-OpenAiApiKey([string]$filePath, [string]$apiKey) {
if (-not (Test-Path $filePath)) {
Write-Host "Skipping missing file: $filePath"
return
}

$obj = Get-Content -Raw $filePath | ConvertFrom-Json
if (-not $obj.OpenAI) {
$obj | Add-Member -MemberType NoteProperty -Name OpenAI -Value ([pscustomobject]@{})
}

$obj.OpenAI.ApiKey = $apiKey
$obj | ConvertTo-Json -Depth 50 | Set-Content -Path $filePath
Write-Host "Updated OpenAI.ApiKey in: $filePath"
}

$apiKey = "$(OPENAI_APIKEY)"
Set-OpenAiApiKey -filePath "$(srcPath)/$(apiPath)/appsettings.json" -apiKey $apiKey
Set-OpenAiApiKey -filePath "$(srcPath)/$(apiPath)/appsettings.$(runtimeEnv).json" -apiKey $apiKey
Set-OpenAiApiKey -filePath "$(srcPath)/$(testPath)/appsettings.test.json" -apiKey $apiKey

- task: DotNetCoreCLI@2
displayName: dotnet restore
inputs:
Expand Down Expand Up @@ -191,6 +206,15 @@ stages:
script: |
Write-Host "Working Directory: $(Get-Location)"
$tempDir = "$(srcPath)/$(infraPath)/.temp-migrations"
$migrationsDir = "$(srcPath)/$(infraPath)/Migrations"

Write-Host "Expected committed migrations directory: $migrationsDir"
if (Test-Path $migrationsDir) {
Write-Host "Committed migration files currently in repo:"
Get-ChildItem -Path $migrationsDir -File | Sort-Object Name | ForEach-Object {
Write-Host " - $($_.Name) [$([math]::Round($_.Length / 1KB, 2)) KB]"
}
}

if (Test-Path $tempDir) {
Remove-Item -Recurse -Force $tempDir
Expand All @@ -204,12 +228,68 @@ stages:
}

if (Test-Path $tempDir) {
$files = Get-ChildItem -Path $tempDir
$files | ForEach-Object { Write-Host $_.FullName }
$files = Get-ChildItem -Path $tempDir -File | Sort-Object Name
Write-Host "Temp migration output directory: $tempDir"
Write-Host "Temp migration file count: $($files.Count)"
$files | ForEach-Object {
Write-Host "Generated file: $($_.FullName) [$([math]::Round($_.Length / 1KB, 2)) KB]"
}

if ($files.Count -gt 0) {
Write-Error "Uncommitted model changes detected. Run dotnet ef migrations add locally and commit migration files."
Remove-Item -Recurse -Force $tempDir
exit 1
Write-Host ""
Write-Host "===== BEGIN TEMP MIGRATION FILE PREVIEW ====="
foreach ($file in $files) {
Write-Host "----- FILE: $($file.Name) -----"
Get-Content -Path $file.FullName -TotalCount 250 | ForEach-Object { Write-Host $_ }
Write-Host "----- END FILE: $($file.Name) -----"
}
Write-Host "===== END TEMP MIGRATION FILE PREVIEW ====="
Write-Host ""

Write-Host "Git status for migration-related files:"
git status --short "$(srcPath)/$(infraPath)/Migrations" "$tempDir"

Write-Host ""
Write-Host "Git diff summary for migration-related files:"
git --no-pager diff --stat -- "$(srcPath)/$(infraPath)/Migrations" "$tempDir"

$snapshotFile = "$(srcPath)/$(infraPath)/Migrations/$(infraDbContext)ModelSnapshot.cs"
if (Test-Path $snapshotFile) {
Write-Host ""
Write-Host "===== BEGIN SNAPSHOT DIFF: $snapshotFile ====="
git --no-pager diff -- $snapshotFile
Write-Host "===== END SNAPSHOT DIFF ====="

Write-Host ""
Write-Host "===== SNAPSHOT DIFF (IGNORE EOL/WHITESPACE) ====="
git --no-pager diff --ignore-cr-at-eol --ignore-space-at-eol -- $snapshotFile
Write-Host "===== END SNAPSHOT DIFF (IGNORE EOL/WHITESPACE) ====="
}

Write-Host ""
Write-Host "Git line-ending settings (for diagnosing LF/CRLF churn):"
git config --get core.autocrlf
git config --get core.eol

Write-Host ""
Write-Host "EF pending-model-changes command result (diagnostic only):"
dotnet ef migrations has-pending-model-changes --project "$(srcPath)/$(infraPath)/$(infraProject)" --startup-project "$(srcPath)/$(apiPath)/$(apiProject)" --context "$(infraDbContext)" --configuration "$(buildConfiguration)" --no-build
$hasPendingModelChangesExitCode = $LASTEXITCODE
Write-Host "EF has-pending-model-changes exit code: $hasPendingModelChangesExitCode"

if (Test-Path $snapshotFile) {
Write-Host ""
Write-Host "Snapshot file SHA256:"
Get-FileHash -Path $snapshotFile -Algorithm SHA256 | Format-List
}

if ($hasPendingModelChangesExitCode -ne 0) {
Write-Error "Uncommitted model changes detected. Run dotnet ef migrations add locally and commit migration files."
Remove-Item -Recurse -Force $tempDir
exit 1
}

Write-Warning "Temp migration files were generated, but EF reports no pending model changes. Continuing build (likely line-ending/tooling churn)."
}
Remove-Item -Recurse -Force $tempDir
}
Expand All @@ -229,6 +309,12 @@ stages:
projects: $(srcSolution)
arguments: --configuration $(buildConfiguration) --no-build
publishTestResults: true
env:
ASPNETCORE_ENVIRONMENT: $(runtimeEnv)
AgentProvider__Kind: $(agentProviderKind)
AzureOpenAI__ApiKey: $(azureOpenAiKey)
AzureOpenAI__Endpoint: $(azureOpenAiEndpoint)
AzureOpenAI__ChatCompletionModelId: $(azureOpenAiChatCompletionModelId)

- task: DotNetCoreCLI@2
displayName: Pack Web API artifact
Expand Down Expand Up @@ -276,21 +362,8 @@ stages:
displayName: Web, API and SQL CD
dependsOn: ci
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), or(and(or(eq(variables['Build.Reason'], 'IndividualCI'), eq(variables['Build.Reason'], 'BatchedCI')), eq(variables['Build.SourceBranch'], 'refs/heads/main')), and(eq(variables['Build.Reason'], 'Manual'), eq(lower('${{ parameters.RunCd }}'), 'true'), eq(lower('${{ parameters.Environment }}'), 'development'))))
variables:
- name: appiName
value: agent-dev-wus2-001-appi
- name: azureApiAppName
value: agent-dev-wus2-001-api
- name: azureWebAppName
value: agent-dev-wus2-001-web
- name: azureRgName
value: gtc-agent-dev-wus2-001-rg
- name: sqlName
value: agent-dev-wus2-001-sql
- name: sqlDbName
value: agent-dev-wus2-001-sqldb
- name: runtimeEnv
value: Development
pool:
vmImage: windows-latest
jobs:
- job: deploy_web_api_sql
displayName: Web, API and SQL CD
Expand Down Expand Up @@ -326,8 +399,8 @@ stages:
AuthenticationType: server
ServerName: $(sqlName).database.windows.net
DatabaseName: $(sqlDbName)
SqlUsername: $(SQL_ADMIN_USER)
SqlPassword: $(SQL_ADMIN_PASSWORD)
SqlUsername: $(sqlUser)
SqlPassword: $(sqlPassword)
deployType: SqlTask
SqlFile: $(Pipeline.Workspace)/$(MIGRATION_ARTIFACT_NAME)/$(MIGRATION_ARTIFACT_FILE)
IpDetectionMethod: AutoDetect
Expand Down Expand Up @@ -356,9 +429,14 @@ stages:
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings APPINSIGHTS_INSTRUMENTATIONKEY=$INSTR_KEY
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings APPLICATIONINSIGHTS_CONNECTION_STRING=$CONN_STR
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings ASPNETCORE_ENVIRONMENT=$(runtimeEnv)
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings EntraExternalId:TenantId=$(EEID_TENANT_ID)
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings EntraExternalId:ClientId=$(API_CLIENT_ID)
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings OpenAI:ApiKey=$(OPENAI_APIKEY)
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings EntraExternalId:ValidateAuthority=$(entraExternalIdValidateAuthority)
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings EntraExternalId:TenantId=$(entraExternalIdTenantId)
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings EntraExternalId:Instance=$(entraExternalIdInstance)
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings EntraExternalId:ClientId=$(apiClientId)
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings AgentProvider:Kind=$(agentProviderKind)
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings AzureOpenAI:ApiKey=$(azureOpenAiKey)
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings AzureOpenAI:Endpoint=$(azureOpenAiEndpoint)
az webapp config appsettings set -g $(azureRgName) -n $(azureApiAppName) --settings AzureOpenAI:ChatCompletionModelId=$(azureOpenAiChatCompletionModelId)

- task: AzureCLI@2
displayName: $(azureApiAppName) connection strings
Expand All @@ -368,8 +446,8 @@ stages:
scriptLocation: inlineScript
inlineScript: |
$TEMP_STR = az sql db show-connection-string --client ado.net --server $(sqlName) --name $(sqlDbName) -o tsv
$TEMP_STR = $TEMP_STR.Replace("<username>", "$(SQL_ADMIN_USER)")
$TEMP_STR = $TEMP_STR.Replace("<password>", "$(SQL_ADMIN_PASSWORD)")
$TEMP_STR = $TEMP_STR.Replace("<username>", "$(sqlUser)")
$TEMP_STR = $TEMP_STR.Replace("<password>", "$(sqlPassword)")
az webapp config connection-string set -g $(azureRgName) -n $(azureApiAppName) -t SQLServer --settings DefaultConnection="$TEMP_STR"

- task: AzureWebApp@1
Expand All @@ -396,11 +474,13 @@ stages:
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings APPINSIGHTS_INSTRUMENTATIONKEY=$INSTR_KEY
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings APPLICATIONINSIGHTS_CONNECTION_STRING=$CONN_STR
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings ASPNETCORE_ENVIRONMENT=$(runtimeEnv)
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings EntraExternalId:TenantId=$(EEID_TENANT_ID)
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings EntraExternalId:ClientId=$(WEB_CLIENT_ID)
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings EntraExternalId:ClientSecret=$(WEB_CLIENT_SECRET)
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings EntraExternalId:ValidateAuthority=$(entraExternalIdValidateAuthority)
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings EntraExternalId:TenantId=$(entraExternalIdTenantId)
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings EntraExternalId:Instance=$(entraExternalIdInstance)
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings EntraExternalId:ClientId=$(webClientId)
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings EntraExternalId:ClientSecret=$(webClientSecret)
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings BackendApi:BaseUrl="https://$(azureApiAppName).azurewebsites.net"
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings BackendApi:ClientId=$(API_CLIENT_ID)
az webapp config appsettings set -g $(azureRgName) -n $(azureWebAppName) --settings BackendApi:ClientId=$(apiClientId)

- task: AzureCLI@2
displayName: Swap to production slot
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ body:
label: Impact
description: User/business impact and severity
validations:
required: true
required: false

- type: textarea
id: evidence
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/gtc-agent-standalone-web-api-sql-aoai.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ env:
AZURE_RG_NAME: 'gtc-agent-dev-wus2-001-rg'
SQL_NAME: 'agent-dev-wus2-001-sql'
SQLDB_NAME: 'agent-dev-wus2-001-sqldb'
EEID_VALIDATE_AUTHORITY: 'true'

API_ARTIFACT_OUTPUT: 'publish_output'
WEB_ARTIFACT_OUTPUT: 'publish_web_output'
Expand Down Expand Up @@ -270,8 +271,10 @@ jobs:
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings APPINSIGHTS_INSTRUMENTATIONKEY=$INSTR_KEY
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings APPLICATIONINSIGHTS_CONNECTION_STRING=$CONN_STR
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings ASPNETCORE_ENVIRONMENT=${{ env.RUNTIME_ENV }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings EntraExternalId:Instance=${{ secrets.EEID_INSTANCE }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings EntraExternalId:TenantId=${{ secrets.EEID_TENANT_ID }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings EntraExternalId:ClientId=${{ secrets.API_CLIENT_ID }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings EntraExternalId:ValidateAuthority=${{ env.EEID_VALIDATE_AUTHORITY }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings AzureOpenAI:Endpoint=${{ secrets.AZURE_OPENAI_ENDPOINT }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings AzureOpenAI:ApiKey=${{ secrets.AZURE_OPENAI_APIKEY }}
shell: pwsh
Expand All @@ -296,9 +299,11 @@ jobs:
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_WEBAPP_NAME }} --settings APPINSIGHTS_INSTRUMENTATIONKEY=$INSTR_KEY
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_WEBAPP_NAME }} --settings APPLICATIONINSIGHTS_CONNECTION_STRING=$CONN_STR
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_WEBAPP_NAME }} --settings ASPNETCORE_ENVIRONMENT=${{ env.RUNTIME_ENV }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_WEBAPP_NAME }} --settings EntraExternalId:Instance=${{ secrets.EEID_INSTANCE }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_WEBAPP_NAME }} --settings EntraExternalId:TenantId=${{ secrets.EEID_TENANT_ID }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_WEBAPP_NAME }} --settings EntraExternalId:ClientId=${{ secrets.WEB_CLIENT_ID }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_WEBAPP_NAME }} --settings EntraExternalId:ClientSecret=${{ secrets.WEB_CLIENT_SECRET }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_WEBAPP_NAME }} --settings EntraExternalId:ValidateAuthority=${{ env.EEID_VALIDATE_AUTHORITY }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_WEBAPP_NAME }} --settings BackendApi:BaseUrl="https://${{ env.AZURE_APIAPP_NAME }}.azurewebsites.net"
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_WEBAPP_NAME }} --settings BackendApi:ClientId=${{ secrets.API_CLIENT_ID }}
shell: pwsh
Expand Down
Loading
Loading