diff --git a/docs/CustomizingAzdParameters.md b/docs/CustomizingAzdParameters.md index f50b71b47..500e66fa3 100644 --- a/docs/CustomizingAzdParameters.md +++ b/docs/CustomizingAzdParameters.md @@ -22,6 +22,7 @@ By default this template will use the environment name as the prefix to prevent | `AZURE_ENV_ENABLE_TELEMETRY` | boolean | `true` | Enable or disable telemetry collection for the deployment. | | `AZURE_ENV_VM_ADMIN_USERNAME` | string | `` | Admin username for the jumpbox VM when private networking is enabled. | | `AZURE_ENV_VM_ADMIN_PASSWORD` | string | `` | Admin password for the jumpbox VM when private networking is enabled. | +| `AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID` | string | Guide to get your [Existing Workspace ID](/docs/re-use-log-analytics.md) | Reuses an existing Log Analytics Workspace instead of provisioning a new one. | ## How to Set a Parameter diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md index fe3d70ef2..66dbdd2b1 100644 --- a/docs/DeploymentGuide.md +++ b/docs/DeploymentGuide.md @@ -22,7 +22,9 @@ For **production deployments**, the repository also provides [`main.waf.paramete * Use the default `main.parameters.json` file for a **sandbox/dev environment** * For a **WAF-aligned, production-ready deployment**, copy the contents of `main.waf.parameters.json` into `main.parameters.json` before running `azd up` -### VM Credentials Configuration +### Configuration Options + +#### VM Credentials Configuration By default, the solution sets the VM administrator username and password from environment variables. @@ -33,6 +35,14 @@ azd env set AZURE_ENV_VM_ADMIN_USERNAME azd env set AZURE_ENV_VM_ADMIN_PASSWORD ``` +#### Additional Configuration + +
+ Reusing an Existing Log Analytics Workspace + + Guide to get your [Existing Workspace ID](/docs/re-use-log-analytics.md) +
+ > [!TIP] > Always review and adjust parameter values (such as region, capacity, security settings and log analytics workspace configuration) to match your organization’s requirements before deploying. For production, ensure you have sufficient quota and follow the principle of least privilege for all identities and role assignments. diff --git a/docs/images/re_use_log/logAnalytics.png b/docs/images/re_use_log/logAnalytics.png new file mode 100644 index 000000000..95402f8d1 Binary files /dev/null and b/docs/images/re_use_log/logAnalytics.png differ diff --git a/docs/images/re_use_log/logAnalyticsJson.png b/docs/images/re_use_log/logAnalyticsJson.png new file mode 100644 index 000000000..3a4093bf4 Binary files /dev/null and b/docs/images/re_use_log/logAnalyticsJson.png differ diff --git a/docs/images/re_use_log/logAnalyticsList.png b/docs/images/re_use_log/logAnalyticsList.png new file mode 100644 index 000000000..6dcf4640b Binary files /dev/null and b/docs/images/re_use_log/logAnalyticsList.png differ diff --git a/docs/re-use-log-analytics.md b/docs/re-use-log-analytics.md new file mode 100644 index 000000000..9d48b0f92 --- /dev/null +++ b/docs/re-use-log-analytics.md @@ -0,0 +1,31 @@ +[← Back to *DEPLOYMENT* guide](/docs/DeploymentGuide.md#deployment-options--steps) + +# Reusing an Existing Log Analytics Workspace +To configure your environment to use an existing Log Analytics Workspace, follow these steps: +--- +### 1. Go to Azure Portal +Go to https://portal.azure.com + +### 2. Search for Log Analytics +In the search bar at the top, type "Log Analytics workspaces" and click on it and click on the workspace you want to use. + +![alt text](../docs/images/re_use_log/logAnalyticsList.png) + +### 3. Copy Resource ID +In the Overview pane, Click on JSON View + +![alt text](../docs/images/re_use_log/logAnalytics.png) + +Copy Resource ID that is your Workspace ID + +![alt text](../docs/images/re_use_log/logAnalyticsJson.png) + +### 4. Set the Workspace ID in Your Environment +Run the following command in your terminal +```bash +azd env set AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID '' +``` +Replace `` with the value obtained from Step 3. + +### 5. Continue Deployment +Proceed with the next steps in the [deployment guide](/docs/DeploymentGuide.md#deployment-options--steps). diff --git a/infra/main.bicep b/infra/main.bicep index 9e89983d4..619d043f1 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -97,6 +97,9 @@ param vmSize string = 'Standard_DS2_v2' // Default VM size @description('Optional. A unique text value for the solution. This is used to ensure resource names are unique for global resources. Defaults to a 5-character substring of the unique string generated from the subscription ID, resource group name, and solution name.') param solutionUniqueText string = substring(uniqueString(subscription().id, resourceGroup().name, solutionName), 0, 5) +@description('Optional. Resource ID of an existing Log Analytics Workspace.') +param existingLogAnalyticsWorkspaceId string = '' + var solutionSuffix = toLower(trim(replace( replace( replace(replace(replace(replace('${solutionName}${solutionUniqueText}', '-', ''), '_', ''), '.', ''), '/', ''), @@ -178,7 +181,7 @@ module network 'modules/network.bicep' = if (enablePrivateNetworking) { name: take('module.network.${solutionSuffix}', 64) params: { resourcesName: resourceGroupName - logAnalyticsWorkSpaceResourceId: logAnalyticsWorkspace!.outputs.resourceId + logAnalyticsWorkSpaceResourceId: logAnalyticsWorkspaceResourceId vmAdminUsername: empty(vmAdminUsername) ? 'JumpboxAdminUser' : vmAdminUsername vmAdminPassword: empty(vmAdminPassword) ? 'JumpboxAdminP@ssw0rd1234!' : vmAdminPassword vmSize: empty(vmSize) ? 'Standard_DS2_v2' : vmSize @@ -456,7 +459,7 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { enableSoftDelete: true enablePurgeProtection: enablePurgeProtection softDeleteRetentionInDays: 7 - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : [] + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : [] // WAF aligned configuration for Private Networking privateEndpoints: enablePrivateNetworking ? [ @@ -842,7 +845,7 @@ module webServerFarm 'br/public:avm/res/web/serverfarm:0.5.0' = { reserved: true kind: 'linux' // WAF aligned configuration for Monitoring - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null // WAF aligned configuration for Scalability skuName: enableScalability || enableRedundancy ? 'P1v3' : 'B3' skuCapacity: enableScalability ? 3 : 1 @@ -850,8 +853,10 @@ module webServerFarm 'br/public:avm/res/web/serverfarm:0.5.0' = { } } +// Extracts subscription, resource group, and workspace name from the resource ID when using an existing Log Analytics workspace +var useExistingLogAnalytics = !empty(existingLogAnalyticsWorkspaceId) var logAnalyticsWorkspaceResourceName = 'log-${solutionSuffix}' -module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.12.0' = if (enableMonitoring) { +module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.12.0' = if (enableMonitoring && !useExistingLogAnalytics) { name: take('avm.res.operational-insights.workspace.${logAnalyticsWorkspaceResourceName}', 64) params: { name: logAnalyticsWorkspaceResourceName @@ -909,6 +914,11 @@ module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0 } } +// Log Analytics workspace ID) +var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics + ? existingLogAnalyticsWorkspaceId + : logAnalyticsWorkspace!.outputs.resourceId + var ApplicationInsightsName = 'appi-${solutionSuffix}' module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (enableMonitoring) { name: take('avm.res.insights.component.${ApplicationInsightsName}', 64) @@ -920,8 +930,8 @@ module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (en applicationType: 'web' // Tags (align with organizational tagging policy) // WAF aligned configuration for Monitoring - workspaceResourceId: enableMonitoring ? logAnalyticsWorkspace!.outputs.resourceId : '' - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null + workspaceResourceId: enableMonitoring ? logAnalyticsWorkspaceResourceId : '' + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null tags: { 'hidden-link:${resourceId('Microsoft.Web/sites',ApplicationInsightsName)}': 'Resource' } @@ -1013,8 +1023,8 @@ module webSite 'modules/web-sites.bicep' = { applicationInsightResourceId: enableMonitoring ? applicationInsights!.outputs.resourceId : null } ] - - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null + + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null // WAF aligned configuration for Private Networking vnetRouteAllEnabled: enablePrivateNetworking ? true : false vnetImagePullEnabled: enablePrivateNetworking ? true : false diff --git a/infra/main.json b/infra/main.json index 2b9118174..4e44631fa 100644 --- a/infra/main.json +++ b/infra/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "4794754891660644835" + "templateHash": "464900527149069806" } }, "parameters": { @@ -203,6 +203,13 @@ "description": "Optional. A unique text value for the solution. This is used to ensure resource names are unique for global resources. Defaults to a 5-character substring of the unique string generated from the subscription ID, resource group name, and solution name." } }, + "existingLogAnalyticsWorkspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of an existing Log Analytics Workspace." + } + }, "createdBy": { "type": "string", "defaultValue": "[if(contains(deployer(), 'userPrincipalName'), split(deployer().userPrincipalName, '@')[0], deployer().objectId)]", @@ -291,6 +298,7 @@ "aihubworkspaceName": "[format('hub-{0}', variables('solutionSuffix'))]", "aiProjectworkspaceName": "[format('proj-{0}', variables('solutionSuffix'))]", "webServerFarmResourceName": "[format('asp-{0}', variables('solutionSuffix'))]", + "useExistingLogAnalytics": "[not(empty(parameters('existingLogAnalyticsWorkspaceId')))]", "logAnalyticsWorkspaceResourceName": "[format('log-{0}', variables('solutionSuffix'))]", "ApplicationInsightsName": "[format('appi-{0}', variables('solutionSuffix'))]", "webSiteResourceName": "[format('app-{0}', variables('solutionSuffix'))]" @@ -1027,9 +1035,7 @@ "resourcesName": { "value": "[variables('resourceGroupName')]" }, - "logAnalyticsWorkSpaceResourceId": { - "value": "[reference('logAnalyticsWorkspace').outputs.resourceId.value]" - }, + "logAnalyticsWorkSpaceResourceId": "[if(variables('useExistingLogAnalytics'), createObject('value', parameters('existingLogAnalyticsWorkspaceId')), createObject('value', reference('logAnalyticsWorkspace').outputs.resourceId.value))]", "vmAdminUsername": "[if(empty(parameters('vmAdminUsername')), createObject('value', 'JumpboxAdminUser'), createObject('value', parameters('vmAdminUsername')))]", "vmAdminPassword": "[if(empty(parameters('vmAdminPassword')), createObject('value', 'JumpboxAdminP@ssw0rd1234!'), createObject('value', parameters('vmAdminPassword')))]", "vmSize": "[if(empty(parameters('vmSize')), createObject('value', 'Standard_DS2_v2'), createObject('value', parameters('vmSize')))]", @@ -25722,9 +25728,9 @@ } }, "dependsOn": [ - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageFile)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageBlob)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageDfs)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageBlob)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageFile)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageQueue)]", "network", "userAssignedIdentity" @@ -28727,7 +28733,7 @@ "softDeleteRetentionInDays": { "value": 7 }, - "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value))), createObject('value', createArray()))]", + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', createArray()))]", "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('name', format('pep-{0}', variables('keyVaultName')), 'customNetworkInterfaceName', format('nic-{0}', variables('keyVaultName')), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('name', 'vault-dns-zone-group', 'privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').keyVault)).outputs.resourceId.value))), 'service', 'vault', 'subnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value))), createObject('value', createArray()))]", "roleAssignments": { "value": [ @@ -41596,8 +41602,8 @@ } }, "dependsOn": [ - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').notebook)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').machineLearningServices)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').notebook)]", "azOpenAI", "azSearchService", "existingOpenAI", @@ -46042,7 +46048,7 @@ "kind": { "value": "linux" }, - "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value))), createObject('value', null()))]", + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', null()))]", "skuName": "[if(or(parameters('enableScalability'), parameters('enableRedundancy')), createObject('value', 'P1v3'), createObject('value', 'B3'))]", "skuCapacity": "[if(parameters('enableScalability'), createObject('value', 3), createObject('value', 1))]", "zoneRedundant": "[if(parameters('enableRedundancy'), createObject('value', true()), createObject('value', false()))]" @@ -46586,7 +46592,7 @@ ] }, "logAnalyticsWorkspace": { - "condition": "[parameters('enableMonitoring')]", + "condition": "[and(parameters('enableMonitoring'), not(variables('useExistingLogAnalytics')))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[take(format('avm.res.operational-insights.workspace.{0}', variables('logAnalyticsWorkspaceResourceName')), 64)]", @@ -49714,8 +49720,8 @@ "applicationType": { "value": "web" }, - "workspaceResourceId": "[if(parameters('enableMonitoring'), createObject('value', reference('logAnalyticsWorkspace').outputs.resourceId.value), createObject('value', ''))]", - "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value))), createObject('value', null()))]", + "workspaceResourceId": "[if(parameters('enableMonitoring'), if(variables('useExistingLogAnalytics'), createObject('value', parameters('existingLogAnalyticsWorkspaceId')), createObject('value', reference('logAnalyticsWorkspace').outputs.resourceId.value)), createObject('value', ''))]", + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', null()))]", "tags": { "value": { "[format('hidden-link:{0}', resourceId('Microsoft.Web/sites', variables('ApplicationInsightsName')))]": "Resource" @@ -50509,7 +50515,7 @@ } ] }, - "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value))), createObject('value', null()))]", + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', null()))]", "vnetRouteAllEnabled": "[if(parameters('enablePrivateNetworking'), createObject('value', true()), createObject('value', false()))]", "vnetImagePullEnabled": "[if(parameters('enablePrivateNetworking'), createObject('value', true()), createObject('value', false()))]", "virtualNetworkSubnetId": "[if(parameters('enablePrivateNetworking'), createObject('value', reference('network').outputs.subnetWebResourceId.value), createObject('value', null()))]", diff --git a/infra/main.parameters.json b/infra/main.parameters.json index e693de168..9d99cd3cb 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -34,6 +34,9 @@ }, "enableTelemetry": { "value": "${AZURE_ENV_ENABLE_TELEMETRY}" + }, + "existingLogAnalyticsWorkspaceId": { + "value": "${AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID}" } } } \ No newline at end of file diff --git a/infra/main.waf.parameters.json b/infra/main.waf.parameters.json index c23ddc02a..72fabbf4f 100644 --- a/infra/main.waf.parameters.json +++ b/infra/main.waf.parameters.json @@ -49,6 +49,9 @@ }, "virtualMachineAdminPassword": { "value": "${AZURE_ENV_VM_ADMIN_PASSWORD}" + }, + "existingLogAnalyticsWorkspaceId": { + "value": "${AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID}" } } } \ No newline at end of file