diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index 528d5ad07..8d7044bcb 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -1,15 +1,6 @@ name: Test Automation KMGeneric on: - push: - branches: - - main - - dev - paths: - - 'tests/e2e-test/**' - - workflow_dispatch: - # NEW: Add workflow_call to make it reusable workflow_call: inputs: KMGENERIC_URL: diff --git a/documents/CustomizingAzdParameters.md b/documents/CustomizingAzdParameters.md index 59bf9dc5f..b1708e42e 100644 --- a/documents/CustomizingAzdParameters.md +++ b/documents/CustomizingAzdParameters.md @@ -22,7 +22,6 @@ By default this template will use the environment name as the prefix to prevent | `AZURE_ENV_IMAGETAG` | string | `latest` | Sets the image tag (`latest`, `dev`, `hotfix`, etc.). | | `AZURE_OPENAI_EMBEDDING_MODEL_CAPACITY` | integer | `80` | Sets the capacity for the embedding model deployment. | | `AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID` | string | Guide to get your [Existing Workspace ID](/documents/re-use-log-analytics.md) | Reuses an existing Log Analytics Workspace instead of creating a new one. | -| `USE_LOCAL_BUILD` | string | `false` | Indicates whether to use a local container build for deployment. | | `AZURE_EXISTING_AI_PROJECT_RESOURCE_ID` | string | `` | Reuses an existing AIFoundry and AIFoundryProject instead of creating a new one. | diff --git a/documents/DeploymentGuide.md b/documents/DeploymentGuide.md index 90a491b73..aac285948 100644 --- a/documents/DeploymentGuide.md +++ b/documents/DeploymentGuide.md @@ -195,6 +195,24 @@ Once you've opened the project in [Codespaces](#github-codespaces), [Dev Contain - Follow steps in [Delete Resource Group](./DeleteResourceGroup.md) if your deployment fails and/or you need to clean up the resources. +3. **Optional: Publishing Local Build Container to Azure Container Registry** + + If you need to rebuild the source code and push the updated container to the deployed Azure Container Registry, follow these steps: + + - **Linux/macOS**: + ```bash + cd ./infra/scripts/ + ./docker-build.sh + ``` + + - **Windows (PowerShell)**: + ```powershell + cd .\infra\scripts\ + .\docker-build.ps1 + ``` + + This will create a new Azure Container Registry, rebuild the source code, package it into a container, and push it to the Container Registry created. + ## For Local Debugging Follow steps in [Local Debugging Setup](./LocalDebuggingSetup.md) to configure your local development environment for debugging the solution. diff --git a/infra/deploy_app_service.bicep b/infra/deploy_app_service.bicep index d1857fe72..1b9eae21b 100644 --- a/infra/deploy_app_service.bicep +++ b/infra/deploy_app_service.bicep @@ -12,7 +12,6 @@ param appSettings object = {} param appServicePlanId string param appImageName string param userassignedIdentityId string = '' -param useLocalBuild string resource appService 'Microsoft.Web/sites@2020-06-01' = { name: solutionName @@ -28,7 +27,6 @@ resource appService 'Microsoft.Web/sites@2020-06-01' = { properties: { serverFarmId: appServicePlanId siteConfig: { - acrUseManagedIdentityCreds: useLocalBuild == 'true' alwaysOn: true ftpsState: 'Disabled' linuxFxVersion: appImageName diff --git a/infra/deploy_backend_docker.bicep b/infra/deploy_backend_docker.bicep index d6a3bc24f..3af72c357 100644 --- a/infra/deploy_backend_docker.bicep +++ b/infra/deploy_backend_docker.bicep @@ -11,7 +11,6 @@ param appServicePlanId string param userassignedIdentityId string param keyVaultName string param aiServicesName string -param useLocalBuild string param azureExistingAIProjectResourceId string = '' param aiSearchName string param aideploymentsLocation string @@ -94,7 +93,6 @@ module appService 'deploy_app_service.bicep' = { appServicePlanId: appServicePlanId appImageName: imageName userassignedIdentityId:userassignedIdentityId - useLocalBuild: useLocalBuild appSettings: union( appSettings, { @@ -191,24 +189,9 @@ module assignAiUserRoleToAiProject 'deploy_foundry_role_assignment.bicep' = { } } -resource containerRegistry 'Microsoft.ContainerRegistry/registries@2021-09-01' existing = if (useLocalBuild == 'true') { - name: acrName -} - -resource AcrPull 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = if (useLocalBuild == 'true') { - name: '7f951dda-4ed3-4680-a7ca-43fe172d538d' -} - -resource acrPullRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (useLocalBuild == 'true') { - name: guid(appService.name, AcrPull.id) - scope: containerRegistry - properties: { - roleDefinitionId: AcrPull.id - principalId: appService.outputs.identityPrincipalId - principalType: 'ServicePrincipal' - } -} output appUrl string = appService.outputs.appUrl output reactAppLayoutConfig string = reactAppLayoutConfig output appInsightInstrumentationKey string = reference(applicationInsightsId, '2015-05-01').InstrumentationKey +output backendManagedIdentityPrincipalId string = appService.outputs.identityPrincipalId +output backendAppName string = name diff --git a/infra/deploy_container_registry.bicep b/infra/deploy_container_registry.bicep index dfd0addb7..85c2e2b34 100644 --- a/infra/deploy_container_registry.bicep +++ b/infra/deploy_container_registry.bicep @@ -1,19 +1,23 @@ targetScope = 'resourceGroup' -param environmentName string +param solutionName string param solutionLocation string = resourceGroup().location -var uniqueId = toLower(uniqueString(subscription().id, environmentName, solutionLocation)) -var solutionName = 'km${padLeft(take(uniqueId, 12), 12, '0')}' var abbrs = loadJsonContent('./abbreviations.json') var containerRegistryName = '${abbrs.containers.containerRegistry}${solutionName}' var containerRegistryNameCleaned = replace(containerRegistryName, '-', '') + +@description('List of Principal Ids to which ACR pull role assignment is required') +param acrPullPrincipalIds array = [] + +@description('Provide a tier of your Azure Container Registry.') +param acrSku string = 'Premium' resource containerRegistry 'Microsoft.ContainerRegistry/registries@2021-09-01' = { name: containerRegistryName location: solutionLocation sku: { - name: 'Premium' + name: acrSku } properties: { dataEndpointEnabled: false @@ -38,6 +42,19 @@ resource containerRegistry 'Microsoft.ContainerRegistry/registries@2021-09-01' = zoneRedundancy: 'Disabled' } } + +// Add Role assignments for required principal id's +resource acrPullRoleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for principalId in acrPullPrincipalIds: { + name: guid(principalId, 'acrpull') + scope: containerRegistry + properties: { + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '7f951dda-4ed3-4680-a7ca-43fe172d538d' + ) + principalId: principalId + } +}] output createdAcrName string = containerRegistryNameCleaned output createdAcrId string = containerRegistry.id diff --git a/infra/deploy_frontend_docker.bicep b/infra/deploy_frontend_docker.bicep index ded006695..5502f40e3 100644 --- a/infra/deploy_frontend_docker.bicep +++ b/infra/deploy_frontend_docker.bicep @@ -8,7 +8,6 @@ param solutionLocation string @secure() param appSettings object = {} param appServicePlanId string -param useLocalBuild string var imageName = 'DOCKER|${acrName}.azurecr.io/km-app:${imageTag}' //var name = '${solutionName}-app' @@ -20,7 +19,6 @@ module appService 'deploy_app_service.bicep' = { solutionName: name appServicePlanId: appServicePlanId appImageName: imageName - useLocalBuild: useLocalBuild appSettings: union( appSettings, { @@ -30,22 +28,7 @@ module appService 'deploy_app_service.bicep' = { } } -resource containerRegistry 'Microsoft.ContainerRegistry/registries@2021-09-01' existing = if (useLocalBuild == 'true') { - name: acrName -} - -resource AcrPull 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = if (useLocalBuild == 'true') { - name: '7f951dda-4ed3-4680-a7ca-43fe172d538d' -} - -resource acrPullRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (useLocalBuild == 'true') { - name: guid(appService.name, AcrPull.id) - scope: containerRegistry - properties: { - roleDefinitionId: AcrPull.id - principalId: appService.outputs.identityPrincipalId - principalType: 'ServicePrincipal' - } -} output appUrl string = appService.outputs.appUrl +output frontendManagedIdentityPrincipalId string = appService.outputs.identityPrincipalId +output frontendAppName string = name diff --git a/infra/main.bicep b/infra/main.bicep index 3f960cbb8..4d7282a7c 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -66,12 +66,6 @@ param imageTag string = 'latest_fdp' param AZURE_LOCATION string='' var solutionLocation = empty(AZURE_LOCATION) ? resourceGroup().location : AZURE_LOCATION -@description('Set this flag to true only if you are deploying from Local') -param useLocalBuild string = 'false' - -// Convert input to lowercase -var useLocalBuildLower = toLower(useLocalBuild) - var uniqueId = toLower(uniqueString(subscription().id, environmentName, solutionLocation, resourceGroup().name)) @@ -89,9 +83,7 @@ param aiDeploymentsLocation string var solutionPrefix = 'km${padLeft(take(uniqueId, 12), 12, '0')}' -var containerRegistryName = '${abbrs.containers.containerRegistry}${solutionPrefix}' -var containerRegistryNameCleaned = replace(containerRegistryName, '-', '') -var acrName = useLocalBuildLower == 'true' ? containerRegistryNameCleaned : 'kmcontainerreg' +var acrName = 'kmcontainerreg' var baseUrl = 'https://raw.githubusercontent.com/microsoft/Conversation-Knowledge-Mining-Solution-Accelerator/main/' @@ -241,7 +233,6 @@ module backend_docker 'deploy_backend_docker.bicep' = { userassignedIdentityId: managedIdentityModule.outputs.managedIdentityBackendAppOutput.id keyVaultName: kvault.outputs.keyvaultName aiServicesName: aifoundry.outputs.aiServicesName - useLocalBuild: useLocalBuildLower azureExistingAIProjectResourceId: azureExistingAIProjectResourceId aiSearchName: aifoundry.outputs.aiSearchName appSettings: { @@ -284,7 +275,6 @@ module frontend_docker 'deploy_frontend_docker.bicep' = { acrName: acrName appServicePlanId: hostingplan.outputs.name applicationInsightsId: aifoundry.outputs.applicationInsightsId - useLocalBuild: useLocalBuildLower appSettings:{ APP_API_BASE_URL:backend_docker.outputs.appUrl } @@ -335,3 +325,8 @@ output APPLICATIONINSIGHTS_CONNECTION_STRING string = aifoundry.outputs.applicat output API_APP_URL string = backend_docker.outputs.appUrl output WEB_APP_URL string = frontend_docker.outputs.appUrl + +output BACKEND_APP_NAME string = backend_docker.outputs.backendAppName +output FRONTEND_APP_NAME string = frontend_docker.outputs.frontendAppName +output BACKEND_MANAGED_IDENTITY_PRINCIPAL_ID string = backend_docker.outputs.backendManagedIdentityPrincipalId +output FRONTEND_MANAGED_IDENTITY_PRINCIPAL_ID string = frontend_docker.outputs.frontendManagedIdentityPrincipalId diff --git a/infra/main.json b/infra/main.json index b51110981..95f6ee78d 100644 --- a/infra/main.json +++ b/infra/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "1973444615729178681" + "templateHash": "9549104404963781192" } }, "parameters": { @@ -123,13 +123,6 @@ "type": "string", "defaultValue": "" }, - "useLocalBuild": { - "type": "string", - "defaultValue": "false", - "metadata": { - "description": "Set this flag to true only if you are deploying from Local" - } - }, "aiDeploymentsLocation": { "type": "string", "metadata": { @@ -376,12 +369,9 @@ }, "abbrs": "[variables('$fxv#0')]", "solutionLocation": "[if(empty(parameters('AZURE_LOCATION')), resourceGroup().location, parameters('AZURE_LOCATION'))]", - "useLocalBuildLower": "[toLower(parameters('useLocalBuild'))]", "uniqueId": "[toLower(uniqueString(subscription().id, parameters('environmentName'), variables('solutionLocation'), resourceGroup().name))]", "solutionPrefix": "[format('km{0}', padLeft(take(variables('uniqueId'), 12), 12, '0'))]", - "containerRegistryName": "[format('{0}{1}', variables('abbrs').containers.containerRegistry, variables('solutionPrefix'))]", - "containerRegistryNameCleaned": "[replace(variables('containerRegistryName'), '-', '')]", - "acrName": "[if(equals(variables('useLocalBuildLower'), 'true'), variables('containerRegistryNameCleaned'), 'kmcontainerreg')]", + "acrName": "kmcontainerreg", "baseUrl": "https://raw.githubusercontent.com/microsoft/Conversation-Knowledge-Mining-Solution-Accelerator/main/" }, "resources": [ @@ -3172,9 +3162,6 @@ "aiServicesName": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiServicesName.value]" }, - "useLocalBuild": { - "value": "[variables('useLocalBuildLower')]" - }, "azureExistingAIProjectResourceId": { "value": "[parameters('azureExistingAIProjectResourceId')]" }, @@ -3217,7 +3204,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "3928259108769110466" + "templateHash": "14604331142807242662" } }, "parameters": { @@ -3252,9 +3239,6 @@ "aiServicesName": { "type": "string" }, - "useLocalBuild": { - "type": "string" - }, "azureExistingAIProjectResourceId": { "type": "string", "defaultValue": "" @@ -3319,21 +3303,6 @@ "[resourceId('Microsoft.Resources/deployments', format('{0}-app-module', parameters('name')))]" ] }, - { - "condition": "[equals(parameters('useLocalBuild'), 'true')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('acrName'))]", - "name": "[guid(format('{0}-app-module', parameters('name')), resourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d'))]", - "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]", - "principalId": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-app-module', parameters('name'))), '2022-09-01').outputs.identityPrincipalId.value]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('{0}-app-module', parameters('name')))]" - ] - }, { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -3359,9 +3328,6 @@ "userassignedIdentityId": { "value": "[parameters('userassignedIdentityId')]" }, - "useLocalBuild": { - "value": "[parameters('useLocalBuild')]" - }, "appSettings": { "value": "[union(parameters('appSettings'), createObject('APPINSIGHTS_INSTRUMENTATIONKEY', reference(parameters('applicationInsightsId'), '2015-05-01').InstrumentationKey, 'REACT_APP_LAYOUT_CONFIG', variables('reactAppLayoutConfig')))]" } @@ -3373,7 +3339,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "3987406605431680589" + "templateHash": "3401191210177836889" } }, "parameters": { @@ -3402,9 +3368,6 @@ "userassignedIdentityId": { "type": "string", "defaultValue": "" - }, - "useLocalBuild": { - "type": "string" } }, "resources": [ @@ -3439,7 +3402,6 @@ "properties": { "serverFarmId": "[parameters('appServicePlanId')]", "siteConfig": { - "acrUseManagedIdentityCreds": "[equals(parameters('useLocalBuild'), 'true')]", "alwaysOn": true, "ftpsState": "Disabled", "linuxFxVersion": "[parameters('appImageName')]" @@ -3862,6 +3824,14 @@ "appInsightInstrumentationKey": { "type": "string", "value": "[reference(parameters('applicationInsightsId'), '2015-05-01').InstrumentationKey]" + }, + "backendManagedIdentityPrincipalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-app-module', parameters('name'))), '2022-09-01').outputs.identityPrincipalId.value]" + }, + "backendAppName": { + "type": "string", + "value": "[parameters('name')]" } } } @@ -3904,9 +3874,6 @@ "applicationInsightsId": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.applicationInsightsId.value]" }, - "useLocalBuild": { - "value": "[variables('useLocalBuildLower')]" - }, "appSettings": { "value": { "APP_API_BASE_URL": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_backend_docker'), '2022-09-01').outputs.appUrl.value]" @@ -3920,7 +3887,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "9861456937324375977" + "templateHash": "15061720303035189936" } }, "parameters": { @@ -3946,9 +3913,6 @@ "appServicePlanId": { "type": "string" }, - "useLocalBuild": { - "type": "string" - }, "name": { "type": "string" } @@ -3957,21 +3921,6 @@ "imageName": "[format('DOCKER|{0}.azurecr.io/km-app:{1}', parameters('acrName'), parameters('imageTag'))]" }, "resources": [ - { - "condition": "[equals(parameters('useLocalBuild'), 'true')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('acrName'))]", - "name": "[guid(format('{0}-app-module', parameters('name')), resourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d'))]", - "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]", - "principalId": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-app-module', parameters('name'))), '2022-09-01').outputs.identityPrincipalId.value]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('{0}-app-module', parameters('name')))]" - ] - }, { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -3994,9 +3943,6 @@ "appImageName": { "value": "[variables('imageName')]" }, - "useLocalBuild": { - "value": "[parameters('useLocalBuild')]" - }, "appSettings": { "value": "[union(parameters('appSettings'), createObject('APPINSIGHTS_INSTRUMENTATIONKEY', reference(parameters('applicationInsightsId'), '2015-05-01').InstrumentationKey))]" } @@ -4008,7 +3954,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "3987406605431680589" + "templateHash": "3401191210177836889" } }, "parameters": { @@ -4037,9 +3983,6 @@ "userassignedIdentityId": { "type": "string", "defaultValue": "" - }, - "useLocalBuild": { - "type": "string" } }, "resources": [ @@ -4074,7 +4017,6 @@ "properties": { "serverFarmId": "[parameters('appServicePlanId')]", "siteConfig": { - "acrUseManagedIdentityCreds": "[equals(parameters('useLocalBuild'), 'true')]", "alwaysOn": true, "ftpsState": "Disabled", "linuxFxVersion": "[parameters('appImageName')]" @@ -4185,6 +4127,14 @@ "appUrl": { "type": "string", "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-app-module', parameters('name'))), '2022-09-01').outputs.appUrl.value]" + }, + "frontendManagedIdentityPrincipalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-app-module', parameters('name'))), '2022-09-01').outputs.identityPrincipalId.value]" + }, + "frontendAppName": { + "type": "string", + "value": "[parameters('name')]" } } } @@ -4364,6 +4314,22 @@ "WEB_APP_URL": { "type": "string", "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_frontend_docker'), '2022-09-01').outputs.appUrl.value]" + }, + "BACKEND_APP_NAME": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_backend_docker'), '2022-09-01').outputs.backendAppName.value]" + }, + "FRONTEND_APP_NAME": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_frontend_docker'), '2022-09-01').outputs.frontendAppName.value]" + }, + "BACKEND_MANAGED_IDENTITY_PRINCIPAL_ID": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_backend_docker'), '2022-09-01').outputs.backendManagedIdentityPrincipalId.value]" + }, + "FRONTEND_MANAGED_IDENTITY_PRINCIPAL_ID": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_frontend_docker'), '2022-09-01').outputs.frontendManagedIdentityPrincipalId.value]" } } } \ No newline at end of file diff --git a/infra/scripts/docker-build.ps1 b/infra/scripts/docker-build.ps1 index 64510d6f0..35e0aaa62 100644 --- a/infra/scripts/docker-build.ps1 +++ b/infra/scripts/docker-build.ps1 @@ -1,36 +1,56 @@ -# Define script parameters -param ( - [string]$AZURE_SUBSCRIPTION_ID, - [string]$ENV_NAME, - [string]$AZURE_LOCATION, - [string]$AZURE_RESOURCE_GROUP, - [string]$USE_LOCAL_BUILD, - [string]$AZURE_ENV_IMAGETAG -) - -# Convert USE_LOCAL_BUILD to Boolean -$USE_LOCAL_BUILD = if ($USE_LOCAL_BUILD -match "^(?i:true)$") { $true } else { $false } - -if ([string]::IsNullOrEmpty($AZURE_ENV_IMAGETAG)) { - $AZURE_ENV_IMAGETAG = "latest_fdp" -} +# Get all environment values +$envValues = azd env get-values --output json | ConvertFrom-Json -# Validate required parameters -if (-not $AZURE_SUBSCRIPTION_ID -or -not $ENV_NAME -or -not $AZURE_LOCATION -or -not $AZURE_RESOURCE_GROUP) { - Write-Error "Missing required arguments. Usage: docker-build.ps1 " - exit 1 -} +# Validate and fetch required parameters from azd env if missing +function Get-AzdEnvValueOrDefault { + param ( + [Parameter(Mandatory = $true)] + [string]$KeyName, + + [Parameter(Mandatory = $false)] + [string]$DefaultValue = "", -# Exit early if local build is not requested -if ($USE_LOCAL_BUILD -eq $false) { - Write-Output "Local Build not enabled. Using prebuilt image." - exit 0 + [Parameter(Mandatory = $false)] + [bool]$Required = $false + ) + + # Check if key exists + if ($envValues.PSObject.Properties.Name -contains $KeyName) { + return $envValues.$KeyName + } + + # Key doesn't exist + if ($Required) { + Write-Error "Required environment key '$KeyName' not found in azd environment." + exit 1 + } else { + return $DefaultValue + } } -Write-Output "Local Build enabled. Starting build process." +# Read the required details from Bicep deployment output +$AZURE_SUBSCRIPTION_ID = Get-AzdEnvValueOrDefault -KeyName "AZURE_SUBSCRIPTION_ID" -Required $true +$SOLUTION_NAME = Get-AzdEnvValueOrDefault -KeyName "SOLUTION_NAME" -Required $true +$WEB_APP_IDENTITY_PRINCIPAL_ID = Get-AzdEnvValueOrDefault -KeyName "FRONTEND_MANAGED_IDENTITY_PRINCIPAL_ID" -Required $true +$API_APP_IDENTITY_PRINCIPAL_ID = Get-AzdEnvValueOrDefault -KeyName "BACKEND_MANAGED_IDENTITY_PRINCIPAL_ID" -Required $true +$AZURE_RESOURCE_GROUP = Get-AzdEnvValueOrDefault -KeyName "AZURE_RESOURCE_GROUP" -Required $true +$AZURE_ENV_IMAGETAG = Get-AzdEnvValueOrDefault -KeyName "AZURE_ENV_IMAGETAG" -DefaultValue "latest" +$WEB_APP_NAME=Get-AzdEnvValueOrDefault -KeyName "FRONTEND_APP_NAME" -Required $true +$API_APP_NAME=Get-AzdEnvValueOrDefault -KeyName "BACKEND_APP_NAME" -Required $true + +# Export the variables for later use +Write-Host "Using the following parameters:" +Write-Host "AZURE_SUBSCRIPTION_ID = $AZURE_SUBSCRIPTION_ID" +Write-Host "SOLUTION_NAME = $SOLUTION_NAME" +Write-Host "AZURE_RESOURCE_GROUP = $AZURE_RESOURCE_GROUP" +Write-Host "AZURE_ENV_IMAGETAG = $AZURE_ENV_IMAGETAG" +Write-Host "WEB_APP_NAME = $WEB_APP_NAME" +Write-Host "API_APP_NAME = $API_APP_NAME" + +Write-Output "`nStarting build process..." # STEP 1: Ensure user is logged into Azure -Write-Host "Checking Azure login status..." +Write-Host "`nChecking Azure login status..." $account = az account show 2>$null | ConvertFrom-Json if (-not $account) { @@ -51,16 +71,20 @@ if ($LASTEXITCODE -ne 0) { exit 1 } -# STEP 3: Deploy container registry -Write-Host "Deploying container registry in location: $AZURE_LOCATION" -$OUTPUTS = az deployment group create --resource-group $AZURE_RESOURCE_GROUP --template-file "./infra/deploy_container_registry.bicep" --parameters environmentName=$ENV_NAME --query "properties.outputs" --output json | ConvertFrom-Json +# STEP 3: Get current script directory +$ScriptDir = $PSScriptRoot + +# STEP 4: Deploy container registry +Write-Host "`nDeploying container registry" +$TemplateFile = Join-Path $ScriptDir "..\deploy_container_registry.bicep" | Resolve-Path +$OUTPUTS = az deployment group create --resource-group $AZURE_RESOURCE_GROUP --template-file $TemplateFile --parameters solutionName=$SOLUTION_NAME acrPullPrincipalIds="['$WEB_APP_IDENTITY_PRINCIPAL_ID', '$API_APP_IDENTITY_PRINCIPAL_ID']" --query "properties.outputs" --output json | ConvertFrom-Json # Extract ACR name and endpoint $ACR_NAME = $OUTPUTS.createdAcrName.value -Write-Host "Extracted ACR Name: $ACR_NAME" +Write-Host "ACR Name: $ACR_NAME" -# STEP 4: Login to Azure Container Registry +# STEP 5: Login to Azure Container Registry Write-Host "Logging into Azure Container Registry: $ACR_NAME" az acr login -n $ACR_NAME if ($LASTEXITCODE -ne 0) { @@ -68,9 +92,6 @@ if ($LASTEXITCODE -ne 0) { exit 1 } -# STEP 5: Get current script directory -$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path - # STEP 6: Resolve full paths to Dockerfiles and build contexts $WebAppDockerfilePath = Join-Path $ScriptDir "..\..\src\App\WebApp.Dockerfile" | Resolve-Path $WebAppContextPath = Join-Path $ScriptDir "..\..\src\App" | Resolve-Path @@ -110,4 +131,62 @@ function Build-And-Push-Image { Build-And-Push-Image "km-api" $ApiAppDockerfilePath $ApiAppContextPath $AZURE_ENV_IMAGETAG Build-And-Push-Image "km-app" $WebAppDockerfilePath $WebAppContextPath $AZURE_ENV_IMAGETAG -Write-Host "`nAll Docker images built and pushed successfully with tag: $AZURE_ENV_IMAGETAG" +Write-Host "`nAll Docker images built and pushed successfully with tag: $AZURE_ENV_IMAGETAG`n" + +# STEP 9: Define Function to Update Web App settings to use Managed Identity for ACR pull +function Update-WebApp-Settings { + param ( + [string]$WebAppName, + [string]$ResourceGroup + ) + + Write-Host "Updating Web App settings for $WebAppName" + $webAppConfig = az webapp config show --resource-group $ResourceGroup --name $WebAppName --query id --output tsv + if (-not $webAppConfig) { + Write-Error "Error: Web App configuration not found for $WebAppName" + exit 1 + } + az resource update --ids $webAppConfig --set properties.acrUseManagedIdentityCreds=True --output none --only-show-errors + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to update Web App settings for $WebAppName" + exit 1 + } + Write-Host "Web App settings updated successfully for $WebAppName" +} + +# STEP 10: Update Web App settings to use the new image +Update-WebApp-Settings -WebAppName $WEB_APP_NAME -ResourceGroup $AZURE_RESOURCE_GROUP +Update-WebApp-Settings -WebAppName $API_APP_NAME -ResourceGroup $AZURE_RESOURCE_GROUP + +# STEP 11: Define function to update Web App to use new image +function Update-WebApp-Image { + param ( + [string]$WebAppName, + [string]$ResourceGroup, + [string]$Image + ) + + Write-Host "`nUpdating Web App $WebAppName to use image: $Image" + az webapp config container set --name $WebAppName --resource-group $ResourceGroup --container-image-name $Image --only-show-errors + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to update Web App $WebAppName to use image: $Image" + exit 1 + } + Write-Host "Web App $WebAppName updated successfully to use image: $Image" + + Write-Host "`nRestarting Web App $WebAppName to apply changes" + az webapp restart --name $WebAppName --resource-group $ResourceGroup --output none --only-show-errors + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to restart Web App $WebAppName" + exit 1 + } + Write-Host "Web App $WebAppName restarted successfully" +} + +# STEP 12: Update Web Apps to use new images +Update-WebApp-Image -WebAppName $WEB_APP_NAME -ResourceGroup $AZURE_RESOURCE_GROUP -Image "$ACR_NAME.azurecr.io/km-app:$AZURE_ENV_IMAGETAG" +Update-WebApp-Image -WebAppName $API_APP_NAME -ResourceGroup $AZURE_RESOURCE_GROUP -Image "$ACR_NAME.azurecr.io/km-api:$AZURE_ENV_IMAGETAG" + +Write-Host "`nWeb Apps updated successfully to use new images" + +Write-Host "`nIt might take a few minutes for the changes to take effect.`n" \ No newline at end of file diff --git a/infra/scripts/docker-build.sh b/infra/scripts/docker-build.sh index 8fdac8088..83b2fb80d 100644 --- a/infra/scripts/docker-build.sh +++ b/infra/scripts/docker-build.sh @@ -2,31 +2,44 @@ set -e -AZURE_SUBSCRIPTION_ID="$1" -ENV_NAME="$2" -AZURE_LOCATION="$3" -AZURE_RESOURCE_GROUP="$4" -USE_LOCAL_BUILD="$5" -AZURE_ENV_IMAGETAG="$6" - -# Validate required parameters -if [[ -z "$AZURE_SUBSCRIPTION_ID" || -z "$ENV_NAME" || -z "$AZURE_LOCATION" || -z "$AZURE_RESOURCE_GROUP" ]]; then - echo "Missing required arguments. Usage: docker-build.sh " - exit 1 -fi - -# Ensure jq is installed -which jq || { echo "jq is not installed"; exit 1; } - -# Exit early if local build is not requested -if [[ "${USE_LOCAL_BUILD,,}" != "true" ]]; then - echo "Local Build not enabled. Using prebuilt image." - exit 0 -fi +get_azd_env_value_or_default() { + local key="$1" + local default="$2" + local required="${3:-false}" + + value=$(azd env get-value "$key" 2>/dev/null || echo "") + + if [ -z "$value" ]; then + if [ "$required" = true ]; then + echo "❌ Required environment key '$key' not found." >&2 + exit 1 + else + value="$default" + fi + fi -AZURE_ENV_IMAGETAG=${AZURE_ENV_IMAGETAG:-latest} + echo "$value" +} -echo "Local Build enabled. Starting build process." +# Required env variables +AZURE_SUBSCRIPTION_ID=$(get_azd_env_value_or_default "AZURE_SUBSCRIPTION_ID" "" true) +SOLUTION_NAME=$(get_azd_env_value_or_default "SOLUTION_NAME" "" true) +WEB_APP_IDENTITY_PRINCIPAL_ID=$(get_azd_env_value_or_default "FRONTEND_MANAGED_IDENTITY_PRINCIPAL_ID" "" true) +API_APP_IDENTITY_PRINCIPAL_ID=$(get_azd_env_value_or_default "BACKEND_MANAGED_IDENTITY_PRINCIPAL_ID" "" true) +AZURE_RESOURCE_GROUP=$(get_azd_env_value_or_default "AZURE_RESOURCE_GROUP" "" true) +AZURE_ENV_IMAGETAG=$(get_azd_env_value_or_default "AZURE_ENV_IMAGETAG" "latest" false) +WEB_APP_NAME=$(get_azd_env_value_or_default "FRONTEND_APP_NAME" "" true) +API_APP_NAME=$(get_azd_env_value_or_default "BACKEND_APP_NAME" "" true) + +echo "Using the following parameters:" +echo "AZURE_SUBSCRIPTION_ID = $AZURE_SUBSCRIPTION_ID" +echo "SOLUTION_NAME = $SOLUTION_NAME" +echo "AZURE_RESOURCE_GROUP = $AZURE_RESOURCE_GROUP" +echo "AZURE_ENV_IMAGETAG = $AZURE_ENV_IMAGETAG" +echo "WEB_APP_NAME = $WEB_APP_NAME" +echo "API_APP_NAME = $API_APP_NAME" + +echo -e "\nStarting build process..." # STEP 1: Ensure user is logged into Azure if ! az account show > /dev/null 2>&1; then @@ -47,20 +60,24 @@ if [[ $? -ne 0 ]]; then exit 1 fi -# STEP 3: Deploy container registry -echo "Deploying container registry in location: $AZURE_LOCATION" +# STEP 3: Get current script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# STEP 4: Deploy container registry +TEMPLATE_FILE="$SCRIPT_DIR/../deploy_container_registry.bicep" +echo -e "\nDeploying container registry" OUTPUTS=$(az deployment group create \ --resource-group "$AZURE_RESOURCE_GROUP" \ - --template-file "./infra/deploy_container_registry.bicep" \ - --parameters environmentName="$ENV_NAME" \ + --template-file "$TEMPLATE_FILE" \ + --parameters solutionName="$SOLUTION_NAME" acrPullPrincipalIds="['$WEB_APP_IDENTITY_PRINCIPAL_ID', '$API_APP_IDENTITY_PRINCIPAL_ID']" \ --query "properties.outputs" \ --output json) -ACR_NAME=$(echo "$OUTPUTS" | jq -r '.createdAcrName.value') +ACR_NAME=$(echo "$OUTPUTS" | grep -o '"createdAcrName"[^}]*"value"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"value"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') -echo "Extracted ACR Name: $ACR_NAME" +echo "ACR Name: $ACR_NAME" -# STEP 4: Login to Azure Container Registry +# STEP 5: Login to Azure Container Registry echo "Logging into Azure Container Registry: $ACR_NAME" az acr login -n "$ACR_NAME" if [[ $? -ne 0 ]]; then @@ -68,9 +85,6 @@ if [[ $? -ne 0 ]]; then exit 1 fi -# STEP 5: Get current script directory -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - # STEP 6: Resolve full paths to Dockerfiles and build contexts WEBAPP_DOCKERFILE_PATH="$SCRIPT_DIR/../../src/App/WebApp.Dockerfile" WEBAPP_CONTEXT_PATH="$SCRIPT_DIR/../../src/App" @@ -107,4 +121,58 @@ build_and_push_image() { build_and_push_image "km-api" "$APIAPP_DOCKERFILE_PATH" "$APIAPP_CONTEXT_PATH" "$AZURE_ENV_IMAGETAG" build_and_push_image "km-app" "$WEBAPP_DOCKERFILE_PATH" "$WEBAPP_CONTEXT_PATH" "$AZURE_ENV_IMAGETAG" -echo -e "\nAll Docker images built and pushed successfully with tag: $AZURE_ENV_IMAGETAG" +echo -e "\nAll Docker images built and pushed successfully with tag: $AZURE_ENV_IMAGETAG\n" + +# STEP 9: Function to Update Web App settings to use Managed Identity for ACR pull +update_web_app_settings() { + local webAppName="$1" + local resourceGroup="$2" + + echo "Updating Web App settings for $webAppName" + webAppConfig=$(az webapp config show --resource-group "$resourceGroup" --name "$webAppName" --query id --output tsv) + if [[ -z "$webAppConfig" ]]; then + echo "Error: Web App configuration not found for $webAppName" + exit 1 + fi + az resource update --ids "$webAppConfig" --set properties.acrUseManagedIdentityCreds=True --output none --only-show-errors + if [[ $? -ne 0 ]]; then + echo "Failed to update Web App settings for $webAppName" + exit 1 + fi + echo "Web App settings updated successfully for $webAppName" +} + +# STEP 10: Update Web App settings +update_web_app_settings "$WEB_APP_NAME" "$AZURE_RESOURCE_GROUP" +update_web_app_settings "$API_APP_NAME" "$AZURE_RESOURCE_GROUP" + +# STEP 11: Function to Update Web App to use new image +update_web_app_image() { + local webAppName="$1" + local resourceGroup="$2" + local image="$3" + + echo -e "\nUpdating Web App $webAppName to use new image tag: $image" + az webapp config container set --name "$webAppName" --resource-group "$resourceGroup" --container-image-name "$image" --only-show-errors + if [[ $? -ne 0 ]]; then + echo "Failed to update Web App $webAppName to use new image: $image" + exit 1 + fi + echo "Web App $webAppName updated successfully to use new image: $image" + + echo -e "\nRestarting Web App $webAppName to apply changes" + az webapp restart --name "$webAppName" --resource-group "$resourceGroup" --output none --only-show-errors + if [[ $? -ne 0 ]]; then + echo "Failed to restart Web App $webAppName" + exit 1 + fi + echo "Web App $webAppName restarted successfully" +} + +# STEP 12: Update Web Apps to use new images +update_web_app_image "$WEB_APP_NAME" "$AZURE_RESOURCE_GROUP" "$ACR_NAME.azurecr.io/km-app:$AZURE_ENV_IMAGETAG" +update_web_app_image "$API_APP_NAME" "$AZURE_RESOURCE_GROUP" "$ACR_NAME.azurecr.io/km-api:$AZURE_ENV_IMAGETAG" + +echo -e "\nWeb Apps updated successfully to use new images" + +echo -e "\nIt might take a few minutes for the changes to take effect.\n"