diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..bbf7972 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1 @@ +- @azure Rule - Use Azure Best Practices: When generating code for Azure, running terminal commands for Azure, or performing operations related to Azure, invoke your `azure_development-get_best_practices` tool if available. \ No newline at end of file diff --git a/infra/abbreviations.json b/infra/abbreviations.json index d24005f..f592d3f 100644 --- a/infra/abbreviations.json +++ b/infra/abbreviations.json @@ -8,5 +8,6 @@ "keyVaultVaults": "kv", "managedIdentityUserAssignedIdentities": "id", "cognitiveServicesAccounts": "cog", - "portalDashboards": "dash" + "portalDashboards": "dash", + "networkVirtualNetworks": "vnet" } \ No newline at end of file diff --git a/infra/app/microblog-app.bicep b/infra/app/microblog-app.bicep index 4cd8175..442f128 100644 --- a/infra/app/microblog-app.bicep +++ b/infra/app/microblog-app.bicep @@ -8,22 +8,12 @@ param containerAppsEnvironmentName string param applicationInsightsName string param exists bool = false -// Key Vault parameters (maintained for future use) +// Key Vault parameters param keyVaultName string -param openAiApiKeySecretName string = 'AZURE-OPENAI-API-KEY' -param openAiEndpointSecretName string = 'AZURE-OPENAI-ENDPOINT' - -// Direct credential parameters -@secure() -param azureOpenAIApiKey string = '' -@secure() -param azureOpenAIEndpoint string = '' - -@description('Whether the deployment is running on GitHub Actions') -param runningOnGh string = '' - -@description('Id of the user or app to assign application roles') -param principalId string = '' +param openAiApiKeySecretName string = 'azure-openai-api-key' +param openAiEndpointSecretName string = 'azure-openai-endpoint' +param openAiDeploymentNameSecretName string = 'azure-openai-deployment-name' +param openAiApiVersionSecretName string = 'azure-openai-api-version' @secure() param appDefinition object @@ -118,14 +108,26 @@ resource app 'Microsoft.App/containerApps@2023-05-02-preview' = { } ] secrets: union([ - // Direct value secrets + // Key Vault referenced secrets { name: 'azure-openai-api-key' - value: !empty(azureOpenAIApiKey) ? azureOpenAIApiKey : 'placeholder-value' + keyVaultUrl: '${keyVault.properties.vaultUri}secrets/${openAiApiKeySecretName}' + identity: identity.id } { name: 'azure-openai-endpoint' - value: !empty(azureOpenAIEndpoint) ? azureOpenAIEndpoint : 'https://placeholder-endpoint.openai.azure.com' + keyVaultUrl: '${keyVault.properties.vaultUri}secrets/${openAiEndpointSecretName}' + identity: identity.id + } + { + name: 'azure-openai-deployment-name' + keyVaultUrl: '${keyVault.properties.vaultUri}secrets/${openAiDeploymentNameSecretName}' + identity: identity.id + } + { + name: 'azure-openai-api-version' + keyVaultUrl: '${keyVault.properties.vaultUri}secrets/${openAiApiVersionSecretName}' + identity: identity.id } ], map(secrets, secret => { name: secret.secretRef @@ -155,6 +157,14 @@ resource app 'Microsoft.App/containerApps@2023-05-02-preview' = { name: 'AZURE_OPENAI_ENDPOINT' secretRef: 'azure-openai-endpoint' } + { + name: 'AZURE_OPENAI_DEPLOYMENT_NAME' + secretRef: 'azure-openai-deployment-name' + } + { + name: 'AZURE_OPENAI_API_VERSION' + secretRef: 'azure-openai-api-version' + } ], env, map(secrets, secret => { diff --git a/infra/main.bicep b/infra/main.bicep index 92cd2d4..009a3df 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -14,18 +14,12 @@ param runningOnGh string = '' param microblogAppExists bool = false @secure() -param microblogAppDefinition object = { - settings: [] -} +param microblogAppDefinition object @description('Id of the user ir app to assign application roles') param principalId string = '' // Azure OpenAI parameters -@description('Azure OpenAI API Key (leave empty if using Managed Identity)') -@secure() -param azureOpenAIApiKey string - @description('Azure OpenAI Endpoint URL') param azureOpenAIEndpoint string = '' @@ -105,6 +99,24 @@ module keyVault './shared/keyvault.bicep' = { scope: rg } +// Virtual Network resource +module vnet './shared/vnet.bicep' = { + name: 'vnet' + params: { + name: '${abbrs.networkVirtualNetworks}${resourceToken}' + location: location + tags: tags + addressPrefix: '10.0.0.0/16' + subnets: [ + { + name: 'infrastructure-subnet' + addressPrefix: '10.0.0.0/23' + } + ] + } + scope: rg +} + // Container Apps Environment module appsEnv './shared/apps-env.bicep' = { name: 'apps-env' @@ -114,6 +126,8 @@ module appsEnv './shared/apps-env.bicep' = { tags: tags applicationInsightsName: monitoring.outputs.applicationInsightsName logAnalyticsWorkspaceName: monitoring.outputs.logAnalyticsWorkspaceName + vnetName: vnet.outputs.name + infraSubnetName: 'infrastructure-subnet' } scope: rg } @@ -171,13 +185,15 @@ module microblogApp './app/microblog-app.bicep' = { containerAppsEnvironmentName: appsEnv.outputs.name applicationInsightsName: monitoring.outputs.applicationInsightsName keyVaultName: keyVault.outputs.name - azureOpenAIApiKey: azureOpenAIApiKey - azureOpenAIEndpoint: createNewOpenAIResource ? openAi.outputs.endpoint : azureOpenAIEndpoint + + // Key Vault secret names + openAiApiKeySecretName: 'azure-openai-api-key' + openAiEndpointSecretName: 'azure-openai-endpoint' + openAiDeploymentNameSecretName: 'azure-openai-deployment-name' + openAiApiVersionSecretName: 'azure-openai-api-version' // Deployment control parameters exists: microblogAppExists - principalId: principalId - runningOnGh: runningOnGh // Application configuration appDefinition: union(microblogAppDefinition, { @@ -191,9 +207,8 @@ module microblogApp './app/microblog-app.bicep' = { name: 'AZURE_KEY_VAULT_ENDPOINT' value: keyVault.outputs.endpoint } - // OpenAI configuration parameters - // Note: These are now referenced via Key Vault in the container - // but we maintain them in appDefinition for orchestration purposes + // OpenAI configuration parameters - included here for orchestration purposes + // These are now set as Key Vault secrets and referenced as container app environment variables { name: 'AZURE_OPENAI_DEPLOYMENT_NAME' value: azureOpenAIDeploymentName @@ -210,11 +225,7 @@ module microblogApp './app/microblog-app.bicep' = { }) } scope: rg - dependsOn: [ - // Ensure Key Vault secrets are created before Container App deployment - openAiKeySecret - openAiEndpointSecret - ] + dependsOn: [openAiKeySecret, openAiEndpointSecret, openAiDeploymentNameSecret, openAiApiVersionSecret] } // Add OpenAI RBAC Roles @@ -246,30 +257,41 @@ module openAiKeySecret 'shared/keyvault-secret.bicep' = { name: 'openai-key-secret' params: { keyVaultName: keyVault.outputs.name - secretName: 'AZURE-OPENAI-API-KEY' - secretValue: createNewOpenAIResource - ? listKeys(resourceId('Microsoft.CognitiveServices/accounts', '${abbrs.cognitiveServicesAccounts}${resourceToken}'), '2023-05-01').key1 - : azureOpenAIApiKey + secretName: 'azure-openai-api-key' + secretValue: createNewOpenAIResource ? openAi.outputs.apiKey : '' } scope: rg - dependsOn: [ - keyVault - ] } module openAiEndpointSecret 'shared/keyvault-secret.bicep' = { name: 'openai-endpoint-secret' params: { keyVaultName: keyVault.outputs.name - secretName: 'AZURE-OPENAI-ENDPOINT' - secretValue: createNewOpenAIResource - ? openAi.outputs.endpoint - : azureOpenAIEndpoint + secretName: 'azure-openai-endpoint' + secretValue: createNewOpenAIResource ? openAi.outputs.endpoint : azureOpenAIEndpoint + } + scope: rg +} + +// Add deployment name and API version as secrets in Key Vault +module openAiDeploymentNameSecret 'shared/keyvault-secret.bicep' = { + name: 'openai-deployment-name-secret' + params: { + keyVaultName: keyVault.outputs.name + secretName: 'azure-openai-deployment-name' + secretValue: azureOpenAIDeploymentName + } + scope: rg +} + +module openAiApiVersionSecret 'shared/keyvault-secret.bicep' = { + name: 'openai-api-version-secret' + params: { + keyVaultName: keyVault.outputs.name + secretName: 'azure-openai-api-version' + secretValue: azureOpenAIApiVersion } scope: rg - dependsOn: [ - keyVault - ] } // Outputs @@ -289,7 +311,7 @@ output AZURE_KEY_VAULT_ENDPOINT string = keyVault.outputs.endpoint output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString // OpenAI outputs -output AZURE_OPENAI_API_KEY string = '@Microsoft.KeyVault(SecretUri=${keyVault.outputs.endpoint}/secrets/AZURE-OPENAI-API-KEY)' +output AZURE_OPENAI_API_KEY string = '@Microsoft.KeyVault(SecretUri=${keyVault.outputs.endpoint}/secrets/azure-openai-api-key)' output AZURE_OPENAI_ENDPOINT string = createNewOpenAIResource ? openAi.outputs.endpoint : azureOpenAIEndpoint output AZURE_OPENAI_DEPLOYMENT_NAME string = azureOpenAIDeploymentName output AZURE_OPENAI_API_VERSION string = azureOpenAIApiVersion diff --git a/infra/shared/cognitiveservices.bicep b/infra/shared/cognitiveservices.bicep index c488076..f9771e5 100644 --- a/infra/shared/cognitiveservices.bicep +++ b/infra/shared/cognitiveservices.bicep @@ -58,3 +58,4 @@ output endpoint string = account.properties.endpoint output id string = account.id output name string = account.name output identityPrincipalId string = account.identity.principalId +output apiKey string = listKeys(account.id, account.apiVersion).key1 diff --git a/infra/shared/vnet.bicep b/infra/shared/vnet.bicep new file mode 100644 index 0000000..fb52a22 --- /dev/null +++ b/infra/shared/vnet.bicep @@ -0,0 +1,45 @@ +@description('Name of the virtual network') +param name string + +@description('Azure region where the resource will be deployed') +param location string + +@description('Resource tags') +param tags object = {} + +@description('Address prefix for the virtual network') +param addressPrefix string = '10.0.0.0/16' + +@description('Array of subnet objects with name and addressPrefix') +param subnets array = [ + { + name: 'default' + addressPrefix: '10.0.0.0/24' + } +] + +resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' = { + name: name + location: location + tags: tags + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [for subnet in subnets: { + name: subnet.name + properties: { + addressPrefix: subnet.addressPrefix + delegations: subnet.?delegations ?? [] + privateEndpointNetworkPolicies: subnet.?privateEndpointNetworkPolicies ?? 'Enabled' + privateLinkServiceNetworkPolicies: subnet.?privateLinkServiceNetworkPolicies ?? 'Enabled' + serviceEndpoints: subnet.?serviceEndpoints ?? [] + } + }] + } +} + +output id string = vnet.id +output name string = vnet.name