diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 853cd7c506..906430d006 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,6 +11,7 @@ "microsoft-dciborow.align-bicep", "ms-azuretools.vscode-bicep", "ms-vsliveshare.vsliveshare", + "ms-vscode.azure-account", "ms-vscode-remote.remote-containers", "zokugun.explicit-folding", "GitHub.copilot-labs" diff --git a/avm/res/app/job/README.md b/avm/res/app/job/README.md index 24315945b5..b4bdd8f3ec 100644 --- a/avm/res/app/job/README.md +++ b/avm/res/app/job/README.md @@ -27,11 +27,86 @@ The following section provides usage examples for the module, which were used to >**Note**: To reference the module, please use the following syntax `br/public:avm/res/app/job:`. -- [Using only defaults](#example-1-using-only-defaults) -- [Using large parameter set](#example-2-using-large-parameter-set) -- [WAF-aligned](#example-3-waf-aligned) +- [Using a consumption plan](#example-1-using-a-consumption-plan) +- [Using only defaults](#example-2-using-only-defaults) +- [Using large parameter set](#example-3-using-large-parameter-set) +- [WAF-aligned](#example-4-waf-aligned) -### Example 1: _Using only defaults_ +### Example 1: _Using a consumption plan_ + +This instance deploys the module to a Container Apps Environment with a consumption plan. + + +
+ +via Bicep module + +```bicep +module job 'br/public:avm/res/app/job:' = { + name: 'jobDeployment' + params: { + // Required parameters + containers: [ + { + image: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + name: 'simple-hello-world-container' + } + ] + environmentResourceId: '' + name: 'ajcon001' + triggerType: 'Manual' + // Non-required parameters + location: '' + manualTriggerConfig: {} + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "containers": { + "value": [ + { + "image": "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest", + "name": "simple-hello-world-container" + } + ] + }, + "environmentResourceId": { + "value": "" + }, + "name": { + "value": "ajcon001" + }, + "triggerType": { + "value": "Manual" + }, + // Non-required parameters + "location": { + "value": "" + }, + "manualTriggerConfig": { + "value": {} + } + } +} +``` + +
+

+ +### Example 2: _Using only defaults_ This instance deploys the module with the minimum set of required parameters. @@ -113,7 +188,7 @@ module job 'br/public:avm/res/app/job:' = {

-### Example 2: _Using large parameter set_ +### Example 3: _Using large parameter set_ This instance deploys the module with most of its features enabled. @@ -451,7 +526,7 @@ module job 'br/public:avm/res/app/job:' = {

-### Example 3: _WAF-aligned_ +### Example 4: _WAF-aligned_ This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. @@ -621,7 +696,7 @@ module job 'br/public:avm/res/app/job:' = { | [`secrets`](#parameter-secrets) | array | The secrets of the Container App. | | [`tags`](#parameter-tags) | object | Tags of the resource. | | [`volumes`](#parameter-volumes) | array | List of volume definitions for the Container App. | -| [`workloadProfileName`](#parameter-workloadprofilename) | string | The name of the workload profile to use. | +| [`workloadProfileName`](#parameter-workloadprofilename) | string | The name of the workload profile to use. Leave empty to use a consumption based profile. | ### Parameter: `containers` @@ -1767,6 +1842,7 @@ The secrets of the Container App. keyVaultUrl: 'https://myvault${environment().suffixes.keyvaultDns}/secrets/mysecret' } { + // You can do this, but you shouldn't. Use a secret reference instead. name: 'mysecret' value: 'mysecretvalue' } @@ -1928,11 +2004,10 @@ Name of the Container App secret from which to pull the secret value. ### Parameter: `workloadProfileName` -The name of the workload profile to use. +The name of the workload profile to use. Leave empty to use a consumption based profile. - Required: No - Type: string -- Default: `'Consumption'` ## Outputs diff --git a/avm/res/app/job/main.bicep b/avm/res/app/job/main.bicep index 87094e1034..f237c49b71 100644 --- a/avm/res/app/job/main.bicep +++ b/avm/res/app/job/main.bicep @@ -86,8 +86,8 @@ param manualTriggerConfig manualTriggerConfigType? @description('Optional. The maximum number of times a replica can be retried.') param replicaRetryLimit int = 0 -@description('Optional. The name of the workload profile to use.') -param workloadProfileName string = 'Consumption' +@description('Optional. The name of the workload profile to use. Leave empty to use a consumption based profile.') +param workloadProfileName string? @description('Optional. The secrets of the Container App.') @metadata({ @@ -104,6 +104,7 @@ param workloadProfileName string = 'Consumption' keyVaultUrl: 'https://myvault${environment().suffixes.keyvaultDns}/secrets/mysecret' } { + // You can do this, but you shouldn't. Use a secret reference instead. name: 'mysecret' value: 'mysecretvalue' } @@ -114,6 +115,7 @@ param workloadProfileName string = 'Consumption' ] ''' }) +#disable-next-line secure-secrets-in-params // @secure() is specified in UDT param secrets secretType[]? @description('Optional. List of volume definitions for the Container App.') diff --git a/avm/res/app/job/main.json b/avm/res/app/job/main.json index 718a957a6d..44afb314c5 100644 --- a/avm/res/app/job/main.json +++ b/avm/res/app/job/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.28.1.47646", - "templateHash": "785827336085494592" + "templateHash": "2845806145425627201" }, "name": "Container App Jobs", "description": "This module deploys a Container App Job.", @@ -886,9 +886,9 @@ }, "workloadProfileName": { "type": "string", - "defaultValue": "Consumption", + "nullable": true, "metadata": { - "description": "Optional. The name of the workload profile to use." + "description": "Optional. The name of the workload profile to use. Leave empty to use a consumption based profile." } }, "secrets": { @@ -898,7 +898,7 @@ }, "nullable": true, "metadata": { - "example": " [\n {\n name: 'mysecret'\n identity: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myManagedIdentity'\n keyVaultUrl: 'https://myvault${environment().suffixes.keyvaultDns}/secrets/mysecret'\n }\n {\n name: 'mysecret'\n identity: 'system'\n keyVaultUrl: 'https://myvault${environment().suffixes.keyvaultDns}/secrets/mysecret'\n }\n {\n name: 'mysecret'\n value: 'mysecretvalue'\n }\n {\n name: 'connection-string'\n value: listKeys('/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.Storage/storageAccounts/myStorageAccount', '2023-04-01').keys[0].value\n }\n ]\n ", + "example": " [\n {\n name: 'mysecret'\n identity: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myManagedIdentity'\n keyVaultUrl: 'https://myvault${environment().suffixes.keyvaultDns}/secrets/mysecret'\n }\n {\n name: 'mysecret'\n identity: 'system'\n keyVaultUrl: 'https://myvault${environment().suffixes.keyvaultDns}/secrets/mysecret'\n }\n {\n // You can do this, but you shouldn't. Use a secret reference instead.\n name: 'mysecret'\n value: 'mysecretvalue'\n }\n {\n name: 'connection-string'\n value: listKeys('/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.Storage/storageAccounts/myStorageAccount', '2023-04-01').keys[0].value\n }\n ]\n ", "description": "Optional. The secrets of the Container App." } }, diff --git a/avm/res/app/job/tests/e2e/consumptionPlan/dependencies.bicep b/avm/res/app/job/tests/e2e/consumptionPlan/dependencies.bicep new file mode 100644 index 0000000000..714fda8ab7 --- /dev/null +++ b/avm/res/app/job/tests/e2e/consumptionPlan/dependencies.bicep @@ -0,0 +1,16 @@ +@description('Required. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Managed Environment to create.') +param managedEnvironmentName string + +resource managedEnvironment 'Microsoft.App/managedEnvironments@2024-03-01' = { + name: managedEnvironmentName + location: location + properties: { + zoneRedundant: false + } +} + +@description('The resource ID of the created Managed Environment.') +output managedEnvironmentResourceId string = managedEnvironment.id diff --git a/avm/res/app/job/tests/e2e/consumptionPlan/main.test.bicep b/avm/res/app/job/tests/e2e/consumptionPlan/main.test.bicep new file mode 100644 index 0000000000..c018523634 --- /dev/null +++ b/avm/res/app/job/tests/e2e/consumptionPlan/main.test.bicep @@ -0,0 +1,66 @@ +targetScope = 'subscription' + +metadata name = 'Using a consumption plan' +metadata description = 'This instance deploys the module to a Container Apps Environment with a consumption plan.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-app.job-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'ajcon' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// =========== // +// Deployments // +// =========== // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-paramNested' + params: { + location: resourceLocation + managedEnvironmentName: 'dep-${namePrefix}-menv-${serviceShort}' + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}${serviceShort}001' + environmentResourceId: nestedDependencies.outputs.managedEnvironmentResourceId + location: resourceLocation + triggerType: 'Manual' + manualTriggerConfig: {} + containers: [ + { + name: 'simple-hello-world-container' + image: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + } + ] + } + } +] diff --git a/avm/res/app/job/tests/e2e/defaults/dependencies.bicep b/avm/res/app/job/tests/e2e/defaults/dependencies.bicep index bb2af3d0f8..8468d023ec 100644 --- a/avm/res/app/job/tests/e2e/defaults/dependencies.bicep +++ b/avm/res/app/job/tests/e2e/defaults/dependencies.bicep @@ -4,14 +4,16 @@ param location string = resourceGroup().location @description('Required. The name of the Managed Environment to create.') param managedEnvironmentName string -resource managedEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' = { +resource managedEnvironment 'Microsoft.App/managedEnvironments@2024-03-01' = { name: managedEnvironmentName location: location properties: { workloadProfiles: [ { - workloadProfileType: 'Consumption' - name: 'Consumption' + workloadProfileType: 'D4' + name: 'WorkloadProfile' + minimumCount: 1 + maximumCount: 3 } ] } diff --git a/avm/res/app/job/tests/e2e/max/dependencies.bicep b/avm/res/app/job/tests/e2e/max/dependencies.bicep index 1955015c44..798b7bff3f 100644 --- a/avm/res/app/job/tests/e2e/max/dependencies.bicep +++ b/avm/res/app/job/tests/e2e/max/dependencies.bicep @@ -13,7 +13,7 @@ param workloadProfileName string @description('Required. The name of the storage account to create.') param storageAccountName string -resource managedEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' = { +resource managedEnvironment 'Microsoft.App/managedEnvironments@2024-03-01' = { name: managedEnvironmentName location: location properties: { diff --git a/avm/res/app/job/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/app/job/tests/e2e/waf-aligned/dependencies.bicep index b03d4aca93..57183bfe1e 100644 --- a/avm/res/app/job/tests/e2e/waf-aligned/dependencies.bicep +++ b/avm/res/app/job/tests/e2e/waf-aligned/dependencies.bicep @@ -10,7 +10,7 @@ param managedIdentityName string @description('Required. The name of the workload profile to create.') param workloadProfileName string -resource managedEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' = { +resource managedEnvironment 'Microsoft.App/managedEnvironments@2024-03-01' = { name: managedEnvironmentName location: location properties: { diff --git a/avm/res/app/job/version.json b/avm/res/app/job/version.json index 1c035df49f..b3d560b1ad 100644 --- a/avm/res/app/job/version.json +++ b/avm/res/app/job/version.json @@ -1,7 +1,7 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.2", + "version": "0.3", "pathFilters": [ "./main.json" ] -} \ No newline at end of file +} diff --git a/avm/res/cache/redis/README.md b/avm/res/cache/redis/README.md index a5be8429cb..13bd7725e1 100644 --- a/avm/res/cache/redis/README.md +++ b/avm/res/cache/redis/README.md @@ -18,7 +18,8 @@ This module deploys a Redis Cache. | :-- | :-- | | `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | -| `Microsoft.Cache/redis` | [2023-08-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Cache/2023-08-01/redis) | +| `Microsoft.Cache/redis` | [2024-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Cache/redis) | +| `Microsoft.Cache/redis/linkedServers` | [2024-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Cache/redis/linkedServers) | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | | `Microsoft.Network/privateEndpoints` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints) | | `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints/privateDnsZoneGroups) | @@ -34,7 +35,8 @@ The following section provides usage examples for the module, which were used to - [Using only defaults](#example-1-using-only-defaults) - [Using EntraID authentication](#example-2-using-entraid-authentication) - [Using large parameter set](#example-3-using-large-parameter-set) -- [WAF-aligned](#example-4-waf-aligned) +- [Passive Geo-Replicated Redis Cache](#example-4-passive-geo-replicated-redis-cache) +- [WAF-aligned](#example-5-waf-aligned) ### Example 1: _Using only defaults_ @@ -400,7 +402,113 @@ module redis 'br/public:avm/res/cache/redis:' = {

-### Example 4: _WAF-aligned_ +### Example 4: _Passive Geo-Replicated Redis Cache_ + +This instance deploys the module with geo-replication enabled. + + +

+ +via Bicep module + +```bicep +module redis 'br/public:avm/res/cache/redis:' = { + name: 'redisDeployment' + params: { + // Required parameters + name: 'crpgeo001' + // Non-required parameters + capacity: 2 + enableNonSslPort: true + geoReplicationObject: { + linkedRedisCacheLocation: '' + linkedRedisCacheResourceId: '' + name: '' + } + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + minimumTlsVersion: '1.2' + redisVersion: '6' + replicasPerMaster: 1 + replicasPerPrimary: 1 + shardCount: 1 + skuName: 'Premium' + zoneRedundant: false + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "name": { + "value": "crpgeo001" + }, + // Non-required parameters + "capacity": { + "value": 2 + }, + "enableNonSslPort": { + "value": true + }, + "geoReplicationObject": { + "value": { + "linkedRedisCacheLocation": "", + "linkedRedisCacheResourceId": "", + "name": "" + } + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "minimumTlsVersion": { + "value": "1.2" + }, + "redisVersion": { + "value": "6" + }, + "replicasPerMaster": { + "value": 1 + }, + "replicasPerPrimary": { + "value": 1 + }, + "shardCount": { + "value": 1 + }, + "skuName": { + "value": "Premium" + }, + "zoneRedundant": { + "value": false + } + } +} +``` + +
+

+ +### Example 5: _WAF-aligned_ This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. @@ -599,6 +707,7 @@ module redis 'br/public:avm/res/cache/redis:' = { | [`diagnosticSettings`](#parameter-diagnosticsettings) | array | The diagnostic settings of the service. | | [`enableNonSslPort`](#parameter-enablenonsslport) | bool | Specifies whether the non-ssl Redis server port (6379) is enabled. | | [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`geoReplicationObject`](#parameter-georeplicationobject) | object | The geo-replication settings of the service. Requires a Premium SKU. Geo-replication is not supported on a cache with multiple replicas per primary. Secondary cache VM Size must be same or higher as compared to the primary cache VM Size. Geo-replication between a vnet and non vnet cache (and vice-a-versa) not supported. | | [`location`](#parameter-location) | string | The location to deploy the Redis cache service. | | [`lock`](#parameter-lock) | object | The lock settings of the service. | | [`managedIdentities`](#parameter-managedidentities) | object | The managed identity definition for this resource. | @@ -808,6 +917,14 @@ Enable/Disable usage telemetry for module. - Type: bool - Default: `True` +### Parameter: `geoReplicationObject` + +The geo-replication settings of the service. Requires a Premium SKU. Geo-replication is not supported on a cache with multiple replicas per primary. Secondary cache VM Size must be same or higher as compared to the primary cache VM Size. Geo-replication between a vnet and non vnet cache (and vice-a-versa) not supported. + +- Required: No +- Type: object +- Default: `{}` + ### Parameter: `location` The location to deploy the Redis cache service. diff --git a/avm/res/cache/redis/linked-servers/README.md b/avm/res/cache/redis/linked-servers/README.md new file mode 100644 index 0000000000..fe56c2437d --- /dev/null +++ b/avm/res/cache/redis/linked-servers/README.md @@ -0,0 +1,89 @@ +# Redis Cache Linked Servers `[Microsoft.Cache/redis/linkedServers]` + +This module connects a primary and secondary Redis Cache together for geo-replication. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Cache/redis/linkedServers` | [2024-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Cache/redis/linkedServers) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`linkedRedisCacheResourceId`](#parameter-linkedrediscacheresourceid) | string | The resource ID of the linked server. | +| [`redisCacheName`](#parameter-rediscachename) | string | Primary Redis cache name. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`linkedRedisCacheLocation`](#parameter-linkedrediscachelocation) | string | The location of the linked server. If not provided, the location of the primary Redis cache is used. | +| [`name`](#parameter-name) | string | The name of the secondary Redis cache. If not provided, the primary Redis cache name is used. | +| [`serverRole`](#parameter-serverrole) | string | The role of the linked server. Possible values include: "Primary", "Secondary". Default value is "Secondary". | + +### Parameter: `linkedRedisCacheResourceId` + +The resource ID of the linked server. + +- Required: Yes +- Type: string + +### Parameter: `redisCacheName` + +Primary Redis cache name. + +- Required: Yes +- Type: string + +### Parameter: `linkedRedisCacheLocation` + +The location of the linked server. If not provided, the location of the primary Redis cache is used. + +- Required: No +- Type: string + +### Parameter: `name` + +The name of the secondary Redis cache. If not provided, the primary Redis cache name is used. + +- Required: No +- Type: string +- Default: `[parameters('redisCacheName')]` + +### Parameter: `serverRole` + +The role of the linked server. Possible values include: "Primary", "Secondary". Default value is "Secondary". + +- Required: No +- Type: string +- Default: `'Secondary'` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `geoReplicatedPrimaryHostName` | string | The hostname of the linkedServer. | +| `name` | string | The name of the linkedServer resource. | +| `resourceGroupName` | string | The resource group of the deployed linkedServer. | +| `resourceId` | string | The resource ID of the linkedServer. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/cache/redis/linked-servers/main.bicep b/avm/res/cache/redis/linked-servers/main.bicep new file mode 100644 index 0000000000..6b102d8f55 --- /dev/null +++ b/avm/res/cache/redis/linked-servers/main.bicep @@ -0,0 +1,44 @@ +metadata name = 'Redis Cache Linked Servers' +metadata description = 'This module connects a primary and secondary Redis Cache together for geo-replication.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. Primary Redis cache name.') +param redisCacheName string + +@description('Optional. The name of the secondary Redis cache. If not provided, the primary Redis cache name is used.') +param name string = redisCacheName + +@description('Required. The resource ID of the linked server.') +param linkedRedisCacheResourceId string + +@description('Optional. The location of the linked server. If not provided, the location of the primary Redis cache is used.') +param linkedRedisCacheLocation string? + +@description('Optional. The role of the linked server. Possible values include: "Primary", "Secondary". Default value is "Secondary".') +param serverRole string = 'Secondary' + +resource redisCache 'Microsoft.Cache/redis@2024-03-01' existing = { + name: redisCacheName +} + +resource redisLinkedServer 'Microsoft.Cache/redis/linkedServers@2024-03-01' = { + name: name + properties: { + linkedRedisCacheId: linkedRedisCacheResourceId + linkedRedisCacheLocation: linkedRedisCacheLocation ?? redisCache.location + serverRole: serverRole + } + parent: redisCache +} + +@description('The name of the linkedServer resource.') +output name string = redisLinkedServer.name + +@description('The resource ID of the linkedServer.') +output resourceId string = redisLinkedServer.id + +@description('The hostname of the linkedServer.') +output geoReplicatedPrimaryHostName string = redisLinkedServer.properties.geoReplicatedPrimaryHostName + +@description('The resource group of the deployed linkedServer.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/cache/redis/linked-servers/main.json b/avm/res/cache/redis/linked-servers/main.json new file mode 100644 index 0000000000..5834cb3527 --- /dev/null +++ b/avm/res/cache/redis/linked-servers/main.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "16388556183951135369" + }, + "name": "Redis Cache Linked Servers", + "description": "This module connects a primary and secondary Redis Cache together for geo-replication.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "redisCacheName": { + "type": "string", + "metadata": { + "description": "Required. Primary Redis cache name." + } + }, + "name": { + "type": "string", + "defaultValue": "[parameters('redisCacheName')]", + "metadata": { + "description": "Optional. The name of the secondary Redis cache. If not provided, the primary Redis cache name is used." + } + }, + "linkedRedisCacheResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the linked server." + } + }, + "linkedRedisCacheLocation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location of the linked server. If not provided, the location of the primary Redis cache is used." + } + }, + "serverRole": { + "type": "string", + "defaultValue": "Secondary", + "metadata": { + "description": "Optional. The role of the linked server. Possible values include: \"Primary\", \"Secondary\". Default value is \"Secondary\"." + } + } + }, + "resources": { + "redisCache": { + "existing": true, + "type": "Microsoft.Cache/redis", + "apiVersion": "2024-03-01", + "name": "[parameters('redisCacheName')]" + }, + "redisLinkedServer": { + "type": "Microsoft.Cache/redis/linkedServers", + "apiVersion": "2024-03-01", + "name": "[format('{0}/{1}', parameters('redisCacheName'), parameters('name'))]", + "properties": { + "linkedRedisCacheId": "[parameters('linkedRedisCacheResourceId')]", + "linkedRedisCacheLocation": "[coalesce(parameters('linkedRedisCacheLocation'), reference('redisCache', '2024-03-01', 'full').location)]", + "serverRole": "[parameters('serverRole')]" + }, + "dependsOn": [ + "redisCache" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the linkedServer resource." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the linkedServer." + }, + "value": "[resourceId('Microsoft.Cache/redis/linkedServers', parameters('redisCacheName'), parameters('name'))]" + }, + "geoReplicatedPrimaryHostName": { + "type": "string", + "metadata": { + "description": "The hostname of the linkedServer." + }, + "value": "[reference('redisLinkedServer').geoReplicatedPrimaryHostName]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed linkedServer." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/cache/redis/main.bicep b/avm/res/cache/redis/main.bicep index 4610ccc91e..97973ae0fa 100644 --- a/avm/res/cache/redis/main.bicep +++ b/avm/res/cache/redis/main.bicep @@ -99,6 +99,9 @@ param zones int[] = [1, 2, 3] @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') param privateEndpoints privateEndpointType +@description('Optional. The geo-replication settings of the service. Requires a Premium SKU. Geo-replication is not supported on a cache with multiple replicas per primary. Secondary cache VM Size must be same or higher as compared to the primary cache VM Size. Geo-replication between a vnet and non vnet cache (and vice-a-versa) not supported.') +param geoReplicationObject object = {} + @description('Optional. The diagnostic settings of the service.') param diagnosticSettings diagnosticSettingType @@ -161,7 +164,7 @@ resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableT } } -resource redis 'Microsoft.Cache/redis@2023-08-01' = { +resource redis 'Microsoft.Cache/redis@2024-03-01' = { name: name location: location tags: tags @@ -302,6 +305,17 @@ module redis_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.4.1' } ] +module redis_geoReplication 'linked-servers/main.bicep' = if (!empty(geoReplicationObject)) { + name: '${uniqueString(deployment().name, location)}-redis-LinkedServer' + params: { + redisCacheName: redis.name + name: geoReplicationObject.name + linkedRedisCacheResourceId: geoReplicationObject.linkedRedisCacheResourceId + linkedRedisCacheLocation: geoReplicationObject.?linkedRedisCacheLocation + } + dependsOn: redis_privateEndpoints +} + @description('The name of the Redis Cache.') output name string = redis.name diff --git a/avm/res/cache/redis/main.json b/avm/res/cache/redis/main.json index 556aa0a3e5..94b1435f91 100644 --- a/avm/res/cache/redis/main.json +++ b/avm/res/cache/redis/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.28.1.47646", - "templateHash": "6982163037642901188" + "templateHash": "2290807334954408689" }, "name": "Redis Cache", "description": "This module deploys a Redis Cache.", @@ -633,6 +633,13 @@ "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } }, + "geoReplicationObject": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The geo-replication settings of the service. Requires a Premium SKU. Geo-replication is not supported on a cache with multiple replicas per primary. Secondary cache VM Size must be same or higher as compared to the primary cache VM Size. Geo-replication between a vnet and non vnet cache (and vice-a-versa) not supported." + } + }, "diagnosticSettings": { "$ref": "#/definitions/diagnosticSettingType", "metadata": { @@ -683,7 +690,7 @@ }, "redis": { "type": "Microsoft.Cache/redis", - "apiVersion": "2023-08-01", + "apiVersion": "2024-03-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", @@ -1446,6 +1453,137 @@ "dependsOn": [ "redis" ] + }, + "redis_geoReplication": { + "condition": "[not(empty(parameters('geoReplicationObject')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-redis-LinkedServer', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "redisCacheName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[parameters('geoReplicationObject').name]" + }, + "linkedRedisCacheResourceId": { + "value": "[parameters('geoReplicationObject').linkedRedisCacheResourceId]" + }, + "linkedRedisCacheLocation": { + "value": "[tryGet(parameters('geoReplicationObject'), 'linkedRedisCacheLocation')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "16388556183951135369" + }, + "name": "Redis Cache Linked Servers", + "description": "This module connects a primary and secondary Redis Cache together for geo-replication.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "redisCacheName": { + "type": "string", + "metadata": { + "description": "Required. Primary Redis cache name." + } + }, + "name": { + "type": "string", + "defaultValue": "[parameters('redisCacheName')]", + "metadata": { + "description": "Optional. The name of the secondary Redis cache. If not provided, the primary Redis cache name is used." + } + }, + "linkedRedisCacheResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the linked server." + } + }, + "linkedRedisCacheLocation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location of the linked server. If not provided, the location of the primary Redis cache is used." + } + }, + "serverRole": { + "type": "string", + "defaultValue": "Secondary", + "metadata": { + "description": "Optional. The role of the linked server. Possible values include: \"Primary\", \"Secondary\". Default value is \"Secondary\"." + } + } + }, + "resources": { + "redisCache": { + "existing": true, + "type": "Microsoft.Cache/redis", + "apiVersion": "2024-03-01", + "name": "[parameters('redisCacheName')]" + }, + "redisLinkedServer": { + "type": "Microsoft.Cache/redis/linkedServers", + "apiVersion": "2024-03-01", + "name": "[format('{0}/{1}', parameters('redisCacheName'), parameters('name'))]", + "properties": { + "linkedRedisCacheId": "[parameters('linkedRedisCacheResourceId')]", + "linkedRedisCacheLocation": "[coalesce(parameters('linkedRedisCacheLocation'), reference('redisCache', '2024-03-01', 'full').location)]", + "serverRole": "[parameters('serverRole')]" + }, + "dependsOn": [ + "redisCache" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the linkedServer resource." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the linkedServer." + }, + "value": "[resourceId('Microsoft.Cache/redis/linkedServers', parameters('redisCacheName'), parameters('name'))]" + }, + "geoReplicatedPrimaryHostName": { + "type": "string", + "metadata": { + "description": "The hostname of the linkedServer." + }, + "value": "[reference('redisLinkedServer').geoReplicatedPrimaryHostName]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed linkedServer." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "redis", + "redis_privateEndpoints" + ] } }, "outputs": { @@ -1496,14 +1634,14 @@ "metadata": { "description": "The principal ID of the system assigned identity." }, - "value": "[coalesce(tryGet(tryGet(reference('redis', '2023-08-01', 'full'), 'identity'), 'principalId'), '')]" + "value": "[coalesce(tryGet(tryGet(reference('redis', '2024-03-01', 'full'), 'identity'), 'principalId'), '')]" }, "location": { "type": "string", "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('redis', '2023-08-01', 'full').location]" + "value": "[reference('redis', '2024-03-01', 'full').location]" } } } \ No newline at end of file diff --git a/avm/res/cache/redis/tests/e2e/passive-geo-replication/dependencies1.bicep b/avm/res/cache/redis/tests/e2e/passive-geo-replication/dependencies1.bicep new file mode 100644 index 0000000000..b6d657fe00 --- /dev/null +++ b/avm/res/cache/redis/tests/e2e/passive-geo-replication/dependencies1.bicep @@ -0,0 +1,55 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the managed identity to create.') +param managedIdentityName string + +@description('Required. The name of the Deployment Script to create to get the paired region name.') +param pairedRegionScriptName string + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('msi-${location}-${managedIdentity.id}-Reader-RoleAssignment') + properties: { + principalId: managedIdentity.properties.principalId + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'acdd72a7-3385-48ef-bd42-f606fba81ae7' + ) // Reader + principalType: 'ServicePrincipal' + } +} + +resource getPairedRegionScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: pairedRegionScriptName + location: location + kind: 'AzurePowerShell' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${managedIdentity.id}': {} + } + } + properties: { + azPowerShellVersion: '8.0' + retentionInterval: 'P1D' + arguments: '-Location \\"${location}\\"' + scriptContent: loadTextContent('../../../../../../utilities/e2e-template-assets/scripts/Get-PairedRegion.ps1') + } + dependsOn: [ + roleAssignment + ] +} + +@description('The name of the paired region.') +output pairedRegionName string = getPairedRegionScript.properties.outputs.pairedRegionName + +@description('The resource ID of the created Managed Identity.') +output managedIdentityResourceId string = managedIdentity.id + +@description('The principal ID of the created managed identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId diff --git a/avm/res/cache/redis/tests/e2e/passive-geo-replication/dependencies2.bicep b/avm/res/cache/redis/tests/e2e/passive-geo-replication/dependencies2.bicep new file mode 100644 index 0000000000..57338f20af --- /dev/null +++ b/avm/res/cache/redis/tests/e2e/passive-geo-replication/dependencies2.bicep @@ -0,0 +1,32 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Redis Cache to create.') +param redisName string + +resource redis 'Microsoft.Cache/redis@2023-08-01' = { + name: redisName + location: location + properties: { + sku: { + capacity: 2 + family: 'P' + name: 'Premium' + } + enableNonSslPort: true + minimumTlsVersion: '1.2' + redisVersion: '6' + replicasPerMaster: 1 + replicasPerPrimary: 1 + shardCount: 1 + } +} + +@description('The resource ID of the created Redis Cache') +output redisResourceId string = redis.id + +@description('The location of the created Redis Cache') +output redisLocation string = redis.location + +@description('The name of the created Redis Cache') +output redisName string = redis.name diff --git a/avm/res/cache/redis/tests/e2e/passive-geo-replication/main.test.bicep b/avm/res/cache/redis/tests/e2e/passive-geo-replication/main.test.bicep new file mode 100644 index 0000000000..81e928e628 --- /dev/null +++ b/avm/res/cache/redis/tests/e2e/passive-geo-replication/main.test.bicep @@ -0,0 +1,85 @@ +targetScope = 'subscription' + +metadata name = 'Passive Geo-Replicated Redis Cache' +metadata description = 'This instance deploys the module with geo-replication enabled.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-cache.redis-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'crpgeo' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies1 'dependencies1.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies1' + params: { + location: resourceLocation + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + pairedRegionScriptName: 'dep-${namePrefix}-ds-${serviceShort}' + } +} + +module nestedDependencies2 'dependencies2.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies2' + params: { + location: nestedDependencies1.outputs.pairedRegionName + redisName: 'dep-${namePrefix}-redis-sec-${serviceShort}' + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}${serviceShort}001' + location: resourceLocation + capacity: 2 + enableNonSslPort: true + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + minimumTlsVersion: '1.2' + zoneRedundant: false + replicasPerPrimary: 1 + replicasPerMaster: 1 + geoReplicationObject: { + linkedRedisCacheResourceId: nestedDependencies2.outputs.redisResourceId + linkedRedisCacheLocation: nestedDependencies2.outputs.redisLocation + name: nestedDependencies2.outputs.redisName + } + redisVersion: '6' + shardCount: 1 + skuName: 'Premium' + } + } +] diff --git a/avm/res/data-factory/factory/README.md b/avm/res/data-factory/factory/README.md index fd87c1ee6e..68259c4e1e 100644 --- a/avm/res/data-factory/factory/README.md +++ b/avm/res/data-factory/factory/README.md @@ -20,6 +20,7 @@ This module deploys a Data Factory. | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | | `Microsoft.DataFactory/factories` | [2018-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.DataFactory/2018-06-01/factories) | | `Microsoft.DataFactory/factories/integrationRuntimes` | [2018-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.DataFactory/2018-06-01/factories/integrationRuntimes) | +| `Microsoft.DataFactory/factories/linkedservices` | [2018-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.DataFactory/2018-06-01/factories/linkedservices) | | `Microsoft.DataFactory/factories/managedVirtualNetworks` | [2018-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.DataFactory/2018-06-01/factories/managedVirtualNetworks) | | `Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints` | [2018-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.DataFactory/2018-06-01/factories/managedVirtualNetworks/managedPrivateEndpoints) | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | @@ -129,9 +130,13 @@ module factory 'br/public:avm/res/data-factory/factory:' = { } } integrationRuntimes: [ + { + name: 'TestRuntime' + type: 'SelfHosted' + } { managedVirtualNetworkName: 'default' - name: 'AutoResolveIntegrationRuntime' + name: 'IRvnetManaged' type: 'Managed' typeProperties: { computeProperties: { @@ -139,9 +144,29 @@ module factory 'br/public:avm/res/data-factory/factory:' = { } } } + ] + linkedServices: [ { - name: 'TestRuntime' - type: 'SelfHosted' + name: 'SQLdbLinkedservice' + type: 'AzureSQLDatabase' + typeProperties: { + connectionString: '' + } + } + { + description: 'This is a description for the linked service using the IRvnetManaged integration runtime.' + integrationRuntimeName: 'IRvnetManaged' + name: 'LakeStoreLinkedservice' + parameters: { + storageAccountName: { + defaultValue: 'madeupstorageaccname' + type: 'String' + } + } + type: 'AzureBlobFS' + typeProperties: { + url: '@{concat(\'https://\', linkedService().storageAccountName, \'.dfs.core.windows.net\')}' + } } ] location: '' @@ -263,19 +288,45 @@ module factory 'br/public:avm/res/data-factory/factory:' = { }, "integrationRuntimes": { "value": [ + { + "name": "TestRuntime", + "type": "SelfHosted" + }, { "managedVirtualNetworkName": "default", - "name": "AutoResolveIntegrationRuntime", + "name": "IRvnetManaged", "type": "Managed", "typeProperties": { "computeProperties": { "location": "AutoResolve" } } + } + ] + }, + "linkedServices": { + "value": [ + { + "name": "SQLdbLinkedservice", + "type": "AzureSQLDatabase", + "typeProperties": { + "connectionString": "" + } }, { - "name": "TestRuntime", - "type": "SelfHosted" + "description": "This is a description for the linked service using the IRvnetManaged integration runtime.", + "integrationRuntimeName": "IRvnetManaged", + "name": "LakeStoreLinkedservice", + "parameters": { + "storageAccountName": { + "defaultValue": "madeupstorageaccname", + "type": "String" + } + }, + "type": "AzureBlobFS", + "typeProperties": { + "url": "@{concat(\"https://\", linkedService().storageAccountName, \".dfs.core.windows.net\")}" + } } ] }, @@ -489,6 +540,7 @@ module factory 'br/public:avm/res/data-factory/factory:' = { | [`gitTenantId`](#parameter-gittenantid) | string | Add the tenantId of your Azure subscription. | | [`globalParameters`](#parameter-globalparameters) | object | List of Global Parameters for the factory. | | [`integrationRuntimes`](#parameter-integrationruntimes) | array | An array of objects for the configuration of an Integration Runtime. | +| [`linkedServices`](#parameter-linkedservices) | array | An array of objects for the configuration of Linked Services. | | [`location`](#parameter-location) | string | Location for all Resources. | | [`lock`](#parameter-lock) | object | The lock settings of the service. | | [`managedIdentities`](#parameter-managedidentities) | object | The managed identity definition for this resource. | @@ -813,6 +865,14 @@ An array of objects for the configuration of an Integration Runtime. - Type: array - Default: `[]` +### Parameter: `linkedServices` + +An array of objects for the configuration of Linked Services. + +- Required: No +- Type: array +- Default: `[]` + ### Parameter: `location` Location for all Resources. diff --git a/avm/res/data-factory/factory/linked-service/README.md b/avm/res/data-factory/factory/linked-service/README.md new file mode 100644 index 0000000000..15a044aa3a --- /dev/null +++ b/avm/res/data-factory/factory/linked-service/README.md @@ -0,0 +1,111 @@ +# Data Factory Linked Service `[Microsoft.DataFactory/factories/linkedservices]` + +This module deploys a Data Factory Linked Service. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.DataFactory/factories/linkedservices` | [2018-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.DataFactory/2018-06-01/factories/linkedservices) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the Linked Service. | +| [`type`](#parameter-type) | string | The type of Linked Service. See https://learn.microsoft.com/en-us/azure/templates/microsoft.datafactory/factories/linkedservices?pivots=deployment-language-bicep#linkedservice-objects for more information. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`dataFactoryName`](#parameter-datafactoryname) | string | The name of the parent Azure Data Factory. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`description`](#parameter-description) | string | The description of the Integration Runtime. | +| [`integrationRuntimeName`](#parameter-integrationruntimename) | string | The name of the Integration Runtime to use. | +| [`parameters`](#parameter-parameters) | object | Use this to add parameters for a linked service connection string. | +| [`typeProperties`](#parameter-typeproperties) | object | Used to add connection properties for your linked services. | + +### Parameter: `name` + +The name of the Linked Service. + +- Required: Yes +- Type: string + +### Parameter: `type` + +The type of Linked Service. See https://learn.microsoft.com/en-us/azure/templates/microsoft.datafactory/factories/linkedservices?pivots=deployment-language-bicep#linkedservice-objects for more information. + +- Required: Yes +- Type: string + +### Parameter: `dataFactoryName` + +The name of the parent Azure Data Factory. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + +### Parameter: `description` + +The description of the Integration Runtime. + +- Required: No +- Type: string +- Default: `'Linked Service created by avm-res-datafactory-factories'` + +### Parameter: `integrationRuntimeName` + +The name of the Integration Runtime to use. + +- Required: No +- Type: string +- Default: `'none'` + +### Parameter: `parameters` + +Use this to add parameters for a linked service connection string. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `typeProperties` + +Used to add connection properties for your linked services. + +- Required: No +- Type: object +- Default: `{}` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the Linked Service. | +| `resourceGroupName` | string | The name of the Resource Group the Linked Service was created in. | +| `resourceId` | string | The resource ID of the Linked Service. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/data-factory/factory/linked-service/main.bicep b/avm/res/data-factory/factory/linked-service/main.bicep new file mode 100644 index 0000000000..af51a01544 --- /dev/null +++ b/avm/res/data-factory/factory/linked-service/main.bicep @@ -0,0 +1,57 @@ +metadata name = 'Data Factory Linked Service' +metadata description = 'This module deploys a Data Factory Linked Service.' +metadata owner = 'Azure/module-maintainers' + +@sys.description('Conditional. The name of the parent Azure Data Factory. Required if the template is used in a standalone deployment.') +param dataFactoryName string + +@sys.description('Required. The name of the Linked Service.') +param name string + +@sys.description('Required. The type of Linked Service. See https://learn.microsoft.com/en-us/azure/templates/microsoft.datafactory/factories/linkedservices?pivots=deployment-language-bicep#linkedservice-objects for more information.') +param type string + +@sys.description('Optional. Used to add connection properties for your linked services.') +param typeProperties object = {} + +@sys.description('Optional. The name of the Integration Runtime to use.') +param integrationRuntimeName string = 'none' + +@sys.description('Optional. Use this to add parameters for a linked service connection string.') +param parameters object = {} + +@sys.description('Optional. The description of the Integration Runtime.') +param description string = 'Linked Service created by avm-res-datafactory-factories' + +resource dataFactory 'Microsoft.DataFactory/factories@2018-06-01' existing = { + name: dataFactoryName +} + +resource linkedService 'Microsoft.DataFactory/factories/linkedservices@2018-06-01' = { + name: name + parent: dataFactory + + properties: { + annotations: [] + description: description + connectVia: contains(integrationRuntimeName, 'none') + ? null + : { + parameters: {} + referenceName: integrationRuntimeName + type: 'IntegrationRuntimeReference' + } + type: type + typeProperties: typeProperties + parameters: parameters + } +} + +@sys.description('The name of the Resource Group the Linked Service was created in.') +output resourceGroupName string = resourceGroup().name + +@sys.description('The name of the Linked Service.') +output name string = linkedService.name + +@sys.description('The resource ID of the Linked Service.') +output resourceId string = linkedService.id diff --git a/avm/res/data-factory/factory/linked-service/main.json b/avm/res/data-factory/factory/linked-service/main.json new file mode 100644 index 0000000000..c66b5b07a6 --- /dev/null +++ b/avm/res/data-factory/factory/linked-service/main.json @@ -0,0 +1,100 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "6177259720192070721" + }, + "name": "Data Factory Linked Service", + "description": "This module deploys a Data Factory Linked Service.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Azure Data Factory. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Linked Service." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. The type of Linked Service. See https://learn.microsoft.com/en-us/azure/templates/microsoft.datafactory/factories/linkedservices?pivots=deployment-language-bicep#linkedservice-objects for more information." + } + }, + "typeProperties": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Used to add connection properties for your linked services." + } + }, + "integrationRuntimeName": { + "type": "string", + "defaultValue": "none", + "metadata": { + "description": "Optional. The name of the Integration Runtime to use." + } + }, + "parameters": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Use this to add parameters for a linked service connection string." + } + }, + "description": { + "type": "string", + "defaultValue": "Linked Service created by avm-res-datafactory-factories", + "metadata": { + "description": "Optional. The description of the Integration Runtime." + } + } + }, + "resources": [ + { + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('name'))]", + "properties": { + "annotations": [], + "description": "[parameters('description')]", + "connectVia": "[if(contains(parameters('integrationRuntimeName'), 'none'), null(), createObject('parameters', createObject(), 'referenceName', parameters('integrationRuntimeName'), 'type', 'IntegrationRuntimeReference'))]", + "type": "[parameters('type')]", + "typeProperties": "[parameters('typeProperties')]", + "parameters": "[parameters('parameters')]" + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the Linked Service was created in." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the Linked Service." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Linked Service." + }, + "value": "[resourceId('Microsoft.DataFactory/factories/linkedservices', parameters('dataFactoryName'), parameters('name'))]" + } + } +} \ No newline at end of file diff --git a/avm/res/data-factory/factory/main.bicep b/avm/res/data-factory/factory/main.bicep index 1e86f34d5d..57d184e8df 100644 --- a/avm/res/data-factory/factory/main.bicep +++ b/avm/res/data-factory/factory/main.bicep @@ -14,6 +14,9 @@ param managedPrivateEndpoints array = [] @description('Optional. An array of objects for the configuration of an Integration Runtime.') param integrationRuntimes array = [] +@description('Optional. An array of objects for the configuration of Linked Services.') +param linkedServices array = [] + @description('Optional. Location for all Resources.') param location string = resourceGroup().location @@ -233,6 +236,21 @@ module dataFactory_integrationRuntimes 'integration-runtime/main.bicep' = [ } ] +module dataFactory_linkedServices 'linked-service/main.bicep' = [ + for (linkedService, index) in linkedServices: { + name: '${uniqueString(deployment().name, location)}-DataFactory-LinkedServices-${index}' + params: { + dataFactoryName: dataFactory.name + name: linkedService.name + type: linkedService.type + typeProperties: linkedService.?typeProperties + integrationRuntimeName: linkedService.?integrationRuntimeName + parameters: linkedService.?parameters + description: linkedService.?description + } + } +] + resource dataFactory_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { name: lock.?name ?? 'lock-${name}' properties: { diff --git a/avm/res/data-factory/factory/main.json b/avm/res/data-factory/factory/main.json index 52222706f7..2718e401bb 100644 --- a/avm/res/data-factory/factory/main.json +++ b/avm/res/data-factory/factory/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.28.1.47646", - "templateHash": "17403645790981641417" + "templateHash": "14871862405495060994" }, "name": "Data Factories", "description": "This module deploys a Data Factory.", @@ -505,6 +505,13 @@ "description": "Optional. An array of objects for the configuration of an Integration Runtime." } }, + "linkedServices": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. An array of objects for the configuration of Linked Services." + } + }, "location": { "type": "string", "defaultValue": "[resourceGroup().location]", @@ -1162,6 +1169,147 @@ "dataFactory_managedVirtualNetwork" ] }, + "dataFactory_linkedServices": { + "copy": { + "name": "dataFactory_linkedServices", + "count": "[length(parameters('linkedServices'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-DataFactory-LinkedServices-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[parameters('linkedServices')[copyIndex()].name]" + }, + "type": { + "value": "[parameters('linkedServices')[copyIndex()].type]" + }, + "typeProperties": { + "value": "[tryGet(parameters('linkedServices')[copyIndex()], 'typeProperties')]" + }, + "integrationRuntimeName": { + "value": "[tryGet(parameters('linkedServices')[copyIndex()], 'integrationRuntimeName')]" + }, + "parameters": { + "value": "[tryGet(parameters('linkedServices')[copyIndex()], 'parameters')]" + }, + "description": { + "value": "[tryGet(parameters('linkedServices')[copyIndex()], 'description')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "6177259720192070721" + }, + "name": "Data Factory Linked Service", + "description": "This module deploys a Data Factory Linked Service.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Azure Data Factory. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Linked Service." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. The type of Linked Service. See https://learn.microsoft.com/en-us/azure/templates/microsoft.datafactory/factories/linkedservices?pivots=deployment-language-bicep#linkedservice-objects for more information." + } + }, + "typeProperties": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Used to add connection properties for your linked services." + } + }, + "integrationRuntimeName": { + "type": "string", + "defaultValue": "none", + "metadata": { + "description": "Optional. The name of the Integration Runtime to use." + } + }, + "parameters": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Use this to add parameters for a linked service connection string." + } + }, + "description": { + "type": "string", + "defaultValue": "Linked Service created by avm-res-datafactory-factories", + "metadata": { + "description": "Optional. The description of the Integration Runtime." + } + } + }, + "resources": [ + { + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('name'))]", + "properties": { + "annotations": [], + "description": "[parameters('description')]", + "connectVia": "[if(contains(parameters('integrationRuntimeName'), 'none'), null(), createObject('parameters', createObject(), 'referenceName', parameters('integrationRuntimeName'), 'type', 'IntegrationRuntimeReference'))]", + "type": "[parameters('type')]", + "typeProperties": "[parameters('typeProperties')]", + "parameters": "[parameters('parameters')]" + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the Linked Service was created in." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the Linked Service." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Linked Service." + }, + "value": "[resourceId('Microsoft.DataFactory/factories/linkedservices', parameters('dataFactoryName'), parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "dataFactory" + ] + }, "dataFactory_privateEndpoints": { "copy": { "name": "dataFactory_privateEndpoints", diff --git a/avm/res/data-factory/factory/tests/e2e/max/main.test.bicep b/avm/res/data-factory/factory/tests/e2e/max/main.test.bicep index 7eeb7382b7..9dae35b49c 100644 --- a/avm/res/data-factory/factory/tests/e2e/max/main.test.bicep +++ b/avm/res/data-factory/factory/tests/e2e/max/main.test.bicep @@ -96,9 +96,15 @@ module testDeployment '../../../main.bicep' = [ } } integrationRuntimes: [ + // The AutoResolveIntegrationRuntime is the default integration runtime and does not need to be defined. It is not a virtual network managed integration runtime by default { + name: 'TestRuntime' + type: 'SelfHosted' + } + { + // Creating a second integration runtime with a custom name and has a managed virtual network. The name can be anything, but it must be unique within the data factory. We will connect a linked service to this integration runtime. managedVirtualNetworkName: 'default' - name: 'AutoResolveIntegrationRuntime' + name: 'IRvnetManaged' type: 'Managed' typeProperties: { computeProperties: { @@ -106,10 +112,32 @@ module testDeployment '../../../main.bicep' = [ } } } - + ] + linkedServices: [ { - name: 'TestRuntime' - type: 'SelfHosted' + // This will connect to the AutoResolveIntegrationRuntime as it does not have a specific integration runtime defined and by default uses the AutoResolveIntegrationRuntime + name: 'SQLdbLinkedservice' + type: 'AzureSQLDatabase' + typeProperties: { + // An example of a connection string to an Azure SQL Database + connectionString: 'integrated security=False;encrypt=True;connection timeout=30;data source=mydatabase.${environment().suffixes.sqlServerHostname};initial catalog=mydatabase;user id=myuser' + } + } + { + // This will connect to the IRvnetManaged integration runtime as it is specifically defined + name: 'LakeStoreLinkedservice' + integrationRuntimeName: 'IRvnetManaged' + description: 'This is a description for the linked service using the IRvnetManaged integration runtime.' + parameters: { + storageAccountName: { + type: 'String' + defaultValue: 'madeupstorageaccname' + } + } + type: 'AzureBlobFS' + typeProperties: { + url: '@{concat(\'https://\', linkedService().storageAccountName, \'.dfs.core.windows.net\')}' + } } ] lock: { diff --git a/avm/res/insights/data-collection-endpoint/README.md b/avm/res/insights/data-collection-endpoint/README.md index c4a647bdfb..dccb01614e 100644 --- a/avm/res/insights/data-collection-endpoint/README.md +++ b/avm/res/insights/data-collection-endpoint/README.md @@ -17,7 +17,7 @@ This module deploys a Data Collection Endpoint. | :-- | :-- | | `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | -| `Microsoft.Insights/dataCollectionEndpoints` | [2021-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-04-01/dataCollectionEndpoints) | +| `Microsoft.Insights/dataCollectionEndpoints` | [2023-03-11](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/dataCollectionEndpoints) | ## Usage examples @@ -95,6 +95,7 @@ module dataCollectionEndpoint 'br/public:avm/res/insights/data-collection-endpoi // Required parameters name: 'idcemax001' // Non-required parameters + description: 'This is a test data collection endpoint.' kind: 'Windows' location: '' lock: { @@ -145,6 +146,9 @@ module dataCollectionEndpoint 'br/public:avm/res/insights/data-collection-endpoi "value": "idcemax001" }, // Non-required parameters + "description": { + "value": "This is a test data collection endpoint." + }, "kind": { "value": "Windows" }, @@ -274,6 +278,7 @@ module dataCollectionEndpoint 'br/public:avm/res/insights/data-collection-endpoi | Parameter | Type | Description | | :-- | :-- | :-- | +| [`description`](#parameter-description) | string | Description of the data collection endpoint. | | [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | | [`kind`](#parameter-kind) | string | The kind of the resource. | | [`location`](#parameter-location) | string | Location for all Resources. | @@ -289,6 +294,13 @@ The name of the data collection endpoint. The name is case insensitive. - Required: Yes - Type: string +### Parameter: `description` + +Description of the data collection endpoint. + +- Required: No +- Type: string + ### Parameter: `enableTelemetry` Enable/Disable usage telemetry for module. diff --git a/avm/res/insights/data-collection-endpoint/main.bicep b/avm/res/insights/data-collection-endpoint/main.bicep index 2c2ac200d4..876d09233e 100644 --- a/avm/res/insights/data-collection-endpoint/main.bicep +++ b/avm/res/insights/data-collection-endpoint/main.bicep @@ -6,36 +6,39 @@ metadata owner = 'Azure/module-maintainers' // Parameters // // ============== // -@description('Required. The name of the data collection endpoint. The name is case insensitive.') +@sys.description('Required. The name of the data collection endpoint. The name is case insensitive.') param name string -@description('Optional. Enable/Disable usage telemetry for module.') +@sys.description('Optional. Description of the data collection endpoint.') +param description string? + +@sys.description('Optional. Enable/Disable usage telemetry for module.') param enableTelemetry bool = true -@description('Optional. The kind of the resource.') +@sys.description('Optional. The kind of the resource.') @allowed([ 'Linux' 'Windows' ]) param kind string = 'Linux' -@description('Optional. Location for all Resources.') +@sys.description('Optional. Location for all Resources.') param location string = resourceGroup().location -@description('Optional. The lock settings of the service.') +@sys.description('Optional. The lock settings of the service.') param lock lockType -@description('Optional. Array of role assignments to create.') +@sys.description('Optional. Array of role assignments to create.') param roleAssignments roleAssignmentType -@description('Optional. The configuration to set whether network access from public internet to the endpoints are allowed.') +@sys.description('Optional. The configuration to set whether network access from public internet to the endpoints are allowed.') @allowed([ 'Enabled' 'Disabled' ]) param publicNetworkAccess string = 'Disabled' -@description('Optional. Resource tags.') +@sys.description('Optional. Resource tags.') param tags object? var builtInRoleNames = { @@ -75,7 +78,7 @@ resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableT } } -resource dataCollectionEndpoint 'Microsoft.Insights/dataCollectionEndpoints@2021-04-01' = { +resource dataCollectionEndpoint 'Microsoft.Insights/dataCollectionEndpoints@2023-03-11' = { kind: kind location: location name: name @@ -84,6 +87,7 @@ resource dataCollectionEndpoint 'Microsoft.Insights/dataCollectionEndpoints@2021 networkAcls: { publicNetworkAccess: publicNetworkAccess } + description: description } } @@ -122,16 +126,16 @@ resource dataCollectionEndpoint_roleAssignments 'Microsoft.Authorization/roleAss // Outputs // // =========== // -@description('The name of the dataCollectionEndpoint.') +@sys.description('The name of the dataCollectionEndpoint.') output name string = dataCollectionEndpoint.name -@description('The resource ID of the dataCollectionEndpoint.') +@sys.description('The resource ID of the dataCollectionEndpoint.') output resourceId string = dataCollectionEndpoint.id -@description('The name of the resource group the dataCollectionEndpoint was created in.') +@sys.description('The name of the resource group the dataCollectionEndpoint was created in.') output resourceGroupName string = resourceGroup().name -@description('The location the resource was deployed into.') +@sys.description('The location the resource was deployed into.') output location string = dataCollectionEndpoint.location // =============== // @@ -139,32 +143,32 @@ output location string = dataCollectionEndpoint.location // =============== // type lockType = { - @description('Optional. Specify the name of lock.') + @sys.description('Optional. Specify the name of lock.') name: string? - @description('Optional. Specify the type of lock.') + @sys.description('Optional. Specify the type of lock.') kind: ('CanNotDelete' | 'ReadOnly' | 'None')? }? type roleAssignmentType = { - @description('Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') + @sys.description('Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') roleDefinitionIdOrName: string - @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + @sys.description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') principalId: string - @description('Optional. The principal type of the assigned principal ID.') + @sys.description('Optional. The principal type of the assigned principal ID.') principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device')? - @description('Optional. The description of the role assignment.') + @sys.description('Optional. The description of the role assignment.') description: string? - @description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container".') + @sys.description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container".') condition: string? - @description('Optional. Version of the condition.') + @sys.description('Optional. Version of the condition.') conditionVersion: '2.0'? - @description('Optional. The Resource Id of the delegated managed identity resource.') + @sys.description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? diff --git a/avm/res/insights/data-collection-endpoint/main.json b/avm/res/insights/data-collection-endpoint/main.json index 0a3bd96662..c6efa6dee5 100644 --- a/avm/res/insights/data-collection-endpoint/main.json +++ b/avm/res/insights/data-collection-endpoint/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.28.1.47646", - "templateHash": "12831608682693267101" + "templateHash": "4485952202932766854" }, "name": "Data Collection Endpoints", "description": "This module deploys a Data Collection Endpoint.", @@ -112,6 +112,13 @@ "description": "Required. The name of the data collection endpoint. The name is case insensitive." } }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Description of the data collection endpoint." + } + }, "enableTelemetry": { "type": "bool", "defaultValue": true, @@ -200,7 +207,7 @@ }, "dataCollectionEndpoint": { "type": "Microsoft.Insights/dataCollectionEndpoints", - "apiVersion": "2021-04-01", + "apiVersion": "2023-03-11", "name": "[parameters('name')]", "kind": "[parameters('kind')]", "location": "[parameters('location')]", @@ -208,7 +215,8 @@ "properties": { "networkAcls": { "publicNetworkAccess": "[parameters('publicNetworkAccess')]" - } + }, + "description": "[parameters('description')]" } }, "dataCollectionEndpoint_lock": { @@ -275,7 +283,7 @@ "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('dataCollectionEndpoint', '2021-04-01', 'full').location]" + "value": "[reference('dataCollectionEndpoint', '2023-03-11', 'full').location]" } } } \ No newline at end of file diff --git a/avm/res/insights/data-collection-endpoint/tests/e2e/defaults/main.test.bicep b/avm/res/insights/data-collection-endpoint/tests/e2e/defaults/main.test.bicep index cdf5ac6d3a..81f6756b6c 100644 --- a/avm/res/insights/data-collection-endpoint/tests/e2e/defaults/main.test.bicep +++ b/avm/res/insights/data-collection-endpoint/tests/e2e/defaults/main.test.bicep @@ -25,7 +25,7 @@ param namePrefix string = '#_namePrefix_#' // General resources // ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { +resource resourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: resourceGroupName location: resourceLocation } diff --git a/avm/res/insights/data-collection-endpoint/tests/e2e/max/dependencies.bicep b/avm/res/insights/data-collection-endpoint/tests/e2e/max/dependencies.bicep index d16e1031b1..a51da4e725 100644 --- a/avm/res/insights/data-collection-endpoint/tests/e2e/max/dependencies.bicep +++ b/avm/res/insights/data-collection-endpoint/tests/e2e/max/dependencies.bicep @@ -4,7 +4,7 @@ param location string = resourceGroup().location @description('Required. The name of the managed identity to create.') param managedIdentityName string -resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { name: managedIdentityName location: location } diff --git a/avm/res/insights/data-collection-endpoint/tests/e2e/max/main.test.bicep b/avm/res/insights/data-collection-endpoint/tests/e2e/max/main.test.bicep index 2089ac4f21..0a20a8aeea 100644 --- a/avm/res/insights/data-collection-endpoint/tests/e2e/max/main.test.bicep +++ b/avm/res/insights/data-collection-endpoint/tests/e2e/max/main.test.bicep @@ -25,7 +25,7 @@ param namePrefix string = '#_namePrefix_#' // General resources // ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { +resource resourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: resourceGroupName location: resourceLocation } @@ -50,6 +50,7 @@ module testDeployment '../../../main.bicep' = [ name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' params: { name: '${namePrefix}${serviceShort}001' + description: 'This is a test data collection endpoint.' location: resourceLocation publicNetworkAccess: 'Enabled' kind: 'Windows' diff --git a/avm/res/insights/data-collection-endpoint/tests/e2e/waf-aligned/main.test.bicep b/avm/res/insights/data-collection-endpoint/tests/e2e/waf-aligned/main.test.bicep index e421f1282a..67b76b77d9 100644 --- a/avm/res/insights/data-collection-endpoint/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/insights/data-collection-endpoint/tests/e2e/waf-aligned/main.test.bicep @@ -25,7 +25,7 @@ param namePrefix string = '#_namePrefix_#' // General resources // ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { +resource resourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: resourceGroupName location: resourceLocation } diff --git a/avm/res/insights/data-collection-endpoint/version.json b/avm/res/insights/data-collection-endpoint/version.json index 83083db694..1c035df49f 100644 --- a/avm/res/insights/data-collection-endpoint/version.json +++ b/avm/res/insights/data-collection-endpoint/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.1", + "version": "0.2", "pathFilters": [ "./main.json" ] diff --git a/avm/res/machine-learning-services/workspace/README.md b/avm/res/machine-learning-services/workspace/README.md index d9acdd7f55..94948ea6ab 100644 --- a/avm/res/machine-learning-services/workspace/README.md +++ b/avm/res/machine-learning-services/workspace/README.md @@ -19,7 +19,7 @@ This module deploys a Machine Learning Services Workspace. | `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | -| `Microsoft.MachineLearningServices/workspaces` | [2024-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.MachineLearningServices/workspaces) | +| `Microsoft.MachineLearningServices/workspaces` | [2024-04-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.MachineLearningServices/workspaces) | | `Microsoft.MachineLearningServices/workspaces/computes` | [2022-10-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.MachineLearningServices/2022-10-01/workspaces/computes) | | `Microsoft.Network/privateEndpoints` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints) | | `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints/privateDnsZoneGroups) | @@ -61,6 +61,10 @@ module workspace 'br/public:avm/res/machine-learning-services/workspace:' kind: 'Hub' location: '' + workspaceHubConfig: { + additionalWorkspaceStorageAccounts: '' + defaultWorkspaceResourceGroup: '' + } } } ``` @@ -99,6 +103,12 @@ module workspace 'br/public:avm/res/machine-learning-services/workspace:" + }, + "workspaceHubConfig": { + "value": { + "additionalWorkspaceStorageAccounts": "", + "defaultWorkspaceResourceGroup": "" + } } } } @@ -203,6 +213,19 @@ module workspace 'br/public:avm/res/machine-learning-services/workspace:' ] } + managedNetworkSettings: { + isolationMode: 'AllowInternetOutbound' + outboundRules: { + rule: { + category: 'UserDefined' + destination: { + serviceResourceId: '' + subresourceTarget: 'blob' + } + type: 'PrivateEndpoint' + } + } + } primaryUserAssignedIdentity: '' } } @@ -255,6 +278,21 @@ module workspace 'br/public:avm/res/machine-learning-services/workspace:", + "subresourceTarget": "blob" + }, + "type": "PrivateEndpoint" + } + } + } + }, "primaryUserAssignedIdentity": { "value": "" } @@ -424,6 +462,9 @@ module workspace 'br/public:avm/res/machine-learning-services/workspace:' ] } + managedNetworkSettings: { + isolationMode: 'Disabled' + } primaryUserAssignedIdentity: '' privateEndpoints: [ { @@ -461,6 +502,11 @@ module workspace 'br/public:avm/res/machine-learning-services/workspace:' } ] + serverlessComputeSettings: { + serverlessComputeCustomSubnet: '' + serverlessComputeNoPublicIP: true + } + systemDatastoresAuthMode: 'accessKey' tags: { Environment: 'Non-Prod' 'hidden-title': 'This is visible in the resource name' @@ -576,6 +622,11 @@ module workspace 'br/public:avm/res/machine-learning-services/workspace:" }, @@ -619,6 +670,15 @@ module workspace 'br/public:avm/res/machine-learning-services/workspace:", + "serverlessComputeNoPublicIP": true + } + }, + "systemDatastoresAuthMode": { + "value": "accessKey" + }, "tags": { "value": { "Environment": "Non-Prod", @@ -662,6 +722,34 @@ module workspace 'br/public:avm/res/machine-learning-services/workspace:' + managedNetworkSettings: { + isolationMode: 'AllowOnlyApprovedOutbound' + outboundRules: { + rule1: { + category: 'UserDefined' + destination: { + serviceResourceId: '' + sparkEnabled: true + subresourceTarget: 'blob' + } + type: 'PrivateEndpoint' + } + rule2: { + category: 'UserDefined' + destination: 'pypi.org' + type: 'FQDN' + } + rule3: { + category: 'UserDefined' + destination: { + portRanges: '80,443' + protocol: 'TCP' + serviceTag: 'AppService' + } + type: 'ServiceTag' + } + } + } privateEndpoints: [ { privateDnsZoneResourceIds: [ @@ -675,6 +763,7 @@ module workspace 'br/public:avm/res/machine-learning-services/workspace:" }, + "managedNetworkSettings": { + "value": { + "isolationMode": "AllowOnlyApprovedOutbound", + "outboundRules": { + "rule1": { + "category": "UserDefined", + "destination": { + "serviceResourceId": "", + "sparkEnabled": true, + "subresourceTarget": "blob" + }, + "type": "PrivateEndpoint" + }, + "rule2": { + "category": "UserDefined", + "destination": "pypi.org", + "type": "FQDN" + }, + "rule3": { + "category": "UserDefined", + "destination": { + "portRanges": "80,443", + "protocol": "TCP", + "serviceTag": "AppService" + }, + "type": "ServiceTag" + } + } + } + }, "privateEndpoints": { "value": [ { @@ -741,6 +860,9 @@ module workspace 'br/public:avm/res/machine-learning-services/workspace:Any_other_property<`](#parameter-managednetworksettingsoutboundrules>any_other_property<) | object | The outbound rule. The name of the rule is the object key. | + +### Parameter: `managedNetworkSettings.outboundRules.>Any_other_property<` + +The outbound rule. The name of the rule is the object key. + +- Required: Yes +- Type: object + ### Parameter: `privateEndpoints` Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. @@ -1602,10 +1773,11 @@ Tags to be applied on all resources/resource groups in this deployment. ### Parameter: `publicNetworkAccess` -Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set. +Whether or not public network access is allowed for this resource. For security reasons it should be disabled. - Required: No - Type: string +- Default: `'Disabled'` - Allowed: ```Bicep [ @@ -1703,6 +1875,34 @@ The principal type of the assigned principal ID. ] ``` +### Parameter: `serverlessComputeSettings` + +Settings for serverless compute created in the workspace. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`serverlessComputeCustomSubnet`](#parameter-serverlesscomputesettingsserverlesscomputecustomsubnet) | string | The resource ID of an existing virtual network subnet in which serverless compute nodes should be deployed. | +| [`serverlessComputeNoPublicIP`](#parameter-serverlesscomputesettingsserverlesscomputenopublicip) | bool | The flag to signal if serverless compute nodes deployed in custom vNet would have no public IP addresses for a workspace with private endpoint. | + +### Parameter: `serverlessComputeSettings.serverlessComputeCustomSubnet` + +The resource ID of an existing virtual network subnet in which serverless compute nodes should be deployed. + +- Required: No +- Type: string + +### Parameter: `serverlessComputeSettings.serverlessComputeNoPublicIP` + +The flag to signal if serverless compute nodes deployed in custom vNet would have no public IP addresses for a workspace with private endpoint. + +- Required: No +- Type: bool + ### Parameter: `serviceManagedResourcesSettings` The service managed resource settings. @@ -1717,6 +1917,20 @@ The list of shared private link resources in this workspace. Note: This property - Required: No - Type: array +### Parameter: `systemDatastoresAuthMode` + +The authentication mode used by the workspace when connecting to the default storage account. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'accessKey' + 'identity' + ] + ``` + ### Parameter: `tags` Resource tags. @@ -1724,6 +1938,34 @@ Resource tags. - Required: No - Type: object +### Parameter: `workspaceHubConfig` + +Configuration for workspace hub settings. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`additionalWorkspaceStorageAccounts`](#parameter-workspacehubconfigadditionalworkspacestorageaccounts) | array | The resource IDs of additional storage accounts to attach to the workspace. | +| [`defaultWorkspaceResourceGroup`](#parameter-workspacehubconfigdefaultworkspaceresourcegroup) | string | The resource ID of the default resource group for projects created in the workspace hub. | + +### Parameter: `workspaceHubConfig.additionalWorkspaceStorageAccounts` + +The resource IDs of additional storage accounts to attach to the workspace. + +- Required: No +- Type: array + +### Parameter: `workspaceHubConfig.defaultWorkspaceResourceGroup` + +The resource ID of the default resource group for projects created in the workspace hub. + +- Required: No +- Type: string + ## Outputs diff --git a/avm/res/machine-learning-services/workspace/main.bicep b/avm/res/machine-learning-services/workspace/main.bicep index 6b7275b49c..75a53dc8cc 100644 --- a/avm/res/machine-learning-services/workspace/main.bicep +++ b/avm/res/machine-learning-services/workspace/main.bicep @@ -47,9 +47,6 @@ param lock lockType @sys.description('Optional. The flag to signal HBI data in the workspace and reduce diagnostic data collected by the service.') param hbiWorkspace bool = false -@sys.description('Optional. The flag to indicate whether to allow public access when behind VNet.') -param allowPublicAccessWhenBehindVnet bool = false - @sys.description('Conditional. The resource ID of the hub to associate with the workspace. Required if \'kind\' is set to \'Project\'.') param hubResourceId string? @@ -76,6 +73,22 @@ param managedIdentities managedIdentitiesType = { @sys.description('Conditional. Settings for feature store type workspaces. Required if \'kind\' is set to \'FeatureStore\'.') param featureStoreSettings featureStoreSettingType +@sys.description('Optional. Managed Network settings for a machine learning workspace.') +param managedNetworkSettings managedNetworkSettingType + +@sys.description('Optional. Settings for serverless compute created in the workspace.') +param serverlessComputeSettings serverlessComputeSettingType + +@sys.description('Optional. The authentication mode used by the workspace when connecting to the default storage account.') +@allowed([ + 'accessKey' + 'identity' +]) +param systemDatastoresAuthMode string? + +@sys.description('Optional. Configuration for workspace hub settings.') +param workspaceHubConfig workspaceHubConfigType + // Diagnostic Settings @sys.description('Optional. The diagnostic settings of the service.') @@ -102,12 +115,12 @@ param serviceManagedResourcesSettings object? @sys.description('Optional. The list of shared private link resources in this workspace. Note: This property is not idempotent.') param sharedPrivateLinkResources array? -@sys.description('Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set.') +@sys.description('Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled.') @allowed([ 'Enabled' 'Disabled' ]) -param publicNetworkAccess string? +param publicNetworkAccess string = 'Disabled' // ================// // Variables // @@ -200,7 +213,8 @@ resource cMKUserAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentiti ) } -resource workspace 'Microsoft.MachineLearningServices/workspaces@2024-04-01' = { +// Preview API version for 'systemDatastoresAuthMode' +resource workspace 'Microsoft.MachineLearningServices/workspaces@2024-04-01-preview' = { name: name location: location tags: tags @@ -218,7 +232,6 @@ resource workspace 'Microsoft.MachineLearningServices/workspaces@2024-04-01' = { applicationInsights: associatedApplicationInsightsResourceId containerRegistry: associatedContainerRegistryResourceId hbiWorkspace: hbiWorkspace - allowPublicAccessWhenBehindVnet: allowPublicAccessWhenBehindVnet description: description discoveryUrl: discoveryUrl encryption: !empty(customerManagedKey) @@ -239,12 +252,14 @@ resource workspace 'Microsoft.MachineLearningServices/workspaces@2024-04-01' = { : null imageBuildCompute: imageBuildCompute primaryUserAssignedIdentity: primaryUserAssignedIdentity - publicNetworkAccess: !empty(publicNetworkAccess) - ? any(publicNetworkAccess) - : (!empty(privateEndpoints) ? 'Disabled' : 'Enabled') + systemDatastoresAuthMode: systemDatastoresAuthMode + publicNetworkAccess: publicNetworkAccess serviceManagedResourcesSettings: serviceManagedResourcesSettings featureStoreSettings: featureStoreSettings hubResourceId: hubResourceId + managedNetwork: managedNetworkSettings + serverlessComputeSettings: serverlessComputeSettings + workspaceHubConfig: workspaceHubConfig }, // Parameters only added if not empty !empty(sharedPrivateLinkResources) @@ -547,6 +562,87 @@ type featureStoreSettingType = { onlineStoreConnectionName: string? }? +@discriminator('type') +type OutboundRuleType = FqdnOutboundRuleType | PrivateEndpointOutboundRule | ServiceTagOutboundRule + +type FqdnOutboundRuleType = { + @sys.description('Required. Type of a managed network Outbound Rule of a machine learning workspace. Only supported when \'isolationMode\' is \'AllowOnlyApprovedOutbound\'.') + type: 'FQDN' + + @sys.description('Required. Fully Qualified Domain Name to allow for outbound traffic.') + destination: string + + @sys.description('Optional. Category of a managed network Outbound Rule of a machine learning workspace.') + category: 'Dependency' | 'Recommended' | 'Required' | 'UserDefined'? +} + +type PrivateEndpointOutboundRule = { + @sys.description('Required. Type of a managed network Outbound Rule of a machine learning workspace. Only supported when \'isolationMode\' is \'AllowOnlyApprovedOutbound\' or \'AllowInternetOutbound\'.') + type: 'PrivateEndpoint' + + @sys.description('Required. Service Tag destination for a Service Tag Outbound Rule for the managed network of a machine learning workspace.') + destination: { + @sys.description('Required. The resource ID of the target resource for the private endpoint.') + serviceResourceId: string + + @sys.description('Optional. Whether the private endpoint can be used by jobs running on Spark.') + sparkEnabled: bool? + + @sys.description('Required. The sub resource to connect for the private endpoint.') + subresourceTarget: string + } + + @sys.description('Optional. Category of a managed network Outbound Rule of a machine learning workspace.') + category: 'Dependency' | 'Recommended' | 'Required' | 'UserDefined'? +} + +type ServiceTagOutboundRule = { + @sys.description('Required. Type of a managed network Outbound Rule of a machine learning workspace. Only supported when \'isolationMode\' is \'AllowOnlyApprovedOutbound\'.') + type: 'ServiceTag' + + @sys.description('Required. Service Tag destination for a Service Tag Outbound Rule for the managed network of a machine learning workspace.') + destination: { + @sys.description('Required. The name of the service tag to allow.') + portRanges: string + + @sys.description('Required. The protocol to allow. Provide an asterisk(*) to allow any protocol.') + protocol: 'TCP' | 'UDP' | 'ICMP' | '*' + + @sys.description('Required. Which ports will be allow traffic by this rule. Provide an asterisk(*) to allow any port.') + serviceTag: string + } + + @sys.description('Optional. Category of a managed network Outbound Rule of a machine learning workspace.') + category: 'Dependency' | 'Recommended' | 'Required' | 'UserDefined'? +} + +type managedNetworkSettingType = { + @sys.description('Required. Isolation mode for the managed network of a machine learning workspace.') + isolationMode: 'AllowInternetOutbound' | 'AllowOnlyApprovedOutbound' | 'Disabled' + + @sys.description('Optional. Outbound rules for the managed network of a machine learning workspace.') + outboundRules: { + @sys.description('Required. The outbound rule. The name of the rule is the object key.') + *: OutboundRuleType + }? +}? + +type serverlessComputeSettingType = { + @sys.description('Optional. The resource ID of an existing virtual network subnet in which serverless compute nodes should be deployed.') + serverlessComputeCustomSubnet: string? + + @sys.description('Optional. The flag to signal if serverless compute nodes deployed in custom vNet would have no public IP addresses for a workspace with private endpoint.') + serverlessComputeNoPublicIP: bool? +}? + +type workspaceHubConfigType = { + @sys.description('Optional. The resource IDs of additional storage accounts to attach to the workspace.') + additionalWorkspaceStorageAccounts: string[]? + + @sys.description('Optional. The resource ID of the default resource group for projects created in the workspace hub.') + defaultWorkspaceResourceGroup: string? +}? + type diagnosticSettingType = { @sys.description('Optional. The name of diagnostic setting.') name: string? diff --git a/avm/res/machine-learning-services/workspace/main.json b/avm/res/machine-learning-services/workspace/main.json index 91723a42df..e6ad861acb 100644 --- a/avm/res/machine-learning-services/workspace/main.json +++ b/avm/res/machine-learning-services/workspace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.28.1.47646", - "templateHash": "17821708534553004575" + "templateHash": "5554101983463248531" }, "name": "Machine Learning Services Workspaces", "description": "This module deploys a Machine Learning Services Workspace.", @@ -359,6 +359,243 @@ }, "nullable": true }, + "OutboundRuleType": { + "type": "object", + "discriminator": { + "propertyName": "type", + "mapping": { + "FQDN": { + "$ref": "#/definitions/FqdnOutboundRuleType" + }, + "PrivateEndpoint": { + "$ref": "#/definitions/PrivateEndpointOutboundRule" + }, + "ServiceTag": { + "$ref": "#/definitions/ServiceTagOutboundRule" + } + } + } + }, + "FqdnOutboundRuleType": { + "type": "object", + "properties": { + "type": { + "type": "string", + "allowedValues": [ + "FQDN" + ], + "metadata": { + "description": "Required. Type of a managed network Outbound Rule of a machine learning workspace. Only supported when 'isolationMode' is 'AllowOnlyApprovedOutbound'." + } + }, + "destination": { + "type": "string", + "metadata": { + "description": "Required. Fully Qualified Domain Name to allow for outbound traffic." + } + }, + "category": { + "type": "string", + "allowedValues": [ + "Dependency", + "Recommended", + "Required", + "UserDefined" + ], + "nullable": true, + "metadata": { + "description": "Optional. Category of a managed network Outbound Rule of a machine learning workspace." + } + } + } + }, + "PrivateEndpointOutboundRule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "allowedValues": [ + "PrivateEndpoint" + ], + "metadata": { + "description": "Required. Type of a managed network Outbound Rule of a machine learning workspace. Only supported when 'isolationMode' is 'AllowOnlyApprovedOutbound' or 'AllowInternetOutbound'." + } + }, + "destination": { + "type": "object", + "properties": { + "serviceResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the target resource for the private endpoint." + } + }, + "sparkEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the private endpoint can be used by jobs running on Spark." + } + }, + "subresourceTarget": { + "type": "string", + "metadata": { + "description": "Required. The sub resource to connect for the private endpoint." + } + } + }, + "metadata": { + "description": "Required. Service Tag destination for a Service Tag Outbound Rule for the managed network of a machine learning workspace." + } + }, + "category": { + "type": "string", + "allowedValues": [ + "Dependency", + "Recommended", + "Required", + "UserDefined" + ], + "nullable": true, + "metadata": { + "description": "Optional. Category of a managed network Outbound Rule of a machine learning workspace." + } + } + } + }, + "ServiceTagOutboundRule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "allowedValues": [ + "ServiceTag" + ], + "metadata": { + "description": "Required. Type of a managed network Outbound Rule of a machine learning workspace. Only supported when 'isolationMode' is 'AllowOnlyApprovedOutbound'." + } + }, + "destination": { + "type": "object", + "properties": { + "portRanges": { + "type": "string", + "metadata": { + "description": "Required. The name of the service tag to allow." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "*", + "ICMP", + "TCP", + "UDP" + ], + "metadata": { + "description": "Required. The protocol to allow. Provide an asterisk(*) to allow any protocol." + } + }, + "serviceTag": { + "type": "string", + "metadata": { + "description": "Required. Which ports will be allow traffic by this rule. Provide an asterisk(*) to allow any port." + } + } + }, + "metadata": { + "description": "Required. Service Tag destination for a Service Tag Outbound Rule for the managed network of a machine learning workspace." + } + }, + "category": { + "type": "string", + "allowedValues": [ + "Dependency", + "Recommended", + "Required", + "UserDefined" + ], + "nullable": true, + "metadata": { + "description": "Optional. Category of a managed network Outbound Rule of a machine learning workspace." + } + } + } + }, + "managedNetworkSettingType": { + "type": "object", + "properties": { + "isolationMode": { + "type": "string", + "allowedValues": [ + "AllowInternetOutbound", + "AllowOnlyApprovedOutbound", + "Disabled" + ], + "metadata": { + "description": "Required. Isolation mode for the managed network of a machine learning workspace." + } + }, + "outboundRules": { + "type": "object", + "properties": {}, + "additionalProperties": { + "$ref": "#/definitions/OutboundRuleType", + "metadata": { + "description": "Required. The outbound rule. The name of the rule is the object key." + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Outbound rules for the managed network of a machine learning workspace." + } + } + }, + "nullable": true + }, + "serverlessComputeSettingType": { + "type": "object", + "properties": { + "serverlessComputeCustomSubnet": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of an existing virtual network subnet in which serverless compute nodes should be deployed." + } + }, + "serverlessComputeNoPublicIP": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. The flag to signal if serverless compute nodes deployed in custom vNet would have no public IP addresses for a workspace with private endpoint." + } + } + }, + "nullable": true + }, + "workspaceHubConfigType": { + "type": "object", + "properties": { + "additionalWorkspaceStorageAccounts": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource IDs of additional storage accounts to attach to the workspace." + } + }, + "defaultWorkspaceResourceGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the default resource group for projects created in the workspace hub." + } + } + }, + "nullable": true + }, "diagnosticSettingType": { "type": "array", "items": { @@ -592,13 +829,6 @@ "description": "Optional. The flag to signal HBI data in the workspace and reduce diagnostic data collected by the service." } }, - "allowPublicAccessWhenBehindVnet": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. The flag to indicate whether to allow public access when behind VNet." - } - }, "hubResourceId": { "type": "string", "nullable": true, @@ -654,6 +884,35 @@ "description": "Conditional. Settings for feature store type workspaces. Required if 'kind' is set to 'FeatureStore'." } }, + "managedNetworkSettings": { + "$ref": "#/definitions/managedNetworkSettingType", + "metadata": { + "description": "Optional. Managed Network settings for a machine learning workspace." + } + }, + "serverlessComputeSettings": { + "$ref": "#/definitions/serverlessComputeSettingType", + "metadata": { + "description": "Optional. Settings for serverless compute created in the workspace." + } + }, + "systemDatastoresAuthMode": { + "type": "string", + "nullable": true, + "allowedValues": [ + "accessKey", + "identity" + ], + "metadata": { + "description": "Optional. The authentication mode used by the workspace when connecting to the default storage account." + } + }, + "workspaceHubConfig": { + "$ref": "#/definitions/workspaceHubConfigType", + "metadata": { + "description": "Optional. Configuration for workspace hub settings." + } + }, "diagnosticSettings": { "$ref": "#/definitions/diagnosticSettingType", "metadata": { @@ -710,13 +969,13 @@ }, "publicNetworkAccess": { "type": "string", - "nullable": true, + "defaultValue": "Disabled", "allowedValues": [ "Enabled", "Disabled" ], "metadata": { - "description": "Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set." + "description": "Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled." } } }, @@ -788,7 +1047,7 @@ }, "workspace": { "type": "Microsoft.MachineLearningServices/workspaces", - "apiVersion": "2024-04-01", + "apiVersion": "2024-04-01-preview", "name": "[parameters('name')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", @@ -797,7 +1056,7 @@ "tier": "[parameters('sku')]" }, "identity": "[variables('identity')]", - "properties": "[union(createObject('friendlyName', parameters('name'), 'storageAccount', parameters('associatedStorageAccountResourceId'), 'keyVault', parameters('associatedKeyVaultResourceId'), 'applicationInsights', parameters('associatedApplicationInsightsResourceId'), 'containerRegistry', parameters('associatedContainerRegistryResourceId'), 'hbiWorkspace', parameters('hbiWorkspace'), 'allowPublicAccessWhenBehindVnet', parameters('allowPublicAccessWhenBehindVnet'), 'description', parameters('description'), 'discoveryUrl', parameters('discoveryUrl'), 'encryption', if(not(empty(parameters('customerManagedKey'))), createObject('status', 'Enabled', 'identity', if(not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'))), createObject('userAssignedIdentity', extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '//'), '/')[2], split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '////'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', last(split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), 'dummyMsi'), '/')))), null()), 'keyVaultProperties', createObject('keyVaultArmId', extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '//'), '/')[2], split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '////'), '/')[4]), 'Microsoft.KeyVault/vaults', last(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), 'dummyVault'), '/'))), 'keyIdentifier', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'keyVersion'), ''))), format('{0}/{1}', reference('cMKKeyVault::cMKKey').keyUri, parameters('customerManagedKey').keyVersion), reference('cMKKeyVault::cMKKey').keyUriWithVersion))), null()), 'imageBuildCompute', parameters('imageBuildCompute'), 'primaryUserAssignedIdentity', parameters('primaryUserAssignedIdentity'), 'publicNetworkAccess', if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(not(empty(parameters('privateEndpoints'))), 'Disabled', 'Enabled')), 'serviceManagedResourcesSettings', parameters('serviceManagedResourcesSettings'), 'featureStoreSettings', parameters('featureStoreSettings'), 'hubResourceId', parameters('hubResourceId')), if(not(empty(parameters('sharedPrivateLinkResources'))), createObject('sharedPrivateLinkResources', parameters('sharedPrivateLinkResources')), createObject()))]", + "properties": "[union(createObject('friendlyName', parameters('name'), 'storageAccount', parameters('associatedStorageAccountResourceId'), 'keyVault', parameters('associatedKeyVaultResourceId'), 'applicationInsights', parameters('associatedApplicationInsightsResourceId'), 'containerRegistry', parameters('associatedContainerRegistryResourceId'), 'hbiWorkspace', parameters('hbiWorkspace'), 'description', parameters('description'), 'discoveryUrl', parameters('discoveryUrl'), 'encryption', if(not(empty(parameters('customerManagedKey'))), createObject('status', 'Enabled', 'identity', if(not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'))), createObject('userAssignedIdentity', extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '//'), '/')[2], split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '////'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', last(split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), 'dummyMsi'), '/')))), null()), 'keyVaultProperties', createObject('keyVaultArmId', extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '//'), '/')[2], split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '////'), '/')[4]), 'Microsoft.KeyVault/vaults', last(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), 'dummyVault'), '/'))), 'keyIdentifier', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'keyVersion'), ''))), format('{0}/{1}', reference('cMKKeyVault::cMKKey').keyUri, parameters('customerManagedKey').keyVersion), reference('cMKKeyVault::cMKKey').keyUriWithVersion))), null()), 'imageBuildCompute', parameters('imageBuildCompute'), 'primaryUserAssignedIdentity', parameters('primaryUserAssignedIdentity'), 'systemDatastoresAuthMode', parameters('systemDatastoresAuthMode'), 'publicNetworkAccess', parameters('publicNetworkAccess'), 'serviceManagedResourcesSettings', parameters('serviceManagedResourcesSettings'), 'featureStoreSettings', parameters('featureStoreSettings'), 'hubResourceId', parameters('hubResourceId'), 'managedNetwork', parameters('managedNetworkSettings'), 'serverlessComputeSettings', parameters('serverlessComputeSettings'), 'workspaceHubConfig', parameters('workspaceHubConfig')), if(not(empty(parameters('sharedPrivateLinkResources'))), createObject('sharedPrivateLinkResources', parameters('sharedPrivateLinkResources')), createObject()))]", "kind": "[parameters('kind')]", "dependsOn": [ "cMKKeyVault", @@ -1843,14 +2102,14 @@ "metadata": { "description": "The principal ID of the system assigned identity." }, - "value": "[coalesce(tryGet(tryGet(reference('workspace', '2024-04-01', 'full'), 'identity'), 'principalId'), '')]" + "value": "[coalesce(tryGet(tryGet(reference('workspace', '2024-04-01-preview', 'full'), 'identity'), 'principalId'), '')]" }, "location": { "type": "string", "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('workspace', '2024-04-01', 'full').location]" + "value": "[reference('workspace', '2024-04-01-preview', 'full').location]" } } } \ No newline at end of file diff --git a/avm/res/machine-learning-services/workspace/tests/e2e/ai/dependencies.bicep b/avm/res/machine-learning-services/workspace/tests/e2e/ai/dependencies.bicep index 6ddec13775..8ea028e953 100644 --- a/avm/res/machine-learning-services/workspace/tests/e2e/ai/dependencies.bicep +++ b/avm/res/machine-learning-services/workspace/tests/e2e/ai/dependencies.bicep @@ -10,6 +10,9 @@ param applicationInsightsName string @description('Required. The name of the Storage Account to create.') param storageAccountName string +@description('Required. The name of the additional Storage Account to create.') +param secondaryStorageAccountName string + resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { name: keyVaultName location: location @@ -44,11 +47,23 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = { kind: 'StorageV2' } +resource secondaryStorageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = { + name: secondaryStorageAccountName + location: location + sku: { + name: 'Standard_LRS' + } + kind: 'StorageV2' +} + @description('The resource ID of the created Application Insights instance.') output applicationInsightsResourceId string = applicationInsights.id @description('The resource ID of the created Storage Account.') output storageAccountResourceId string = storageAccount.id +@description('The resource ID of the additional created Storage Account.') +output secondaryStorageAccountResourceId string = secondaryStorageAccount.id + @description('The resource ID of the created Key Vault.') output keyVaultResourceId string = keyVault.id diff --git a/avm/res/machine-learning-services/workspace/tests/e2e/ai/main.test.bicep b/avm/res/machine-learning-services/workspace/tests/e2e/ai/main.test.bicep index b76ec22ecd..dea496d17f 100644 --- a/avm/res/machine-learning-services/workspace/tests/e2e/ai/main.test.bicep +++ b/avm/res/machine-learning-services/workspace/tests/e2e/ai/main.test.bicep @@ -41,6 +41,7 @@ module nestedDependencies 'dependencies.bicep' = { keyVaultName: 'dep-${namePrefix}-kv-${serviceShort}-${substring(uniqueString(baseTime), 0, 3)}' applicationInsightsName: 'dep-${namePrefix}-appI-${serviceShort}' storageAccountName: 'dep${namePrefix}st${serviceShort}' + secondaryStorageAccountName: 'dep${namePrefix}st${serviceShort}2' location: resourceLocation } } @@ -62,6 +63,10 @@ module testDeployment '../../../main.bicep' = [ associatedStorageAccountResourceId: nestedDependencies.outputs.storageAccountResourceId sku: 'Basic' kind: 'Hub' + workspaceHubConfig: { + additionalWorkspaceStorageAccounts: [nestedDependencies.outputs.secondaryStorageAccountResourceId] + defaultWorkspaceResourceGroup: resourceGroup.id + } } dependsOn: [ nestedDependencies diff --git a/avm/res/machine-learning-services/workspace/tests/e2e/encr/dependencies.bicep b/avm/res/machine-learning-services/workspace/tests/e2e/encr/dependencies.bicep index 99e9ed9814..7a7181129f 100644 --- a/avm/res/machine-learning-services/workspace/tests/e2e/encr/dependencies.bicep +++ b/avm/res/machine-learning-services/workspace/tests/e2e/encr/dependencies.bicep @@ -15,6 +15,9 @@ param applicationInsightsName string @description('Required. The name of the Storage Account to create.') param storageAccountName string +@description('Required. The name of the additional Storage Account to create.') +param secondaryStorageAccountName string + resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { name: keyVaultName location: location @@ -87,6 +90,15 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = { kind: 'StorageV2' } +resource secondaryStorageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = { + name: secondaryStorageAccountName + location: location + sku: { + name: 'Standard_LRS' + } + kind: 'StorageV2' +} + @description('The resource ID of the created Key Vault.') output keyVaultResourceId string = keyVault.id @@ -99,5 +111,8 @@ output applicationInsightsResourceId string = applicationInsights.id @description('The resource ID of the created Storage Account.') output storageAccountResourceId string = storageAccount.id +@description('The resource ID of the additional created Storage Account.') +output secondaryStorageAccountResourceId string = secondaryStorageAccount.id + @description('The name of the Key Vault Encryption Key.') output keyVaultEncryptionKeyName string = keyVault::key.name diff --git a/avm/res/machine-learning-services/workspace/tests/e2e/encr/main.test.bicep b/avm/res/machine-learning-services/workspace/tests/e2e/encr/main.test.bicep index 909437d5c9..250f2ed881 100644 --- a/avm/res/machine-learning-services/workspace/tests/e2e/encr/main.test.bicep +++ b/avm/res/machine-learning-services/workspace/tests/e2e/encr/main.test.bicep @@ -43,6 +43,7 @@ module nestedDependencies 'dependencies.bicep' = { managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' applicationInsightsName: 'dep-${namePrefix}-appI-${serviceShort}' storageAccountName: 'dep${namePrefix}st${serviceShort}' + secondaryStorageAccountName: 'dep${namePrefix}st${serviceShort}2' location: resourceLocation } } @@ -75,6 +76,19 @@ module testDeployment '../../../main.bicep' = [ nestedDependencies.outputs.managedIdentityResourceId ] } + managedNetworkSettings: { + isolationMode: 'AllowInternetOutbound' + outboundRules: { + rule: { + type: 'PrivateEndpoint' + destination: { + serviceResourceId: nestedDependencies.outputs.secondaryStorageAccountResourceId + subresourceTarget: 'blob' + } + category: 'UserDefined' + } + } + } } dependsOn: [ nestedDependencies diff --git a/avm/res/machine-learning-services/workspace/tests/e2e/max/main.test.bicep b/avm/res/machine-learning-services/workspace/tests/e2e/max/main.test.bicep index c33513246c..b1fff69995 100644 --- a/avm/res/machine-learning-services/workspace/tests/e2e/max/main.test.bicep +++ b/avm/res/machine-learning-services/workspace/tests/e2e/max/main.test.bicep @@ -175,6 +175,14 @@ module testDeployment '../../../main.bicep' = [ nestedDependencies.outputs.managedIdentityResourceId ] } + serverlessComputeSettings: { + serverlessComputeCustomSubnet: nestedDependencies.outputs.subnetResourceId + serverlessComputeNoPublicIP: true + } + managedNetworkSettings: { + isolationMode: 'Disabled' + } + systemDatastoresAuthMode: 'accessKey' tags: { 'hidden-title': 'This is visible in the resource name' Environment: 'Non-Prod' diff --git a/avm/res/machine-learning-services/workspace/tests/e2e/waf-aligned/main.test.bicep b/avm/res/machine-learning-services/workspace/tests/e2e/waf-aligned/main.test.bicep index ff83c904ba..9d3e13cb35 100644 --- a/avm/res/machine-learning-services/workspace/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/machine-learning-services/workspace/tests/e2e/waf-aligned/main.test.bicep @@ -98,6 +98,35 @@ module testDeployment '../../../main.bicep' = [ } } ] + managedNetworkSettings: { + isolationMode: 'AllowOnlyApprovedOutbound' + outboundRules: { + rule1: { + type: 'PrivateEndpoint' + destination: { + serviceResourceId: diagnosticDependencies.outputs.storageAccountResourceId + subresourceTarget: 'blob' + sparkEnabled: true + } + category: 'UserDefined' + } + rule2: { + type: 'FQDN' + destination: 'pypi.org' + category: 'UserDefined' + } + rule3: { + type: 'ServiceTag' + destination: { + serviceTag: 'AppService' + portRanges: '80,443' + protocol: 'TCP' + } + category: 'UserDefined' + } + } + } + systemDatastoresAuthMode: 'identity' tags: { 'hidden-title': 'This is visible in the resource name' Environment: 'Non-Prod' diff --git a/avm/res/machine-learning-services/workspace/version.json b/avm/res/machine-learning-services/workspace/version.json index b3d560b1ad..96236a61ba 100644 --- a/avm/res/machine-learning-services/workspace/version.json +++ b/avm/res/machine-learning-services/workspace/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.3", + "version": "0.4", "pathFilters": [ "./main.json" ] diff --git a/avm/res/operational-insights/workspace/README.md b/avm/res/operational-insights/workspace/README.md index 21b105fc0e..6334b728d3 100644 --- a/avm/res/operational-insights/workspace/README.md +++ b/avm/res/operational-insights/workspace/README.md @@ -325,6 +325,7 @@ module workspace 'br/public:avm/res/operational-insights/workspace:' = 'hidden-title': 'This is visible in the resource name' Role: 'DeploymentValidation' } + useDeployedWorkspaceForDiagnosticSettings: true useResourcePermissions: true } } @@ -645,6 +646,9 @@ module workspace 'br/public:avm/res/operational-insights/workspace:' = "Role": "DeploymentValidation" } }, + "useDeployedWorkspaceForDiagnosticSettings": { + "value": true + }, "useResourcePermissions": { "value": true } @@ -1717,6 +1721,7 @@ module workspace 'br/public:avm/res/operational-insights/workspace:' = | [`storageInsightsConfigs`](#parameter-storageinsightsconfigs) | array | List of storage accounts to be read by the workspace. | | [`tables`](#parameter-tables) | array | LAW custom tables to be deployed. | | [`tags`](#parameter-tags) | object | Tags of the resource. | +| [`useDeployedWorkspaceForDiagnosticSettings`](#parameter-usedeployedworkspacefordiagnosticsettings) | bool | Instead of using an external reference, use the deployed instance as the target for its diagnostic settings. | | [`useResourcePermissions`](#parameter-useresourcepermissions) | bool | Set to 'true' to use resource or workspace permissions and 'false' (or leave empty) to require workspace permissions. | ### Parameter: `name` @@ -2195,6 +2200,14 @@ Tags of the resource. - Required: No - Type: object +### Parameter: `useDeployedWorkspaceForDiagnosticSettings` + +Instead of using an external reference, use the deployed instance as the target for its diagnostic settings. + +- Required: No +- Type: bool +- Default: `False` + ### Parameter: `useResourcePermissions` Set to 'true' to use resource or workspace permissions and 'false' (or leave empty) to require workspace permissions. diff --git a/avm/res/operational-insights/workspace/main.bicep b/avm/res/operational-insights/workspace/main.bicep index 2a7b5e5dae..d7e2e80689 100644 --- a/avm/res/operational-insights/workspace/main.bicep +++ b/avm/res/operational-insights/workspace/main.bicep @@ -82,6 +82,9 @@ param useResourcePermissions bool = false @description('Optional. The diagnostic settings of the service.') param diagnosticSettings diagnosticSettingType +@description('Optional. Instead of using an external reference, use the deployed instance as the target for its diagnostic settings.') +param useDeployedWorkspaceForDiagnosticSettings bool = false + @description('Optional. Indicates whether customer managed storage is mandatory for query management.') param forceCmkForQuery bool = true @@ -198,7 +201,9 @@ resource logAnalyticsWorkspace_diagnosticSettings 'Microsoft.Insights/diagnostic name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' properties: { storageAccountId: diagnosticSetting.?storageAccountResourceId - workspaceId: diagnosticSetting.?workspaceResourceId + workspaceId: useDeployedWorkspaceForDiagnosticSettings + ? logAnalyticsWorkspace.id + : diagnosticSetting.?workspaceResourceId eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId eventHubName: diagnosticSetting.?eventHubName metrics: [ diff --git a/avm/res/operational-insights/workspace/main.json b/avm/res/operational-insights/workspace/main.json index bce3b4bfe2..f0dcd643b6 100644 --- a/avm/res/operational-insights/workspace/main.json +++ b/avm/res/operational-insights/workspace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.28.1.47646", - "templateHash": "5922198770832880671" + "templateHash": "4858897614510907922" }, "name": "Log Analytics Workspaces", "description": "This module deploys a Log Analytics Workspace.", @@ -402,6 +402,13 @@ "description": "Optional. The diagnostic settings of the service." } }, + "useDeployedWorkspaceForDiagnosticSettings": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Instead of using an external reference, use the deployed instance as the target for its diagnostic settings." + } + }, "forceCmkForQuery": { "type": "bool", "defaultValue": true, @@ -530,7 +537,7 @@ } ], "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", - "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "workspaceId": "[if(parameters('useDeployedWorkspaceForDiagnosticSettings'), resourceId('Microsoft.OperationalInsights/workspaces', parameters('name')), tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId'))]", "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", diff --git a/avm/res/operational-insights/workspace/tests/e2e/adv/main.test.bicep b/avm/res/operational-insights/workspace/tests/e2e/adv/main.test.bicep index 8bfaf20aee..e0025415db 100644 --- a/avm/res/operational-insights/workspace/tests/e2e/adv/main.test.bicep +++ b/avm/res/operational-insights/workspace/tests/e2e/adv/main.test.bicep @@ -175,6 +175,7 @@ module testDeployment '../../../main.bicep' = [ workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId } ] + useDeployedWorkspaceForDiagnosticSettings: true gallerySolutions: [ { name: 'AzureAutomation' diff --git a/avm/res/operational-insights/workspace/version.json b/avm/res/operational-insights/workspace/version.json index c177b1bb58..3f863a2bec 100644 --- a/avm/res/operational-insights/workspace/version.json +++ b/avm/res/operational-insights/workspace/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.3", + "version": "0.4", "pathFilters": [ "./main.json" ]