diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3b60b86a..4da252a1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,20 +17,22 @@ "customizations": { "vscode": { "extensions": [ - "ms-azuretools.vscode-docker", + "GitHub.vscode-github-actions", + "esbenp.prettier-vscode", + "ipedrazas.kubernetes-snippets", + "ms-azuretools.azure-dev", + "ms-azuretools.vscode-azurecontainerapps", "ms-azuretools.vscode-azurefunctions", "ms-azuretools.vscode-azurestorage", - "ms-azuretools.vscode-azurecontainerapps", + "ms-azuretools.vscode-docker", + "ms-dotnettools.blazorwasm-companion", "ms-dotnettools.csdevkit", + "ms-dotnettools.csharp", "ms-dotnettools.vscode-dotnet-runtime", - "ms-dotnettools.blazorwasm-companion", - "ms-kubernetes-tools.vscode-aks-tools", "ms-kubernetes-tools.aks-devx-tools", + "ms-kubernetes-tools.vscode-aks-tools", "ms-kubernetes-tools.vscode-kubernetes-tools", - "ipedrazas.kubernetes-snippets", - "redhat.vscode-yaml", - "GitHub.vscode-github-actions", - "esbenp.prettier-vscode" + "redhat.vscode-yaml" ] } }, diff --git a/.vscode/extensions.json b/.vscode/extensions.json index b63007d0..7cf4c9e6 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,19 +1,20 @@ { - "recommendations": [ - "ms-azuretools.vscode-azurefunctions", - "ms-dotnettools.csharp", - "ms-azuretools.vscode-docker", - "ms-azuretools.vscode-azurestorage", - "ms-azuretools.vscode-azurecontainerapps", - "ms-dotnettools.csdevkit", - "ms-dotnettools.vscode-dotnet-runtime", - "ms-dotnettools.blazorwasm-companion", - "ms-kubernetes-tools.vscode-aks-tools", - "ms-kubernetes-tools.aks-devx-tools", - "ms-kubernetes-tools.vscode-kubernetes-tools", - "ipedrazas.kubernetes-snippets", - "redhat.vscode-yaml", - "GitHub.vscode-github-actions", - "esbenp.prettier-vscode" - ] - } \ No newline at end of file + "recommendations": [ + "GitHub.vscode-github-actions", + "esbenp.prettier-vscode", + "ipedrazas.kubernetes-snippets", + "ms-azuretools.azure-dev", + "ms-azuretools.vscode-azurecontainerapps", + "ms-azuretools.vscode-azurefunctions", + "ms-azuretools.vscode-azurestorage", + "ms-azuretools.vscode-docker", + "ms-dotnettools.blazorwasm-companion", + "ms-dotnettools.csdevkit", + "ms-dotnettools.csharp", + "ms-dotnettools.vscode-dotnet-runtime", + "ms-kubernetes-tools.aks-devx-tools", + "ms-kubernetes-tools.vscode-aks-tools", + "ms-kubernetes-tools.vscode-kubernetes-tools", + "redhat.vscode-yaml" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index b7d51530..4a006e59 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,51 +1,54 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Frontend: Blazor client", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}//app/backend/bin/Debug/net8.0/ClientApp.dll", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "serverReadyAction": { - "action": "openExternally", - "pattern": "\\bNow listening on:\\s+(https?://\\S+)" - }, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "envFile": "${input:dotEnvFilePath}" + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Frontend: Blazor client", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/app/backend/bin/Debug/net8.0/ClientApp.dll", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" }, - { - "name": "Backend: Minimal API", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/app/backend/bin/Debug/net8.0/MinimalApi.dll", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "console": "internalConsole", - "envFile": "${input:dotEnvFilePath}" - } - ], - "inputs": [ - { - "id": "dotEnvFilePath", - "type": "command", - "command": "azure-dev.commands.getDotEnvFilePath" - } - ], - "compounds": [ - { - "name": "Full Stack", - "configurations": ["Backend: Minimal API", "Frontend: Blazor client"] - } - ] - } \ No newline at end of file + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "envFile": "${input:dotEnvFilePath}" + }, + { + "name": "Backend: Minimal API", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/app/backend/bin/Debug/net8.0/MinimalApi.dll", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole", + "envFile": "${input:dotEnvFilePath}" + } + ], + "inputs": [ + { + "id": "dotEnvFilePath", + "type": "command", + "command": "azure-dev.commands.getDotEnvFilePath" + } + ], + "compounds": [ + { + "name": "Full Stack", + "configurations": [ + "Backend: Minimal API", + "Frontend: Blazor client" + ] + } + ] +} \ No newline at end of file diff --git a/azure.yaml b/azure.yaml index e8e2ee75..d175bf8d 100644 --- a/azure.yaml +++ b/azure.yaml @@ -11,6 +11,10 @@ services: docker: path: ../Dockerfile context: ../ + function: + project: ./app/functions/EmbedFunctions + host: function + language: dotnet hooks: postprovision: windows: diff --git a/infra/app/function.bicep b/infra/app/function.bicep new file mode 100644 index 00000000..78ea69bb --- /dev/null +++ b/infra/app/function.bicep @@ -0,0 +1,35 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param allowedOrigins array = [] +param applicationInsightsName string = '' +param appServicePlanId string +@secure() +param appSettings object = {} +param keyVaultName string +param serviceName string = 'function' +param storageAccountName string + +module function '../core/host/functions.bicep' = { + name: '${serviceName}-function' + params: { + name: name + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + allowedOrigins: allowedOrigins + alwaysOn: false + appSettings: appSettings + applicationInsightsName: applicationInsightsName + appServicePlanId: appServicePlanId + keyVaultName: keyVaultName + runtimeName: 'dotnet-isolated' + runtimeVersion: '8.0' + storageAccountName: storageAccountName + scmDoBuildDuringDeployment: false + } +} + +output SERVICE_FUNCTION_IDENTITY_PRINCIPAL_ID string = function.outputs.identityPrincipalId +output SERVICE_FUNCTION_NAME string = function.outputs.name +output SERVICE_FUNCTION_URI string = function.outputs.uri diff --git a/infra/core/host/appservice-appsettings.bicep b/infra/core/host/appservice-appsettings.bicep new file mode 100644 index 00000000..f4b22f81 --- /dev/null +++ b/infra/core/host/appservice-appsettings.bicep @@ -0,0 +1,17 @@ +metadata description = 'Updates app settings for an Azure App Service.' +@description('The name of the app service resource within the current resource group scope') +param name string + +@description('The app settings to be applied to the app service') +@secure() +param appSettings object + +resource appService 'Microsoft.Web/sites@2022-03-01' existing = { + name: name +} + +resource settings 'Microsoft.Web/sites/config@2022-03-01' = { + name: 'appsettings' + parent: appService + properties: appSettings +} diff --git a/infra/core/host/appservice.bicep b/infra/core/host/appservice.bicep new file mode 100644 index 00000000..5fb45e25 --- /dev/null +++ b/infra/core/host/appservice.bicep @@ -0,0 +1,118 @@ +metadata description = 'Creates an Azure App Service in an existing Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Microsoft.Web/sites Properties +param kind string = 'app,linux' + +// Microsoft.Web/sites/config +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' +@secure() +param appSettings object = {} +param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') +param functionAppScaleLimit int = -1 +param linuxFxVersion string = runtimeNameAndVersion +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 +param scmDoBuildDuringDeployment bool = false +param use32BitWorkerProcess bool = false +param ftpsState string = 'FtpsOnly' +param healthCheckPath string = '' + +resource appService 'Microsoft.Web/sites@2022-03-01' = { + name: name + location: location + tags: tags + kind: kind + properties: { + serverFarmId: appServicePlanId + siteConfig: { + linuxFxVersion: linuxFxVersion + alwaysOn: alwaysOn + ftpsState: ftpsState + minTlsVersion: '1.2' + appCommandLine: appCommandLine + numberOfWorkers: numberOfWorkers != -1 ? numberOfWorkers : null + minimumElasticInstanceCount: minimumElasticInstanceCount != -1 ? minimumElasticInstanceCount : null + use32BitWorkerProcess: use32BitWorkerProcess + functionAppScaleLimit: functionAppScaleLimit != -1 ? functionAppScaleLimit : null + healthCheckPath: healthCheckPath + cors: { + allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) + } + } + clientAffinityEnabled: clientAffinityEnabled + httpsOnly: true + } + + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } + + resource configLogs 'config' = { + name: 'logs' + properties: { + applicationLogs: { fileSystem: { level: 'Verbose' } } + detailedErrorMessages: { enabled: true } + failedRequestsTracing: { enabled: true } + httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } } + } + } + + resource basicPublishingCredentialsPoliciesFtp 'basicPublishingCredentialsPolicies' = { + name: 'ftp' + properties: { + allow: false + } + } + + resource basicPublishingCredentialsPoliciesScm 'basicPublishingCredentialsPolicies' = { + name: 'scm' + properties: { + allow: false + } + } +} + +module config 'appservice-appsettings.bicep' = if (!empty(appSettings)) { + name: '${name}-appSettings' + params: { + name: appService.name + appSettings: union(appSettings, + { + SCM_DO_BUILD_DURING_DEPLOYMENT: string(scmDoBuildDuringDeployment) + ENABLE_ORYX_BUILD: string(enableOryxBuild) + }, + runtimeName == 'python' && appCommandLine == '' ? { PYTHON_ENABLE_GUNICORN_MULTIWORKERS: 'true' } : {}, + !empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {}, + !empty(keyVaultName) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {}) + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty(keyVaultName))) { + name: keyVaultName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output identityPrincipalId string = managedIdentity ? appService.identity.principalId : '' +output name string = appService.name +output uri string = 'https://${appService.properties.defaultHostName}' diff --git a/infra/core/host/appserviceplan.bicep b/infra/core/host/appserviceplan.bicep new file mode 100644 index 00000000..2e37e041 --- /dev/null +++ b/infra/core/host/appserviceplan.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param kind string = '' +param reserved bool = true +param sku object + +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: name + location: location + tags: tags + sku: sku + kind: kind + properties: { + reserved: reserved + } +} + +output id string = appServicePlan.id +output name string = appServicePlan.name diff --git a/infra/core/host/functions.bicep b/infra/core/host/functions.bicep new file mode 100644 index 00000000..7070a2c6 --- /dev/null +++ b/infra/core/host/functions.bicep @@ -0,0 +1,86 @@ +metadata description = 'Creates an Azure Function in an existing Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) +param storageAccountName string + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Function Settings +@allowed([ + '~4', '~3', '~2', '~1' +]) +param extensionVersion string = '~4' + +// Microsoft.Web/sites Properties +param kind string = 'functionapp,linux' + +// Microsoft.Web/sites/config +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' +@secure() +param appSettings object = {} +param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') +param functionAppScaleLimit int = -1 +param linuxFxVersion string = runtimeNameAndVersion +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 +param scmDoBuildDuringDeployment bool = true +param use32BitWorkerProcess bool = false +param healthCheckPath string = '' + +module functions 'appservice.bicep' = { + name: '${name}-functions' + params: { + name: name + location: location + tags: tags + allowedOrigins: allowedOrigins + alwaysOn: alwaysOn + appCommandLine: appCommandLine + applicationInsightsName: applicationInsightsName + appServicePlanId: appServicePlanId + appSettings: union(appSettings, { + AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${storage.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}' + FUNCTIONS_EXTENSION_VERSION: extensionVersion + FUNCTIONS_WORKER_RUNTIME: runtimeName + }) + clientAffinityEnabled: clientAffinityEnabled + enableOryxBuild: enableOryxBuild + functionAppScaleLimit: functionAppScaleLimit + healthCheckPath: healthCheckPath + keyVaultName: keyVaultName + kind: kind + linuxFxVersion: linuxFxVersion + managedIdentity: managedIdentity + minimumElasticInstanceCount: minimumElasticInstanceCount + numberOfWorkers: numberOfWorkers + runtimeName: runtimeName + runtimeVersion: runtimeVersion + runtimeNameAndVersion: runtimeNameAndVersion + scmDoBuildDuringDeployment: scmDoBuildDuringDeployment + use32BitWorkerProcess: use32BitWorkerProcess + } +} + +resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { + name: storageAccountName +} + +output identityPrincipalId string = managedIdentity ? functions.outputs.identityPrincipalId : '' +output name string = functions.outputs.name +output uri string = functions.outputs.uri diff --git a/infra/main.bicep b/infra/main.bicep index ab558906..43c2071b 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -11,7 +11,7 @@ param location string param tags string = '' @description('Location for the OpenAI resource group') -@allowed(['canadaeast', 'eastus', 'eastus2', 'francecentral', 'switzerlandnorth', 'uksouth', 'japaneast', 'northcentralus']) +@allowed([ 'canadaeast', 'eastus', 'eastus2', 'francecentral', 'switzerlandnorth', 'uksouth', 'japaneast', 'northcentralus' ]) @metadata({ azd: { type: 'location' @@ -20,7 +20,7 @@ param tags string = '' param openAiResourceGroupLocation string @description('Name of the chat GPT model. Default: gpt-35-turbo') -@allowed(['gpt-35-turbo', 'gpt-4', 'gpt-35-turbo-16k', 'gpt-4-16k']) +@allowed([ 'gpt-35-turbo', 'gpt-4', 'gpt-35-turbo-16k', 'gpt-4-16k' ]) param chatGptModelName string = 'gpt-35-turbo' @description('Name of the Azure Application Insights dashboard') @@ -29,6 +29,9 @@ param applicationInsightsDashboardName string = '' @description('Name of the Azure Application Insights resource') param applicationInsightsName string = '' +@description('Name of the Azure App Service Plan') +param appServicePlanName string = '' + @description('Capacity of the chat GPT deployment. Default: 30') param chatGptDeploymentCapacity int = 30 @@ -65,6 +68,9 @@ param formRecognizerServiceName string = '' @description('SKU name for the Form Recognizer service. Default: S0') param formRecognizerSkuName string = 'S0' +@description('Name of the Azure Function App') +param functionServiceName string = '' + @description('Name of the Azure Key Vault') param keyVaultName string = '' @@ -263,6 +269,43 @@ module web './app/web.bicep' = { } } +// Create an App Service Plan to group applications under the same payment plan and SKU +module appServicePlan './core/host/appserviceplan.bicep' = { + name: 'appserviceplan' + scope: resourceGroup + params: { + name: !empty(appServicePlanName) ? appServicePlanName : '${abbrs.webServerFarms}${resourceToken}' + location: location + tags: updatedTags + sku: { + name: 'Y1' + tier: 'Dynamic' + } + } +} + +// The application backend +module function './app/function.bicep' = { + name: 'function' + scope: resourceGroup + params: { + name: !empty(functionServiceName) ? functionServiceName : '${abbrs.webSitesFunctions}function-${resourceToken}' + location: location + tags: updatedTags + applicationInsightsName: monitoring.outputs.applicationInsightsName + appServicePlanId: appServicePlan.outputs.id + keyVaultName: keyVault.outputs.name + storageAccountName: storage.outputs.name + allowedOrigins: [ web.outputs.SERVICE_WEB_URI ] + appSettings: { + AZURE_FORMRECOGNIZER_SERVICE_ENDPOINT: formRecognizer.outputs.endpoint + AZURE_SEARCH_SERVICE_ENDPOINT: searchService.outputs.endpoint + AZURE_SEARCH_INDEX: searchIndexName + AZURE_STORAGE_BLOB_ENDPOINT: storage.outputs.primaryEndpoints.blob + } + } +} + // Monitor application with Azure Monitor module monitoring 'core/monitor/monitoring.bicep' = { name: 'monitoring' @@ -445,6 +488,77 @@ module searchSvcContribRoleUser 'core/security/role.bicep' = { } } +// FUNCTION ROLES +module openAiRoleFunction 'core/security/role.bicep' = { + scope: openAiResourceGroup + name: 'openai-role-function' + params: { + principalId: principalId + roleDefinitionId: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' + principalType: principalType + } +} + +module formRecognizerRoleFunction 'core/security/role.bicep' = { + scope: formRecognizerResourceGroup + name: 'formrecognizer-role-function' + params: { + principalId: principalId + roleDefinitionId: 'a97b65f3-24c7-4388-baec-2e87135dc908' + principalType: principalType + } +} + +module storageRoleFunction 'core/security/role.bicep' = { + scope: storageResourceGroup + name: 'storage-role-function' + params: { + principalId: principalId + roleDefinitionId: '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1' + principalType: principalType + } +} + +module storageContribRoleFunction 'core/security/role.bicep' = { + scope: storageResourceGroup + name: 'storage-contribrole-function' + params: { + principalId: principalId + roleDefinitionId: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' + principalType: principalType + } +} + +module searchRoleFunction 'core/security/role.bicep' = { + scope: searchServiceResourceGroup + name: 'search-role-function' + params: { + principalId: principalId + roleDefinitionId: '1407120a-92aa-4202-b7e9-c0e197c71c8f' + principalType: principalType + } +} + +module searchContribRoleFunction 'core/security/role.bicep' = { + scope: searchServiceResourceGroup + name: 'search-contrib-role-function' + params: { + principalId: principalId + roleDefinitionId: '8ebe5a00-799e-43f5-93ac-243d3dce84a7' + principalType: principalType + } +} + +module searchSvcContribRoleFunction 'core/security/role.bicep' = { + scope: searchServiceResourceGroup + name: 'search-svccontrib-role-function' + params: { + principalId: principalId + roleDefinitionId: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' + principalType: principalType + } +} + // SYSTEM IDENTITIES module openAiRoleBackend 'core/security/role.bicep' = { scope: openAiResourceGroup diff --git a/scripts/prepdocs.ps1 b/scripts/prepdocs.ps1 index 81db5c86..798bc7b4 100644 --- a/scripts/prepdocs.ps1 +++ b/scripts/prepdocs.ps1 @@ -1,35 +1,69 @@ -Write-Host "" -Write-Host "Loading azd .env file from current environment" -Write-Host "" +if (-not $script:azdCmd) { + $script:azdCmd = Get-Command azd -ErrorAction SilentlyContinue +} + +# Check if 'azd' command is available +if (-not $script:azdCmd) { + throw "Error: 'azd' command is not found. Please ensure you have 'azd' installed. For installation instructions, visit: https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd" +} -$output = azd env get-values +if (-not $script:dotnetCmd) { + $script:dotnetCmd = Get-Command dotnet -ErrorAction SilentlyContinue +} -foreach ($line in $output) { - $name, $value = $line.Split("=") - $value = $value -replace '^\"|\"$' - [Environment]::SetEnvironmentVariable($name, $value) +# Check if 'dotnet' command is available +if (-not $script:dotnetCmd) { + throw "Error: 'dotnet' command is not found. Please ensure you have 'dotnet' installed. For installation instructions, visit: https://learn.microsoft.com/en-us/dotnet/core/tools/" } -Write-Host "Environment variables set." +function Invoke-ExternalCommand { + param ( + [Parameter(Mandatory = $true)] + [string]$Command, + + [Parameter(Mandatory = $false)] + [string]$Arguments + ) + + $processStartInfo = New-Object System.Diagnostics.ProcessStartInfo + $processStartInfo.FileName = $Command + $processStartInfo.Arguments = $Arguments + $processStartInfo.RedirectStandardOutput = $true + $processStartInfo.RedirectStandardError = $true + $processStartInfo.UseShellExecute = $false + $processStartInfo.CreateNoWindow = $true + + $process = New-Object System.Diagnostics.Process + $process.StartInfo = $processStartInfo + $process.Start() | Out-Null + $output = $process.StandardOutput.ReadToEnd() + $errorOutput = $process.StandardError.ReadToEnd() + $process.WaitForExit() + + if ($errorOutput) { + Write-Error $errorOutput + } + + return $output +} if ([string]::IsNullOrEmpty($env:AZD_PREPDOCS_RAN) -or $env:AZD_PREPDOCS_RAN -eq "false") { Write-Host 'Running "PrepareDocs.dll"' Get-Location | Select-Object -ExpandProperty Path - dotnet run --project "app/prepdocs/PrepareDocs/PrepareDocs.csproj" -- ` - './data/*.pdf' ` - --storageendpoint $env:AZURE_STORAGE_BLOB_ENDPOINT ` - --container $env:AZURE_STORAGE_CONTAINER ` - --searchendpoint $env:AZURE_SEARCH_SERVICE_ENDPOINT ` - --searchindex $env:AZURE_SEARCH_INDEX ` - --openaiendpoint $env:AZURE_OPENAI_ENDPOINT ` - --embeddingmodel $env:AZURE_OPENAI_EMBEDDING_DEPLOYMENT ` - --formrecognizerendpoint $env:AZURE_FORMRECOGNIZER_SERVICE_ENDPOINT ` - --tenantid $env:AZURE_TENANT_ID ` - -v - - azd env set AZD_PREPDOCS_RAN "true" -} else { + $dotnetArguments = @" + run --project "app/prepdocs/PrepareDocs/PrepareDocs.csproj" "./data/*.pdf" --storageendpoint $($env:AZURE_STORAGE_BLOB_ENDPOINT) --container $($env:AZURE_STORAGE_CONTAINER) --searchendpoint $($env:AZURE_SEARCH_SERVICE_ENDPOINT) --searchindex $($env:AZURE_SEARCH_INDEX) --openaiendpoint $($env:AZURE_OPENAI_ENDPOINT) --embeddingmodel $($env:AZURE_OPENAI_EMBEDDING_DEPLOYMENT) --formrecognizerendpoint $($env:AZURE_FORMRECOGNIZER_SERVICE_ENDPOINT) --tenantid $($env:AZURE_TENANT_ID) --verbose +"@ + + $output = Invoke-ExternalCommand -Command "dotnet" -Arguments $dotnetArguments + Write-Host $output + + Invoke-ExternalCommand -Command ($azdCmd).Source -Arguments @" + env set AZD_PREPDOCS_RAN "true" +"@ + +} +else { Write-Host "AZD_PREPDOCS_RAN is set to true. Skipping the run." }