From 7f9c0893518f8df23c72733d9c8b64660c226a74 Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Mon, 20 May 2024 12:22:51 +0200 Subject: [PATCH 01/18] fix: Fixed nested UDT resolution if array definition is in parameter, not the type (#1970) ## Description - Fixed nested UDT resolution if array definition is in parameter, not the type This is now supported ```bicep param test myArrayType[] type myArrayType = { ... } ``` Before it was only ```bicep param test myArrayType type myArrayType = { ... }[] ``` - Updated how the `Set-ModuleReadMe` script throws errors in case a cateogory is missing. - Fixed incorrect parameter metadata for a module - Re-ran generation for ALL modules ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.document-db.database-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.document-db.database-account.yml/badge.svg?branch=users%2Falsehr%2FnestedReadMeFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.document-db.database-account.yml) | | [![avm.res.key-vault.vault](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml/badge.svg?branch=users%2Falsehr%2FnestedReadMeFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml) | ## Type of Change - [x] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- .../document-db/database-account/README.md | 227 ++++++++++++++++++ .../document-db/database-account/main.bicep | 137 ++++++----- .../document-db/database-account/main.json | 42 ++-- .../registration-definition/README.md | 47 ++++ .../sharedScripts/Set-ModuleReadMe.ps1 | 24 +- 5 files changed, 380 insertions(+), 97 deletions(-) diff --git a/avm/res/document-db/database-account/README.md b/avm/res/document-db/database-account/README.md index fa45217f9c..30434814bc 100644 --- a/avm/res/document-db/database-account/README.md +++ b/avm/res/document-db/database-account/README.md @@ -2583,6 +2583,40 @@ Default to the location where the account is deployed. Locations enabled for the - Type: array - Default: `[]` +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`failoverPriority`](#parameter-locationsfailoverpriority) | int | The failover priority of the region. A failover priority of 0 indicates a write region. The maximum value for a failover priority = (total number of regions - 1). Failover priority values must be unique for each of the regions in which the database account exists. | +| [`locationName`](#parameter-locationslocationname) | string | The name of the region. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`isZoneRedundant`](#parameter-locationsiszoneredundant) | bool | Default to true. Flag to indicate whether or not this region is an AvailabilityZone region | + +### Parameter: `locations.failoverPriority` + +The failover priority of the region. A failover priority of 0 indicates a write region. The maximum value for a failover priority = (total number of regions - 1). Failover priority values must be unique for each of the regions in which the database account exists. + +- Required: Yes +- Type: int + +### Parameter: `locations.locationName` + +The name of the region. + +- Required: Yes +- Type: string + +### Parameter: `locations.isZoneRedundant` + +Default to true. Flag to indicate whether or not this region is an AvailabilityZone region + +- Required: No +- Type: bool + ### Parameter: `lock` The lock settings of the service. @@ -3296,6 +3330,199 @@ SQL Databases configurations. - Type: array - Default: `[]` +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-sqldatabasesname) | string | Name of the SQL database . | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`autoscaleSettingsMaxThroughput`](#parameter-sqldatabasesautoscalesettingsmaxthroughput) | int | Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. | +| [`containers`](#parameter-sqldatabasescontainers) | array | Array of containers to deploy in the SQL database. | +| [`throughput`](#parameter-sqldatabasesthroughput) | int | Default to 400. Request units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. | + +### Parameter: `sqlDatabases.name` + +Name of the SQL database . + +- Required: Yes +- Type: string + +### Parameter: `sqlDatabases.autoscaleSettingsMaxThroughput` + +Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. + +- Required: No +- Type: int + +### Parameter: `sqlDatabases.containers` + +Array of containers to deploy in the SQL database. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-sqldatabasescontainersname) | string | Name of the container. | +| [`paths`](#parameter-sqldatabasescontainerspaths) | array | List of paths using which data within the container can be partitioned. For kind=MultiHash it can be up to 3. For anything else it needs to be exactly 1. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`analyticalStorageTtl`](#parameter-sqldatabasescontainersanalyticalstoragettl) | int | Default to 0. Indicates how long data should be retained in the analytical store, for a container. Analytical store is enabled when ATTL is set with a value other than 0. If the value is set to -1, the analytical store retains all historical data, irrespective of the retention of the data in the transactional store. | +| [`autoscaleSettingsMaxThroughput`](#parameter-sqldatabasescontainersautoscalesettingsmaxthroughput) | int | Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. | +| [`conflictResolutionPolicy`](#parameter-sqldatabasescontainersconflictresolutionpolicy) | object | The conflict resolution policy for the container. Conflicts and conflict resolution policies are applicable if the Azure Cosmos DB account is configured with multiple write regions. | +| [`defaultTtl`](#parameter-sqldatabasescontainersdefaultttl) | int | Default to -1. Default time to live (in seconds). With Time to Live or TTL, Azure Cosmos DB provides the ability to delete items automatically from a container after a certain time period. If the value is set to "-1", it is equal to infinity, and items don't expire by default. | +| [`indexingPolicy`](#parameter-sqldatabasescontainersindexingpolicy) | object | Indexing policy of the container. | +| [`kind`](#parameter-sqldatabasescontainerskind) | string | Default to Hash. Indicates the kind of algorithm used for partitioning. | +| [`throughput`](#parameter-sqldatabasescontainersthroughput) | int | Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. | +| [`uniqueKeyPolicyKeys`](#parameter-sqldatabasescontainersuniquekeypolicykeys) | array | The unique key policy configuration containing a list of unique keys that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service. | + +### Parameter: `sqlDatabases.containers.name` + +Name of the container. + +- Required: Yes +- Type: string + +### Parameter: `sqlDatabases.containers.paths` + +List of paths using which data within the container can be partitioned. For kind=MultiHash it can be up to 3. For anything else it needs to be exactly 1. + +- Required: Yes +- Type: array + +### Parameter: `sqlDatabases.containers.analyticalStorageTtl` + +Default to 0. Indicates how long data should be retained in the analytical store, for a container. Analytical store is enabled when ATTL is set with a value other than 0. If the value is set to -1, the analytical store retains all historical data, irrespective of the retention of the data in the transactional store. + +- Required: No +- Type: int + +### Parameter: `sqlDatabases.containers.autoscaleSettingsMaxThroughput` + +Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. + +- Required: No +- Type: int + +### Parameter: `sqlDatabases.containers.conflictResolutionPolicy` + +The conflict resolution policy for the container. Conflicts and conflict resolution policies are applicable if the Azure Cosmos DB account is configured with multiple write regions. + +- Required: No +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`mode`](#parameter-sqldatabasescontainersconflictresolutionpolicymode) | string | Indicates the conflict resolution mode. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`conflictResolutionPath`](#parameter-sqldatabasescontainersconflictresolutionpolicyconflictresolutionpath) | string | The conflict resolution path in the case of LastWriterWins mode. Required if `mode` is set to 'LastWriterWins'. | +| [`conflictResolutionProcedure`](#parameter-sqldatabasescontainersconflictresolutionpolicyconflictresolutionprocedure) | string | The procedure to resolve conflicts in the case of custom mode. Required if `mode` is set to 'Custom'. | + +### Parameter: `sqlDatabases.containers.conflictResolutionPolicy.mode` + +Indicates the conflict resolution mode. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'Custom' + 'LastWriterWins' + ] + ``` + +### Parameter: `sqlDatabases.containers.conflictResolutionPolicy.conflictResolutionPath` + +The conflict resolution path in the case of LastWriterWins mode. Required if `mode` is set to 'LastWriterWins'. + +- Required: No +- Type: string + +### Parameter: `sqlDatabases.containers.conflictResolutionPolicy.conflictResolutionProcedure` + +The procedure to resolve conflicts in the case of custom mode. Required if `mode` is set to 'Custom'. + +- Required: No +- Type: string + +### Parameter: `sqlDatabases.containers.defaultTtl` + +Default to -1. Default time to live (in seconds). With Time to Live or TTL, Azure Cosmos DB provides the ability to delete items automatically from a container after a certain time period. If the value is set to "-1", it is equal to infinity, and items don't expire by default. + +- Required: No +- Type: int + +### Parameter: `sqlDatabases.containers.indexingPolicy` + +Indexing policy of the container. + +- Required: No +- Type: object + +### Parameter: `sqlDatabases.containers.kind` + +Default to Hash. Indicates the kind of algorithm used for partitioning. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Hash' + 'MultiHash' + ] + ``` + +### Parameter: `sqlDatabases.containers.throughput` + +Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. + +- Required: No +- Type: int + +### Parameter: `sqlDatabases.containers.uniqueKeyPolicyKeys` + +The unique key policy configuration containing a list of unique keys that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`paths`](#parameter-sqldatabasescontainersuniquekeypolicykeyspaths) | array | List of paths must be unique for each document in the Azure Cosmos DB service | + +### Parameter: `sqlDatabases.containers.uniqueKeyPolicyKeys.paths` + +List of paths must be unique for each document in the Azure Cosmos DB service + +- Required: Yes +- Type: array + +### Parameter: `sqlDatabases.throughput` + +Default to 400. Request units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. + +- Required: No +- Type: int + ### Parameter: `tags` Tags of the Database Account resource. diff --git a/avm/res/document-db/database-account/main.bicep b/avm/res/document-db/database-account/main.bicep index 5cbfbfe85d..65501fdbbf 100644 --- a/avm/res/document-db/database-account/main.bicep +++ b/avm/res/document-db/database-account/main.bicep @@ -233,7 +233,7 @@ var virtualNetworkRules = [ } ] -var databaseAccount_properties = union( +var databaseAccountProperties = union( { databaseAccountOfferType: databaseAccountOfferType }, @@ -308,24 +308,23 @@ var builtInRoleNames = { ) } -resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = - if (enableTelemetry) { - name: '46d3xbcp.res.documentdb-databaseaccount.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' - properties: { - mode: 'Incremental' - template: { - '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' - contentVersion: '1.0.0.0' - resources: [] - outputs: { - telemetry: { - type: 'String' - value: 'For more information, see https://aka.ms/avm/TelemetryInfo' - } +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { + name: '46d3xbcp.res.documentdb-databaseaccount.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + outputs: { + telemetry: { + type: 'String' + value: 'For more information, see https://aka.ms/avm/TelemetryInfo' } } } } +} resource databaseAccount 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = { name: name @@ -333,20 +332,19 @@ resource databaseAccount 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = { tags: tags identity: identity kind: kind - properties: databaseAccount_properties + properties: databaseAccountProperties } -resource databaseAccount_lock 'Microsoft.Authorization/locks@2020-05-01' = - if (!empty(lock ?? {}) && lock.?kind != 'None') { - name: lock.?name ?? 'lock-${name}' - properties: { - level: lock.?kind ?? '' - notes: lock.?kind == 'CanNotDelete' - ? 'Cannot delete resource or child resources.' - : 'Cannot delete or modify the resource or child resources.' - } - scope: databaseAccount +resource databaseAccount_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' + ? 'Cannot delete resource or child resources.' + : 'Cannot delete or modify the resource or child resources.' } + scope: databaseAccount +} resource databaseAccount_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [ for (diagnosticSetting, index) in (diagnosticSettings ?? []): { @@ -490,49 +488,48 @@ module databaseAccount_privateEndpoints 'br/public:avm/res/network/private-endpo } ] -module keyVault 'modules/secrets-key-vault.bicep' = - if (secretsKeyVault != null) { - name: '${uniqueString(deployment().name, location)}-secrets-kv' - scope: resourceGroup(secretsKeyVault.?resourceGroupName ?? resourceGroup().name) - params: { - keyVaultName: secretsKeyVault!.keyVaultName +module keyVault 'modules/secrets-key-vault.bicep' = if (secretsKeyVault != null) { + name: '${uniqueString(deployment().name, location)}-secrets-kv' + scope: resourceGroup(secretsKeyVault.?resourceGroupName ?? resourceGroup().name) + params: { + keyVaultName: secretsKeyVault!.keyVaultName - keySecrets: [ - { - secretName: secretsKeyVault.?primaryWriteKeySecretName ?? 'Primary-Write-Key' - secretValue: databaseAccount.listKeys().primaryMasterKey - } - { - secretName: secretsKeyVault.?primaryReadOnlyKeySecretName ?? 'Primary-Readonly-Key' - secretValue: databaseAccount.listKeys().primaryReadonlyMasterKey - } - { - secretName: secretsKeyVault.?primaryWriteConnectionStringSecretName ?? 'Primary-Write-ConnectionString' - secretValue: databaseAccount.listConnectionStrings().connectionStrings[0].connectionString - } - { - secretName: secretsKeyVault.?primaryReadonlyConnectionStringSecretName ?? 'Primary-Readonly-ConnectionString' - secretValue: databaseAccount.listConnectionStrings().connectionStrings[2].connectionString - } - { - secretName: secretsKeyVault.?secondaryWriteKeySecretName ?? 'Secondary-Write-Key' - secretValue: databaseAccount.listKeys().secondaryMasterKey - } - { - secretName: secretsKeyVault.?secondaryReadonlyKeySecretName ?? 'Secondary-Readonly-Key' - secretValue: databaseAccount.listKeys().secondaryReadonlyMasterKey - } - { - secretName: secretsKeyVault.?secondaryWriteConnectionStringSecretName ?? 'Secondary-Write-ConnectionString' - secretValue: databaseAccount.listConnectionStrings().connectionStrings[1].connectionString - } - { - secretName: secretsKeyVault.?secondaryReadonlyConnectionStringSecretName ?? 'Secondary-Readonly-ConnectionString' - secretValue: databaseAccount.listConnectionStrings().connectionStrings[3].connectionString - } - ] - } + keySecrets: [ + { + secretName: secretsKeyVault.?primaryWriteKeySecretName ?? 'Primary-Write-Key' + secretValue: databaseAccount.listKeys().primaryMasterKey + } + { + secretName: secretsKeyVault.?primaryReadOnlyKeySecretName ?? 'Primary-Readonly-Key' + secretValue: databaseAccount.listKeys().primaryReadonlyMasterKey + } + { + secretName: secretsKeyVault.?primaryWriteConnectionStringSecretName ?? 'Primary-Write-ConnectionString' + secretValue: databaseAccount.listConnectionStrings().connectionStrings[0].connectionString + } + { + secretName: secretsKeyVault.?primaryReadonlyConnectionStringSecretName ?? 'Primary-Readonly-ConnectionString' + secretValue: databaseAccount.listConnectionStrings().connectionStrings[2].connectionString + } + { + secretName: secretsKeyVault.?secondaryWriteKeySecretName ?? 'Secondary-Write-Key' + secretValue: databaseAccount.listKeys().secondaryMasterKey + } + { + secretName: secretsKeyVault.?secondaryReadonlyKeySecretName ?? 'Secondary-Readonly-Key' + secretValue: databaseAccount.listKeys().secondaryReadonlyMasterKey + } + { + secretName: secretsKeyVault.?secondaryWriteConnectionStringSecretName ?? 'Secondary-Write-ConnectionString' + secretValue: databaseAccount.listConnectionStrings().connectionStrings[1].connectionString + } + { + secretName: secretsKeyVault.?secondaryReadonlyConnectionStringSecretName ?? 'Secondary-Readonly-ConnectionString' + secretValue: databaseAccount.listConnectionStrings().connectionStrings[3].connectionString + } + ] } +} @description('The name of the database account.') output name string = databaseAccount.name @@ -754,10 +751,10 @@ type sqlDatabaseType = { @description('Optional. The conflict resolution policy for the container. Conflicts and conflict resolution policies are applicable if the Azure Cosmos DB account is configured with multiple write regions.') conflictResolutionPolicy: { - @description('Required if mode=LastWriterWins. The conflict resolution path in the case of LastWriterWins mode.') + @description('Conditional. The conflict resolution path in the case of LastWriterWins mode. Required if `mode` is set to \'LastWriterWins\'.') conflictResolutionPath: string? - @description('Required if mode=Custom. The procedure to resolve conflicts in the case of custom mode.') + @description('Conditional. The procedure to resolve conflicts in the case of custom mode. Required if `mode` is set to \'Custom\'.') conflictResolutionProcedure: string? @description('Required. Indicates the conflict resolution mode.') @@ -780,7 +777,7 @@ type sqlDatabaseType = { @description('Optional. The unique key policy configuration containing a list of unique keys that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service.') uniqueKeyPolicyKeys: { - @description('List of paths must be unique for each document in the Azure Cosmos DB service') + @description('Required. List of paths must be unique for each document in the Azure Cosmos DB service') paths: string[] }[]? }[]? diff --git a/avm/res/document-db/database-account/main.json b/avm/res/document-db/database-account/main.json index 943fa8af42..e1a6ae712b 100644 --- a/avm/res/document-db/database-account/main.json +++ b/avm/res/document-db/database-account/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "9794166259717067711" + "version": "0.27.1.19265", + "templateHash": "13227324731926560272" }, "name": "DocumentDB Database Accounts", "description": "This module deploys a DocumentDB Database Account.", @@ -534,14 +534,14 @@ "type": "string", "nullable": true, "metadata": { - "description": "Required if mode=LastWriterWins. The conflict resolution path in the case of LastWriterWins mode." + "description": "Conditional. The conflict resolution path in the case of LastWriterWins mode. Required if `mode` is set to 'LastWriterWins'." } }, "conflictResolutionProcedure": { "type": "string", "nullable": true, "metadata": { - "description": "Required if mode=Custom. The procedure to resolve conflicts in the case of custom mode." + "description": "Conditional. The procedure to resolve conflicts in the case of custom mode. Required if `mode` is set to 'Custom'." } }, "mode": { @@ -605,7 +605,7 @@ "type": "string" }, "metadata": { - "description": "List of paths must be unique for each document in the Azure Cosmos DB service" + "description": "Required. List of paths must be unique for each document in the Azure Cosmos DB service" } } } @@ -1090,7 +1090,7 @@ ], "kind": "[if(or(not(empty(parameters('sqlDatabases'))), not(empty(parameters('gremlinDatabases')))), 'GlobalDocumentDB', if(not(empty(parameters('mongodbDatabases'))), 'MongoDB', 'GlobalDocumentDB'))]", "backupPolicy": "[if(equals(parameters('backupPolicyType'), 'Continuous'), createObject('type', parameters('backupPolicyType'), 'continuousModeProperties', createObject('tier', parameters('backupPolicyContinuousTier'))), createObject('type', parameters('backupPolicyType'), 'periodicModeProperties', createObject('backupIntervalInMinutes', parameters('backupIntervalInMinutes'), 'backupRetentionIntervalInHours', parameters('backupRetentionIntervalInHours'), 'backupStorageRedundancy', parameters('backupStorageRedundancy'))))]", - "databaseAccount_properties": "[union(createObject('databaseAccountOfferType', parameters('databaseAccountOfferType')), if(or(or(not(empty(parameters('sqlDatabases'))), not(empty(parameters('mongodbDatabases')))), not(empty(parameters('gremlinDatabases')))), createObject('consistencyPolicy', variables('consistencyPolicy')[parameters('defaultConsistencyLevel')], 'enableMultipleWriteLocations', parameters('enableMultipleWriteLocations'), 'locations', if(empty(variables('databaseAccount_locations')), variables('defaultFailoverLocation'), variables('databaseAccount_locations')), 'ipRules', variables('ipRules'), 'virtualNetworkRules', variables('virtualNetworkRules'), 'networkAclBypass', coalesce(tryGet(parameters('networkRestrictions'), 'networkAclBypass'), 'AzureServices'), 'publicNetworkAccess', coalesce(tryGet(parameters('networkRestrictions'), 'publicNetworkAccess'), 'Enabled'), 'isVirtualNetworkFilterEnabled', or(not(empty(variables('ipRules'))), not(empty(variables('virtualNetworkRules')))), 'capabilities', variables('capabilities'), 'enableFreeTier', parameters('enableFreeTier'), 'backupPolicy', variables('backupPolicy'), 'enableAutomaticFailover', parameters('automaticFailover'), 'enableAnalyticalStorage', parameters('enableAnalyticalStorage')), createObject()), if(not(empty(parameters('sqlDatabases'))), createObject('disableLocalAuth', parameters('disableLocalAuth'), 'disableKeyBasedMetadataWriteAccess', parameters('disableKeyBasedMetadataWriteAccess')), createObject()), if(not(empty(parameters('mongodbDatabases'))), createObject('apiProperties', createObject('serverVersion', parameters('serverVersion'))), createObject()))]", + "databaseAccountProperties": "[union(createObject('databaseAccountOfferType', parameters('databaseAccountOfferType')), if(or(or(not(empty(parameters('sqlDatabases'))), not(empty(parameters('mongodbDatabases')))), not(empty(parameters('gremlinDatabases')))), createObject('consistencyPolicy', variables('consistencyPolicy')[parameters('defaultConsistencyLevel')], 'enableMultipleWriteLocations', parameters('enableMultipleWriteLocations'), 'locations', if(empty(variables('databaseAccount_locations')), variables('defaultFailoverLocation'), variables('databaseAccount_locations')), 'ipRules', variables('ipRules'), 'virtualNetworkRules', variables('virtualNetworkRules'), 'networkAclBypass', coalesce(tryGet(parameters('networkRestrictions'), 'networkAclBypass'), 'AzureServices'), 'publicNetworkAccess', coalesce(tryGet(parameters('networkRestrictions'), 'publicNetworkAccess'), 'Enabled'), 'isVirtualNetworkFilterEnabled', or(not(empty(variables('ipRules'))), not(empty(variables('virtualNetworkRules')))), 'capabilities', variables('capabilities'), 'enableFreeTier', parameters('enableFreeTier'), 'backupPolicy', variables('backupPolicy'), 'enableAutomaticFailover', parameters('automaticFailover'), 'enableAnalyticalStorage', parameters('enableAnalyticalStorage')), createObject()), if(not(empty(parameters('sqlDatabases'))), createObject('disableLocalAuth', parameters('disableLocalAuth'), 'disableKeyBasedMetadataWriteAccess', parameters('disableKeyBasedMetadataWriteAccess')), createObject()), if(not(empty(parameters('mongodbDatabases'))), createObject('apiProperties', createObject('serverVersion', parameters('serverVersion'))), createObject()))]", "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Cosmos DB Account Reader Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fbdf93bf-df7d-467e-a4d2-9458aa1360c8')]", @@ -1133,7 +1133,7 @@ "tags": "[parameters('tags')]", "identity": "[variables('identity')]", "kind": "[variables('kind')]", - "properties": "[variables('databaseAccount_properties')]" + "properties": "[variables('databaseAccountProperties')]" }, "databaseAccount_lock": { "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", @@ -1249,8 +1249,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "3826907101153138616" + "version": "0.27.1.19265", + "templateHash": "15534269553484112693" }, "name": "DocumentDB Database Account SQL Databases", "description": "This module deploys a SQL Database in a CosmosDB Account.", @@ -1379,8 +1379,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "11250469246171251281" + "version": "0.27.1.19265", + "templateHash": "12755071159425783033" }, "name": "DocumentDB Database Account SQL Database Containers", "description": "This module deploys a SQL Database Container in a CosmosDB Account.", @@ -1622,8 +1622,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "7706794569430424680" + "version": "0.27.1.19265", + "templateHash": "1527748615955553712" }, "name": "DocumentDB Database Account MongoDB Databases", "description": "This module deploys a MongoDB Database within a CosmosDB Account.", @@ -1725,8 +1725,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "1989910090472113779" + "version": "0.27.1.19265", + "templateHash": "6235322895830297683" }, "name": "DocumentDB Database Account MongoDB Database Collections", "description": "This module deploys a MongoDB Database Collection.", @@ -1881,8 +1881,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "13414524346849431368" + "version": "0.27.1.19265", + "templateHash": "6889435067791947905" }, "name": "DocumentDB Database Account Gremlin Databases", "description": "This module deploys a Gremlin Database within a CosmosDB Account.", @@ -1985,8 +1985,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "2823004405398378621" + "version": "0.27.1.19265", + "templateHash": "3987394297554402770" }, "name": "DocumentDB Database Accounts Gremlin Databases Graphs", "description": "This module deploys a DocumentDB Database Accounts Gremlin Database Graph.", @@ -2848,8 +2848,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "3376784638805058420" + "version": "0.27.1.19265", + "templateHash": "17423703517558214368" } }, "definitions": { diff --git a/avm/res/managed-services/registration-definition/README.md b/avm/res/managed-services/registration-definition/README.md index 1197adf8ab..51a35a1643 100644 --- a/avm/res/managed-services/registration-definition/README.md +++ b/avm/res/managed-services/registration-definition/README.md @@ -412,6 +412,53 @@ Specify an array of objects, containing object of Azure Active Directory princip - Required: Yes - Type: array +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`principalId`](#parameter-authorizationsprincipalid) | string | The identifier of the Azure Active Directory principal. | +| [`roleDefinitionId`](#parameter-authorizationsroledefinitionid) | string | The identifier of the Azure built-in role that defines the permissions that the Azure Active Directory principal will have on the projected scope. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`delegatedRoleDefinitionIds`](#parameter-authorizationsdelegatedroledefinitionids) | array | The list of role definition ids which define all the permissions that the user in the authorization can assign to other principals. Required if the `roleDefinitionId` refers to the User Access Administrator Role. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`principalIdDisplayName`](#parameter-authorizationsprincipaliddisplayname) | string | The display name of the Azure Active Directory principal. | + +### Parameter: `authorizations.principalId` + +The identifier of the Azure Active Directory principal. + +- Required: Yes +- Type: string + +### Parameter: `authorizations.roleDefinitionId` + +The identifier of the Azure built-in role that defines the permissions that the Azure Active Directory principal will have on the projected scope. + +- Required: Yes +- Type: string + +### Parameter: `authorizations.delegatedRoleDefinitionIds` + +The list of role definition ids which define all the permissions that the user in the authorization can assign to other principals. Required if the `roleDefinitionId` refers to the User Access Administrator Role. + +- Required: No +- Type: array + +### Parameter: `authorizations.principalIdDisplayName` + +The display name of the Azure Active Directory principal. + +- Required: No +- Type: string + ### Parameter: `managedByTenantId` Specify the tenant ID of the tenant which homes the principals you are delegating permissions to. diff --git a/avm/utilities/pipelines/sharedScripts/Set-ModuleReadMe.ps1 b/avm/utilities/pipelines/sharedScripts/Set-ModuleReadMe.ps1 index 83c923f601..673e5929fe 100644 --- a/avm/utilities/pipelines/sharedScripts/Set-ModuleReadMe.ps1 +++ b/avm/utilities/pipelines/sharedScripts/Set-ModuleReadMe.ps1 @@ -248,16 +248,22 @@ function Set-DefinitionSection { $descriptions = $TemplateFileContent.parameters.Values.metadata.description # Add name as property for later reference $TemplateFileContent.parameters.Keys | ForEach-Object { $TemplateFileContent.parameters[$_]['name'] = $_ } + + # Error handling: Throw error if any parameter is missing a category + if ($paramsWithoutCategory = $TemplateFileContent.parameters.Values | Where-Object { $_.metadata.description -notmatch '^\w+?\.' }) { + $formattedParam = $paramsWithoutCategory | ForEach-Object { [PSCustomObject]@{ name = $_.name; description = $_.metadata.description } } | ConvertTo-Json -Compress + Write-Error ("Each parameter description should start with a category like [Required. / Optional. / Conditional. ]. The following parameters are missing such a category: `n$formattedParam`n") + } } else { $descriptions = $Properties.Values.metadata.description # Add name as property for later reference $Properties.Keys | ForEach-Object { $Properties[$_]['name'] = $_ } - } - # Error handling: Throw error if any parameter is missing a category - if ($paramsWithoutCategory = $TemplateFileContent.parameters.Values | Where-Object { $_.metadata.description -notmatch '^\w+?\.' }) { - $formattedParam = $paramsWithoutCategory | ForEach-Object { [PSCustomObject]@{ name = $_.name; description = $_.metadata.description } } | ConvertTo-Json -Compress - throw ("Each parameter description should start with a category like [Required. / Optional. / Conditional. ]. The following parameters are missing such a category: `n$formattedParam`n") + # Error handling: Throw error if any parameter is missing a category + if ($paramsWithoutCategory = $Properties.Values | Where-Object { $_.metadata.description -notmatch '^\w+?\.' }) { + $formattedParam = $paramsWithoutCategory | ForEach-Object { [PSCustomObject]@{ name = $_.name; description = $_.metadata.description } } | ConvertTo-Json -Compress + Write-Error ("Each parameter description should start with a category like [Required. / Optional. / Conditional. ]. The following parameters are missing such a category: `n$formattedParam`n") + } } # Get the module parameter categories @@ -306,10 +312,16 @@ function Set-DefinitionSection { $type = $definition['type'] $rawAllowedValues = $definition['allowedValues'] } elseif ($parameter.Keys -contains 'items' -and $parameter.items.type -in @('object', 'array') -or $parameter.type -eq 'object') { - # Array has nested non-primitive type (array/object) + # Array has nested non-primitive type (array/object) - and if array, the the UDT itself is declared as the array $definition = $parameter $type = $parameter.type $rawAllowedValues = $parameter.allowedValues + } elseif ($parameter.Keys -contains 'items' -and $parameter.items.keys -contains '$ref') { + # Array has nested non-primitive type (array) - and the parameter is defined as an array of the UDT + $identifier = Split-Path $parameter.items.'$ref' -Leaf + $definition = $TemplateFileContent.definitions[$identifier] + $type = $parameter.type + $rawAllowedValues = $definition['allowedValues'] } else { $definition = $null $type = $parameter.type From 17e37eecfff1a5f22e8f117ad8ffe8f7db4d7535 Mon Sep 17 00:00:00 2001 From: Rainer Halanek <61878316+rahalan@users.noreply.github.com> Date: Tue, 21 May 2024 16:08:36 +0200 Subject: [PATCH 02/18] feat: update logic to create a meaningful issue, if the module list template is out of sync with the CSV files (#1918) If the template file is not in sync with the CSV files, an issue was created. The old issue was not very helpful, thus the new issue will show exactly what it is about and shows in detail the diff. Here you can see how such an issue will look like: https://github.com/Azure/GHPolicyTest/issues/189 --- .../platform/Sync-AvmModulesList.ps1 | 134 ++++++++++++++---- 1 file changed, 109 insertions(+), 25 deletions(-) diff --git a/avm/utilities/pipelines/platform/Sync-AvmModulesList.ps1 b/avm/utilities/pipelines/platform/Sync-AvmModulesList.ps1 index 7a2af595fa..d816729452 100644 --- a/avm/utilities/pipelines/platform/Sync-AvmModulesList.ps1 +++ b/avm/utilities/pipelines/platform/Sync-AvmModulesList.ps1 @@ -30,42 +30,122 @@ function Sync-AvmModulesList { . (Join-Path $RepoRoot 'avm' 'utilities' 'pipelines' 'platform' 'helper' 'Get-AvmCsvData.ps1') . (Join-Path $RepoRoot 'avm' 'utilities' 'pipelines' 'platform' 'helper' 'Add-GithubIssueToProject.ps1') - $workflowFilePath = Join-Path $RepoRoot '.github' 'ISSUE_TEMPLATE' 'avm_module_issue.yml' - # get CSV data - $modules = Get-AvmCsvData -ModuleIndex 'Bicep-Resource' | Select-Object -Property 'ModuleName' - $patterns = Get-AvmCsvData -ModuleIndex 'Bicep-Pattern' | Select-Object -Property 'ModuleName' - - # build new strings - $prefix = ' - "' - $postfix = '"' - $newModuleLines = $modules | ForEach-Object { $prefix + $_.ModuleName + $postfix } - $newPatternLines = $patterns | ForEach-Object { $prefix + $_.ModuleName + $postfix } + $targetModules = Get-AvmCsvData -ModuleIndex 'Bicep-Resource' | Where-Object { ($_.ModuleStatus -eq 'Available :green_circle:') -or ($_.ModuleStatus -eq 'Orphaned :eyes:') } | Select-Object -ExpandProperty 'ModuleName' | Sort-Object + $targetPatterns = Get-AvmCsvData -ModuleIndex 'Bicep-Pattern' | Where-Object { ($_.ModuleStatus -eq 'Available :green_circle:') -or ($_.ModuleStatus -eq 'Orphaned :eyes:') } | Select-Object -ExpandProperty 'ModuleName' | Sort-Object - # parse workflow file - $workflowFileLines = Get-Content $workflowFilePath + $issueTemplatePath = Join-Path $RepoRoot '.github' 'ISSUE_TEMPLATE' 'avm_module_issue.yml' + $issueTemplateContent = Get-Content $issueTemplatePath + + # Identify listed modules $startIndex = 0 - $endIndex = 0 + while ($issueTemplateContent[$startIndex] -notmatch '^\s*#?\s*\-\s+\"avm\/.+\"' -and $startIndex -ne $issueTemplateContent.Length) { + $startIndex++ + } + + $endIndex = $startIndex + while ($issueTemplateContent[$endIndex] -match '.*- "avm\/.*' -and $endIndex -ne $issueTemplateContent.Length) { + $endIndex++ + } + $endIndex-- # Go one back to last module line + + $listedModules = $issueTemplateContent[$startIndex..$endIndex] | ForEach-Object { $_ -replace '.*- "(avm\/.*)".*', '$1' } | Where-Object { $_ -match 'avm\/res\/.*' } + $listedPatterns = $issueTemplateContent[$startIndex..$endIndex] | ForEach-Object { $_ -replace '.*- "(avm\/.*)".*', '$1' } | Where-Object { $_ -match 'avm\/ptn\/.*' } + + $body = '' + + $missingModules = $targetModules | Where { $listedModules -NotContains $_ } + $unexpectedModules = $listedModules | Where { $targetModules -NotContains $_ } + $unexpectedPatterns = $listedPatterns | Where { $targetPatterns -NotContains $_ } + $missingPatterns = $targetPatterns | Where { $listedPatterns -NotContains $_ } + + if ($missingModules.Count -gt 0) + { + $body += @" +**Missing resource modules:** + +$($missingModules -join ([Environment]::NewLine)) +$([Environment]::NewLine) +"@ + } + + if ($unexpectedModules.Count -gt 0) + { + $body += @" +**Unexpected resource modules:** + +$($unexpectedModules -join ([Environment]::NewLine)) +$([Environment]::NewLine) +"@ + } - for ($lineNumber = 0; $lineNumber -lt $workflowFileLines.Count; $lineNumber++) { - if ($startIndex -gt 0 -and (-not ($workflowFileLines[$lineNumber]).Trim().StartsWith('- "avm/'))) { - $endIndex = $lineNumber - break + if ($missingPatterns.Count -gt 0) + { + $body += @" +**Missing pattern modules:** + +$($missingPatterns -join ([Environment]::NewLine)) +$([Environment]::NewLine) +"@ + } + + if ($unexpectedPatterns.Count -gt 0) + { + $body += @" +**Unexpected pattern modules:** + +$($unexpectedPatterns -join ([Environment]::NewLine)) +$([Environment]::NewLine) +"@ + } + + # Should be at correct location + $incorrectModuleLines = @() + foreach ($finding in (Compare-Object $listedModules ($listedModules | Sort-Object) -SyncWindow 0)) { + if ($finding.SideIndicator -eq '<=') { + $incorrectModuleLines += $finding.InputObject } + } + $incorrectModuleLines = $incorrectModuleLines | Sort-Object -Unique - if (($workflowFileLines[$lineNumber]).Trim() -eq '- "Other, as defined below..."') { - $startIndex = $lineNumber + if ($incorrectModuleLines.Count -gt 0) + { + $body += @" +**Resource modules that are not correctly sorted:** + +$($incorrectModuleLines -join ([Environment]::NewLine)) +$([Environment]::NewLine) +"@ + } + + $incorrectPatternLines = @() + foreach ($finding in (Compare-Object $listedPatterns ($listedPatterns | Sort-Object) -SyncWindow 0)) { + if ($finding.SideIndicator -eq '<=') { + $incorrectPatternLines += $finding.InputObject } } + $incorrectPatternLines = $incorrectPatternLines | Sort-Object -Unique - $oldLines = $workflowFileLines[($startIndex + 1)..($endIndex - 1)] - $newLines = $newModuleLines + $newPatternLines - $body = $newLines -join ([Environment]::NewLine) + if ($incorrectPatternLines.Count -gt 0) + { + $body += @" +**Pattern modules that are not correctly sorted:** - if ($oldLines -ne $newLines) { - $title = '[AVM core] Module(s) missing from AVM Module Issue template' +$($incorrectPatternLines -join ([Environment]::NewLine)) +$([Environment]::NewLine) +"@ + } + + if ($body -ne '') { + $title = '[AVM core] AVM Module Issue template is not in sync with published resource modules and pattern modules list' $label = 'Type: AVM :a: :v: :m:,Type: Hygiene :broom:,Needs: Triage :mag:' - $issues = gh issue list --state open --limit 500 --label $label --json 'title' --repo $Repo | ConvertFrom-Json -Depth 100 + $issues = gh issue list --state open --limit 500 --label $label --json 'title,url' --repo $Repo | ConvertFrom-Json -Depth 100 + + $body = @" +> [!IMPORTANT] +> The file [avm_module_issue.yml](https://github.com/Azure/bicep-registry-modules/blob/main/.github/ISSUE_TEMPLATE/avm_module_issue.yml?plain=1) which lists all modules when creating a new issue, is not in sync with the CSV files, that can be found under [resource modules](https://aka.ms/avm/index/bicep/res/csv) and [pattern modules](https://aka.ms/avm/index/bicep/ptn/csv). These CSV files are the single source of truth regarding published modules. Please update the ``avm_module_issue.yml`` accordingly. Please see the following differences that were found. +$([Environment]::NewLine) +"@ + $body if ($issues.title -notcontains $title) { # create issue @@ -74,5 +154,9 @@ function Sync-AvmModulesList { $ProjectNumber = 538 # AVM - Issue Triage Add-GithubIssueToProject -Repo $Repo -ProjectNumber $ProjectNumber -IssueUrl $issueUrl } + else { + # update body + gh issue edit $issues[0].url --body $body --repo $Repo + } } } From 7954f1657dd5494ff730f5f12ba4ec2f9b40e5e7 Mon Sep 17 00:00:00 2001 From: rodney-almeida <64196999+rodney-almeida@users.noreply.github.com> Date: Tue, 21 May 2024 15:23:48 +0100 Subject: [PATCH 03/18] fix: Add check to only apply quarantinePolicyStatus and trustPolicyStatus on Premium SKUs (#1967) --- avm/res/container-registry/registry/README.md | 8 +- .../container-registry/registry/main.bicep | 109 +++++++++--------- avm/res/container-registry/registry/main.json | 31 +++-- 3 files changed, 71 insertions(+), 77 deletions(-) diff --git a/avm/res/container-registry/registry/README.md b/avm/res/container-registry/registry/README.md index 5d0fd4ae87..90db8524cd 100644 --- a/avm/res/container-registry/registry/README.md +++ b/avm/res/container-registry/registry/README.md @@ -611,7 +611,7 @@ module registry 'br/public:avm/res/container-registry/registry:' = { | [`networkRuleSetIpRules`](#parameter-networkrulesetiprules) | array | The IP ACL rules. Note, requires the 'acrSku' to be 'Premium'. | | [`privateEndpoints`](#parameter-privateendpoints) | array | Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. Note, requires the 'acrSku' to be 'Premium'. | | [`publicNetworkAccess`](#parameter-publicnetworkaccess) | string | 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 and networkRuleSetIpRules are not set. Note, requires the 'acrSku' to be 'Premium'. | -| [`quarantinePolicyStatus`](#parameter-quarantinepolicystatus) | string | The value that indicates whether the quarantine policy is enabled or not. | +| [`quarantinePolicyStatus`](#parameter-quarantinepolicystatus) | string | The value that indicates whether the quarantine policy is enabled or not. Note, requires the 'acrSku' to be 'Premium'. | | [`replications`](#parameter-replications) | array | All replications to create. | | [`retentionPolicyDays`](#parameter-retentionpolicydays) | int | The number of days to retain an untagged manifest after which it gets purged. | | [`retentionPolicyStatus`](#parameter-retentionpolicystatus) | string | The value that indicates whether the retention policy is enabled or not. | @@ -619,7 +619,7 @@ module registry 'br/public:avm/res/container-registry/registry:' = { | [`softDeletePolicyDays`](#parameter-softdeletepolicydays) | int | The number of days after which a soft-deleted item is permanently deleted. | | [`softDeletePolicyStatus`](#parameter-softdeletepolicystatus) | string | Soft Delete policy status. Default is disabled. | | [`tags`](#parameter-tags) | object | Tags of the resource. | -| [`trustPolicyStatus`](#parameter-trustpolicystatus) | string | The value that indicates whether the trust policy is enabled or not. | +| [`trustPolicyStatus`](#parameter-trustpolicystatus) | string | The value that indicates whether the trust policy is enabled or not. Note, requires the 'acrSku' to be 'Premium'. | | [`webhooks`](#parameter-webhooks) | array | All webhooks to create. | | [`zoneRedundancy`](#parameter-zoneredundancy) | string | Whether or not zone redundancy is enabled for this container registry. | @@ -1362,7 +1362,7 @@ Whether or not public network access is allowed for this resource. For security ### Parameter: `quarantinePolicyStatus` -The value that indicates whether the quarantine policy is enabled or not. +The value that indicates whether the quarantine policy is enabled or not. Note, requires the 'acrSku' to be 'Premium'. - Required: No - Type: string @@ -1526,7 +1526,7 @@ Tags of the resource. ### Parameter: `trustPolicyStatus` -The value that indicates whether the trust policy is enabled or not. +The value that indicates whether the trust policy is enabled or not. Note, requires the 'acrSku' to be 'Premium'. - Required: No - Type: string diff --git a/avm/res/container-registry/registry/main.bicep b/avm/res/container-registry/registry/main.bicep index 126074e066..059b59f804 100644 --- a/avm/res/container-registry/registry/main.bicep +++ b/avm/res/container-registry/registry/main.bicep @@ -35,14 +35,14 @@ param exportPolicyStatus string = 'disabled' 'disabled' 'enabled' ]) -@description('Optional. The value that indicates whether the quarantine policy is enabled or not.') +@description('Optional. The value that indicates whether the quarantine policy is enabled or not. Note, requires the \'acrSku\' to be \'Premium\'.') param quarantinePolicyStatus string = 'disabled' @allowed([ 'disabled' 'enabled' ]) -@description('Optional. The value that indicates whether the trust policy is enabled or not.') +@description('Optional. The value that indicates whether the trust policy is enabled or not. Note, requires the \'acrSku\' to be \'Premium\'.') param trustPolicyStatus string = 'disabled' @allowed([ @@ -148,8 +148,8 @@ var formattedUserAssignedIdentities = reduce( var identity = !empty(managedIdentities) ? { type: (managedIdentities.?systemAssigned ?? false) - ? (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') - : (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : null) + ? (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'SystemAssigned, UserAssigned' : 'SystemAssigned') + : (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : 'None') userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null } : null @@ -183,47 +183,43 @@ var builtInRoleNames = { ) } -resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = - if (enableTelemetry) { - name: '46d3xbcp.res.containerregistry-registry.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' - properties: { - mode: 'Incremental' - template: { - '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' - contentVersion: '1.0.0.0' - resources: [] - outputs: { - telemetry: { - type: 'String' - value: 'For more information, see https://aka.ms/avm/TelemetryInfo' - } +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { + name: '46d3xbcp.res.containerregistry-registry.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + outputs: { + telemetry: { + type: 'String' + value: 'For more information, see https://aka.ms/avm/TelemetryInfo' } } } } +} -resource cMKKeyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = - if (!empty(customerManagedKey.?keyVaultResourceId)) { - name: last(split((customerManagedKey.?keyVaultResourceId ?? 'dummyVault'), '/')) - scope: resourceGroup( - split((customerManagedKey.?keyVaultResourceId ?? '//'), '/')[2], - split((customerManagedKey.?keyVaultResourceId ?? '////'), '/')[4] - ) - - resource cMKKey 'keys@2023-02-01' existing = - if (!empty(customerManagedKey.?keyVaultResourceId) && !empty(customerManagedKey.?keyName)) { - name: customerManagedKey.?keyName ?? 'dummyKey' - } - } +resource cMKKeyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId)) { + name: last(split((customerManagedKey.?keyVaultResourceId ?? 'dummyVault'), '/')) + scope: resourceGroup( + split((customerManagedKey.?keyVaultResourceId ?? '//'), '/')[2], + split((customerManagedKey.?keyVaultResourceId ?? '////'), '/')[4] + ) -resource cMKUserAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = - if (!empty(customerManagedKey.?userAssignedIdentityResourceId)) { - name: last(split(customerManagedKey.?userAssignedIdentityResourceId ?? 'dummyMsi', '/')) - scope: resourceGroup( - split((customerManagedKey.?userAssignedIdentityResourceId ?? '//'), '/')[2], - split((customerManagedKey.?userAssignedIdentityResourceId ?? '////'), '/')[4] - ) + resource cMKKey 'keys@2023-02-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId) && !empty(customerManagedKey.?keyName)) { + name: customerManagedKey.?keyName ?? 'dummyKey' } +} + +resource cMKUserAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(customerManagedKey.?userAssignedIdentityResourceId)) { + name: last(split(customerManagedKey.?userAssignedIdentityResourceId ?? 'dummyMsi', '/')) + scope: resourceGroup( + split((customerManagedKey.?userAssignedIdentityResourceId ?? '//'), '/')[2], + split((customerManagedKey.?userAssignedIdentityResourceId ?? '////'), '/')[4] + ) +} resource registry 'Microsoft.ContainerRegistry/registries@2023-06-01-preview' = { name: name @@ -258,13 +254,17 @@ resource registry 'Microsoft.ContainerRegistry/registries@2023-06-01-preview' = status: exportPolicyStatus } : null - quarantinePolicy: { - status: quarantinePolicyStatus - } - trustPolicy: { - type: 'Notary' - status: trustPolicyStatus - } + quarantinePolicy: acrSku == 'Premium' + ? { + status: quarantinePolicyStatus + } + : null + trustPolicy: acrSku == 'Premium' + ? { + type: 'Notary' + status: trustPolicyStatus + } + : null retentionPolicy: acrSku == 'Premium' ? { days: retentionPolicyDays @@ -341,17 +341,16 @@ module registry_webhooks 'webhook/main.bicep' = [ } ] -resource registry_lock 'Microsoft.Authorization/locks@2020-05-01' = - if (!empty(lock ?? {}) && lock.?kind != 'None') { - name: lock.?name ?? 'lock-${name}' - properties: { - level: lock.?kind ?? '' - notes: lock.?kind == 'CanNotDelete' - ? 'Cannot delete resource or child resources.' - : 'Cannot delete or modify the resource or child resources.' - } - scope: registry +resource registry_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' + ? 'Cannot delete resource or child resources.' + : 'Cannot delete or modify the resource or child resources.' } + scope: registry +} resource registry_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [ for (diagnosticSetting, index) in (diagnosticSettings ?? []): { diff --git a/avm/res/container-registry/registry/main.json b/avm/res/container-registry/registry/main.json index b176210972..d9cba42298 100644 --- a/avm/res/container-registry/registry/main.json +++ b/avm/res/container-registry/registry/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "15445287807627348186" + "version": "0.27.1.19265", + "templateHash": "10889694252440800848" }, "name": "Azure Container Registries (ACR)", "description": "This module deploys an Azure Container Registry (ACR).", @@ -523,7 +523,7 @@ "enabled" ], "metadata": { - "description": "Optional. The value that indicates whether the quarantine policy is enabled or not." + "description": "Optional. The value that indicates whether the quarantine policy is enabled or not. Note, requires the 'acrSku' to be 'Premium'." } }, "trustPolicyStatus": { @@ -534,7 +534,7 @@ "enabled" ], "metadata": { - "description": "Optional. The value that indicates whether the trust policy is enabled or not." + "description": "Optional. The value that indicates whether the trust policy is enabled or not. Note, requires the 'acrSku' to be 'Premium'." } }, "retentionPolicyStatus": { @@ -717,7 +717,7 @@ }, "variables": { "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", - "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', null())), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned, UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", "builtInRoleNames": { "AcrDelete": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c2f4ef07-c644-48eb-af81-4b1b4947fb11')]", "AcrImageSigner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6cef56e8-d556-48e5-a04f-b8e64114680f')]", @@ -802,13 +802,8 @@ "status": "[parameters('azureADAuthenticationAsArmPolicyStatus')]" }, "exportPolicy": "[if(equals(parameters('acrSku'), 'Premium'), createObject('status', parameters('exportPolicyStatus')), null())]", - "quarantinePolicy": { - "status": "[parameters('quarantinePolicyStatus')]" - }, - "trustPolicy": { - "type": "Notary", - "status": "[parameters('trustPolicyStatus')]" - }, + "quarantinePolicy": "[if(equals(parameters('acrSku'), 'Premium'), createObject('status', parameters('quarantinePolicyStatus')), null())]", + "trustPolicy": "[if(equals(parameters('acrSku'), 'Premium'), createObject('type', 'Notary', 'status', parameters('trustPolicyStatus')), null())]", "retentionPolicy": "[if(equals(parameters('acrSku'), 'Premium'), createObject('days', parameters('retentionPolicyDays'), 'status', parameters('retentionPolicyStatus')), null())]", "softDeletePolicy": { "retentionDays": "[parameters('softDeletePolicyDays')]", @@ -943,8 +938,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16451316437757175736" + "version": "0.27.1.19265", + "templateHash": "17370607380629293508" }, "name": "Azure Container Registry (ACR) Replications", "description": "This module deploys an Azure Container Registry (ACR) Replication.", @@ -1091,8 +1086,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "17108035841365544326" + "version": "0.27.1.19265", + "templateHash": "15228477210534278013" }, "name": "Container Registries Cache", "description": "Cache for Azure Container Registry (Preview) feature allows users to cache container images in a private container registry. Cache for ACR, is a preview feature available in Basic, Standard, and Premium service tiers ([ref](https://learn.microsoft.com/en-us/azure/container-registry/tutorial-registry-cache)).", @@ -1232,8 +1227,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "10731035117081750792" + "version": "0.27.1.19265", + "templateHash": "12261942841024526503" }, "name": "Azure Container Registry (ACR) Webhooks", "description": "This module deploys an Azure Container Registry (ACR) Webhook.", From 5cd812498a197ef7268bddf41114127b4c77146a Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Tue, 21 May 2024 18:24:02 +0200 Subject: [PATCH 04/18] fix: Fixed nested UDT resolution in compliance tests (#1971) ## Description - Added missing case from parameter resolution in tests similar to #1970 - Fixed Cosmos DB - Ran script for all modules Supporting ```bicep param test myArrayType[] type myArrayType = { ... } ``` Before it was only ```bicep param test myArrayType type myArrayType = { ... }[] ``` ## Pipeline Reference | Pipeline | | -------- | [![avm.ptn.authorization.policy-assignment](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.authorization.policy-assignment.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.authorization.policy-assignment.yml) [![avm.ptn.authorization.role-assignment](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.authorization.role-assignment.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.authorization.role-assignment.yml) [![avm.ptn.policy-insights.remediation](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.policy-insights.remediation.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.policy-insights.remediation.yml) [![avm.ptn.security.security-center](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.security.security-center.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.security.security-center.yml) [![avm.res.aad.domain-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.aad.domain-service.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.aad.domain-service.yml) [![avm.res.analysis-services.server](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.analysis-services.server.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.analysis-services.server.yml) [![avm.res.api-management.service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.api-management.service.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.api-management.service.yml) [![avm.res.app-configuration.configuration-store](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app-configuration.configuration-store.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app-configuration.configuration-store.yml) [![avm.res.app.container-app](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.container-app.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.container-app.yml) [![avm.res.app.job](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.job.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.job.yml) [![avm.res.app.managed-environment](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.managed-environment.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.managed-environment.yml) [![avm.res.automation.automation-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.automation.automation-account.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.automation.automation-account.yml) [![avm.res.batch.batch-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.batch.batch-account.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.batch.batch-account.yml) [![avm.res.cache.redis](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cache.redis.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cache.redis.yml) [![avm.res.cdn.profile](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cdn.profile.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cdn.profile.yml) [![avm.res.cognitive-services.account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cognitive-services.account.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cognitive-services.account.yml) [![avm.res.communication.communication-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.communication.communication-service.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.communication.communication-service.yml) [![avm.res.communication.email-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.communication.email-service.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.communication.email-service.yml) [![avm.res.compute.availability-set](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.availability-set.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.availability-set.yml) [![avm.res.compute.disk-encryption-set](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.disk-encryption-set.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.disk-encryption-set.yml) [![avm.res.compute.disk](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.disk.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.disk.yml) [![avm.res.compute.gallery](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.gallery.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.gallery.yml) [![avm.res.compute.image](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.image.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.image.yml) [![avm.res.compute.proximity-placement-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.proximity-placement-group.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.proximity-placement-group.yml) [![avm.res.compute.ssh-public-key](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.ssh-public-key.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.ssh-public-key.yml) [![avm.res.compute.virtual-machine-scale-set](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine-scale-set.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine-scale-set.yml) [![avm.res.compute.virtual-machine](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine.yml) [![avm.res.consumption.budget](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.consumption.budget.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.consumption.budget.yml) [![avm.res.container-instance.container-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-instance.container-group.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-instance.container-group.yml) [![avm.res.container-registry.registry](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-registry.registry.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-registry.registry.yml) [![avm.res.container-service.managed-cluster](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml) [![avm.res.data-factory.factory](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.data-factory.factory.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.data-factory.factory.yml) [![avm.res.data-protection.backup-vault](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.data-protection.backup-vault.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.data-protection.backup-vault.yml) [![avm.res.databricks.access-connector](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.databricks.access-connector.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.databricks.access-connector.yml) [![avm.res.databricks.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.databricks.workspace.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.databricks.workspace.yml) [![avm.res.db-for-my-sql.flexible-server](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.db-for-my-sql.flexible-server.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.db-for-my-sql.flexible-server.yml) [![avm.res.db-for-postgre-sql.flexible-server](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.db-for-postgre-sql.flexible-server.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.db-for-postgre-sql.flexible-server.yml) (unrelated) [![avm.res.desktop-virtualization.application-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.application-group.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.application-group.yml) [![avm.res.desktop-virtualization.host-pool](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.host-pool.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.host-pool.yml) [![avm.res.desktop-virtualization.scaling-plan](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.scaling-plan.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.scaling-plan.yml) [![avm.res.desktop-virtualization.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.workspace.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.workspace.yml) [![avm.res.dev-test-lab.lab](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.dev-test-lab.lab.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.dev-test-lab.lab.yml) [![avm.res.digital-twins.digital-twins-instance](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.digital-twins.digital-twins-instance.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.digital-twins.digital-twins-instance.yml) [![avm.res.document-db.database-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.document-db.database-account.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.document-db.database-account.yml) [![avm.res.event-grid.domain](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.domain.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.domain.yml) [![avm.res.event-grid.namespace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.namespace.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.namespace.yml) [![avm.res.event-grid.system-topic](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.system-topic.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.system-topic.yml) [![avm.res.event-grid.topic](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.topic.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.topic.yml) [![avm.res.event-hub.namespace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-hub.namespace.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-hub.namespace.yml) [![avm.res.health-bot.health-bot](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.health-bot.health-bot.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.health-bot.health-bot.yml) [![avm.res.healthcare-apis.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.healthcare-apis.workspace.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.healthcare-apis.workspace.yml) [![avm.res.insights.action-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.action-group.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.action-group.yml) [![avm.res.insights.activity-log-alert](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.activity-log-alert.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.activity-log-alert.yml) [![avm.res.insights.component](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.component.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.component.yml) [![avm.res.insights.data-collection-endpoint](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-endpoint.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-endpoint.yml) [![avm.res.insights.data-collection-rule](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-rule.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-rule.yml) [![avm.res.insights.diagnostic-setting](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.diagnostic-setting.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.diagnostic-setting.yml) [![avm.res.insights.metric-alert](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.metric-alert.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.metric-alert.yml) [![avm.res.insights.private-link-scope](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.private-link-scope.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.private-link-scope.yml) [![avm.res.insights.scheduled-query-rule](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.scheduled-query-rule.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.scheduled-query-rule.yml) [![avm.res.insights.webtest](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.webtest.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.webtest.yml) [![avm.res.key-vault.vault](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml) [![avm.res.kubernetes-configuration.extension](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kubernetes-configuration.extension.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kubernetes-configuration.extension.yml) [![avm.res.kubernetes-configuration.flux-configuration](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kubernetes-configuration.flux-configuration.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kubernetes-configuration.flux-configuration.yml) [![avm.res.load-test-service.load-test](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.load-test-service.load-test.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.load-test-service.load-test.yml) [![avm.res.logic.workflow](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.logic.workflow.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.logic.workflow.yml) [![avm.res.machine-learning-services.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.machine-learning-services.workspace.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.machine-learning-services.workspace.yml) [![avm.res.maintenance.maintenance-configuration](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.maintenance.maintenance-configuration.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.maintenance.maintenance-configuration.yml) [![avm.res.managed-identity.user-assigned-identity](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.managed-identity.user-assigned-identity.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.managed-identity.user-assigned-identity.yml) [![avm.res.managed-services.registration-definition](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.managed-services.registration-definition.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.managed-services.registration-definition.yml) [![avm.res.management.management-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.management.management-group.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.management.management-group.yml) [![avm.res.net-app.net-app-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.net-app.net-app-account.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.net-app.net-app-account.yml) [![avm.res.network.application-gateway-web-application-firewall-policy](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway-web-application-firewall-policy.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway-web-application-firewall-policy.yml) [![avm.res.network.application-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway.yml) [![avm.res.network.application-security-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-security-group.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-security-group.yml) [![avm.res.network.azure-firewall](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.azure-firewall.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.azure-firewall.yml) [![avm.res.network.bastion-host](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.bastion-host.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.bastion-host.yml) [![avm.res.network.connection](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.connection.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.connection.yml) [![avm.res.network.ddos-protection-plan](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.ddos-protection-plan.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.ddos-protection-plan.yml) [![avm.res.network.dns-forwarding-ruleset](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-forwarding-ruleset.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-forwarding-ruleset.yml) [![avm.res.network.dns-resolver](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-resolver.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-resolver.yml) [![avm.res.network.dns-zone](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-zone.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-zone.yml) [![avm.res.network.express-route-circuit](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.express-route-circuit.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.express-route-circuit.yml) [![avm.res.network.express-route-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.express-route-gateway.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.express-route-gateway.yml) [![avm.res.network.firewall-policy](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.firewall-policy.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.firewall-policy.yml) [![avm.res.network.front-door-web-application-firewall-policy](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.front-door-web-application-firewall-policy.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.front-door-web-application-firewall-policy.yml) [![avm.res.network.front-door](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.front-door.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.front-door.yml) [![avm.res.network.ip-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.ip-group.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.ip-group.yml) [![avm.res.network.load-balancer](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.load-balancer.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.load-balancer.yml) [![avm.res.network.local-network-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.local-network-gateway.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.local-network-gateway.yml) [![avm.res.network.nat-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.nat-gateway.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.nat-gateway.yml) [![avm.res.network.network-interface](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-interface.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-interface.yml) [![avm.res.network.network-manager](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-manager.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-manager.yml) [![avm.res.network.network-security-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-security-group.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-security-group.yml) [![avm.res.network.network-watcher](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-watcher.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-watcher.yml) [![avm.res.network.private-dns-zone](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-dns-zone.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-dns-zone.yml) [![avm.res.network.private-endpoint](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-endpoint.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-endpoint.yml) [![avm.res.network.private-link-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-link-service.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-link-service.yml) [![avm.res.network.public-ip-address](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-address.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-address.yml) [![avm.res.network.public-ip-prefix](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-prefix.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-prefix.yml) [![avm.res.network.route-table](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.route-table.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.route-table.yml) [![avm.res.network.service-endpoint-policy](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.service-endpoint-policy.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.service-endpoint-policy.yml) [![avm.res.network.trafficmanagerprofile](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.trafficmanagerprofile.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.trafficmanagerprofile.yml) [![avm.res.network.virtual-hub](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-hub.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-hub.yml) [![avm.res.network.virtual-network-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network-gateway.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network-gateway.yml) [![avm.res.network.virtual-network](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network.yml) [![avm.res.network.virtual-wan](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-wan.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-wan.yml) [![avm.res.network.vpn-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.vpn-gateway.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.vpn-gateway.yml) [![avm.res.network.vpn-site](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.vpn-site.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.vpn-site.yml) [![avm.res.operational-insights.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.operational-insights.workspace.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.operational-insights.workspace.yml) [![avm.res.operations-management.solution](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.operations-management.solution.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.operations-management.solution.yml) [![avm.res.power-bi-dedicated.capacity](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.power-bi-dedicated.capacity.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.power-bi-dedicated.capacity.yml) [![avm.res.purview.account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.purview.account.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.purview.account.yml) [![avm.res.recovery-services.vault](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.recovery-services.vault.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.recovery-services.vault.yml) [![avm.res.relay.namespace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.relay.namespace.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.relay.namespace.yml) [![avm.res.resource-graph.query](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resource-graph.query.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resource-graph.query.yml) [![avm.res.resources.deployment-script](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resources.deployment-script.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resources.deployment-script.yml) [![avm.res.resources.resource-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resources.resource-group.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resources.resource-group.yml) [![avm.res.search.search-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.search.search-service.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.search.search-service.yml) [![avm.res.service-bus.namespace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.service-bus.namespace.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.service-bus.namespace.yml) [![avm.res.service-fabric.cluster](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.service-fabric.cluster.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.service-fabric.cluster.yml) [![avm.res.signal-r-service.signal-r](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.signal-r.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.signal-r.yml) [![avm.res.signal-r-service.web-pub-sub](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.web-pub-sub.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.web-pub-sub.yml) [![avm.res.sql.instance-pool](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.instance-pool.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.instance-pool.yml) [![avm.res.sql.managed-instance](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.managed-instance.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.managed-instance.yml) [![avm.res.sql.server](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.server.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.server.yml) [![avm.res.storage.storage-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml) (unrelated) [![avm.res.synapse.private-link-hub](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.synapse.private-link-hub.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.synapse.private-link-hub.yml) [![avm.res.synapse.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.synapse.workspace.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.synapse.workspace.yml) [![avm.res.virtual-machine-images.image-template](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.virtual-machine-images.image-template.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.virtual-machine-images.image-template.yml) [![avm.res.web.connection](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.connection.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.connection.yml) [![avm.res.web.hosting-environment](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.hosting-environment.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.hosting-environment.yml) [![avm.res.web.serverfarm](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.serverfarm.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.serverfarm.yml) [![avm.res.web.site](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.site.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.site.yml) [![avm.res.web.static-site](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.static-site.yml/badge.svg?branch=users%2Falsehr%2FudtTestsFix&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.static-site.yml) ## Type of Change - [x] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- avm/res/document-db/database-account/README.md | 8 ++++---- avm/res/document-db/database-account/main.bicep | 4 ++-- avm/res/document-db/database-account/main.json | 6 +++--- .../staticValidation/compliance/helper/helper.psm1 | 6 +++++- .../staticValidation/compliance/module.tests.ps1 | 4 ++-- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/avm/res/document-db/database-account/README.md b/avm/res/document-db/database-account/README.md index 30434814bc..73c60b1d9b 100644 --- a/avm/res/document-db/database-account/README.md +++ b/avm/res/document-db/database-account/README.md @@ -2594,7 +2594,7 @@ Default to the location where the account is deployed. Locations enabled for the | Parameter | Type | Description | | :-- | :-- | :-- | -| [`isZoneRedundant`](#parameter-locationsiszoneredundant) | bool | Default to true. Flag to indicate whether or not this region is an AvailabilityZone region | +| [`isZoneRedundant`](#parameter-locationsiszoneredundant) | bool | Default to true. Flag to indicate whether or not this region is an AvailabilityZone region. | ### Parameter: `locations.failoverPriority` @@ -2612,7 +2612,7 @@ The name of the region. ### Parameter: `locations.isZoneRedundant` -Default to true. Flag to indicate whether or not this region is an AvailabilityZone region +Default to true. Flag to indicate whether or not this region is an AvailabilityZone region. - Required: No - Type: bool @@ -3507,11 +3507,11 @@ The unique key policy configuration containing a list of unique keys that enforc | Parameter | Type | Description | | :-- | :-- | :-- | -| [`paths`](#parameter-sqldatabasescontainersuniquekeypolicykeyspaths) | array | List of paths must be unique for each document in the Azure Cosmos DB service | +| [`paths`](#parameter-sqldatabasescontainersuniquekeypolicykeyspaths) | array | List of paths must be unique for each document in the Azure Cosmos DB service. | ### Parameter: `sqlDatabases.containers.uniqueKeyPolicyKeys.paths` -List of paths must be unique for each document in the Azure Cosmos DB service +List of paths must be unique for each document in the Azure Cosmos DB service. - Required: Yes - Type: array diff --git a/avm/res/document-db/database-account/main.bicep b/avm/res/document-db/database-account/main.bicep index 65501fdbbf..9e95fc47a2 100644 --- a/avm/res/document-db/database-account/main.bicep +++ b/avm/res/document-db/database-account/main.bicep @@ -715,7 +715,7 @@ type failoverLocationsType = { @description('Required. The failover priority of the region. A failover priority of 0 indicates a write region. The maximum value for a failover priority = (total number of regions - 1). Failover priority values must be unique for each of the regions in which the database account exists.') failoverPriority: int - @description('Optional. Default to true. Flag to indicate whether or not this region is an AvailabilityZone region') + @description('Optional. Default to true. Flag to indicate whether or not this region is an AvailabilityZone region.') isZoneRedundant: bool? @description('Required. The name of the region.') @@ -777,7 +777,7 @@ type sqlDatabaseType = { @description('Optional. The unique key policy configuration containing a list of unique keys that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service.') uniqueKeyPolicyKeys: { - @description('Required. List of paths must be unique for each document in the Azure Cosmos DB service') + @description('Required. List of paths must be unique for each document in the Azure Cosmos DB service.') paths: string[] }[]? }[]? diff --git a/avm/res/document-db/database-account/main.json b/avm/res/document-db/database-account/main.json index e1a6ae712b..f5121fe2cd 100644 --- a/avm/res/document-db/database-account/main.json +++ b/avm/res/document-db/database-account/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "13227324731926560272" + "templateHash": "8399904898851436683" }, "name": "DocumentDB Database Accounts", "description": "This module deploys a DocumentDB Database Account.", @@ -456,7 +456,7 @@ "type": "bool", "nullable": true, "metadata": { - "description": "Optional. Default to true. Flag to indicate whether or not this region is an AvailabilityZone region" + "description": "Optional. Default to true. Flag to indicate whether or not this region is an AvailabilityZone region." } }, "locationName": { @@ -605,7 +605,7 @@ "type": "string" }, "metadata": { - "description": "Required. List of paths must be unique for each document in the Azure Cosmos DB service" + "description": "Required. List of paths must be unique for each document in the Azure Cosmos DB service." } } } diff --git a/avm/utilities/pipelines/staticValidation/compliance/helper/helper.psm1 b/avm/utilities/pipelines/staticValidation/compliance/helper/helper.psm1 index cd761d66a9..d5839c3caf 100644 --- a/avm/utilities/pipelines/staticValidation/compliance/helper/helper.psm1 +++ b/avm/utilities/pipelines/staticValidation/compliance/helper/helper.psm1 @@ -246,8 +246,12 @@ function Resolve-ReadMeParameterList { $definition = $TemplateFileContent.definitions[$identifier] # $type = $definition['type'] } elseif ($parameter.Keys -contains 'items' -and $parameter.items.type -in @('object', 'array') -or $parameter.type -eq 'object') { - # Array has nested non-primitive type (array/object) + # Array has nested non-primitive type (array/object) - and if array, the the UDT itself is declared as the array $definition = $parameter + } elseif ($parameter.Keys -contains 'items' -and $parameter.items.keys -contains '$ref') { + # Array has nested non-primitive type (array) - and the parameter is defined as an array of the UDT + $identifier = Split-Path $parameter.items.'$ref' -Leaf + $definition = $TemplateFileContent.definitions[$identifier] } else { $definition = $null } diff --git a/avm/utilities/pipelines/staticValidation/compliance/module.tests.ps1 b/avm/utilities/pipelines/staticValidation/compliance/module.tests.ps1 index 86e8253752..6ac67f95ac 100644 --- a/avm/utilities/pipelines/staticValidation/compliance/module.tests.ps1 +++ b/avm/utilities/pipelines/staticValidation/compliance/module.tests.ps1 @@ -814,9 +814,9 @@ Describe 'Module tests' -Tag 'Module' { $Variables = $templateFileContent.variables.Keys foreach ($variable in $Variables) { - # ^[a-z]+[a-zA-Z]+$ = starts with lower-case letter & may have uppercase letter later + # ^[a-z]+[a-zA-Z0-9]+$ = starts with lower-case letter & may have uppercase letter or numbers later # ^\$fxv#[0-9]+$ = starts with [$fxv#] & ends with a number. This function value is created as a variable when using a Bicep function like loadFileAsBase64() or loadFromJson() - if ($variable -cnotmatch '^[a-z]+[a-zA-Z]+$|^\$fxv#[0-9]+$' -or $variable -match '-') { + if ($variable -cnotmatch '^[a-z]+[a-zA-Z0-9]+$|^\$fxv#[0-9]+$' -or $variable -match '-') { $incorrectVariables += $variable } } From 07699bdb709809e49fd61d85b931f280a6407d58 Mon Sep 17 00:00:00 2001 From: MichielVanHerreweghe <169037533+MichielVanHerreweghe@users.noreply.github.com> Date: Tue, 21 May 2024 22:10:02 +0200 Subject: [PATCH 05/18] feat: Container App added Sticky Session Afinity options (#1936) ## Description Added the option to enable session affinity on the container app ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.app.container-app](https://github.com/MichielVanHerreweghe/bicep-registry-modules/actions/workflows/avm.res.app.container-app.yml/badge.svg?branch=users%2Fmichielvanherreweghe%2Fcontainer-app-ingress-affinity)](https://github.com/MichielVanHerreweghe/bicep-registry-modules/actions/workflows/avm.res.app.container-app.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [x] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Zach Trocinski <30884663+oZakari@users.noreply.github.com> --- avm/res/app/container-app/README.md | 16 ++++++++ avm/res/app/container-app/main.bicep | 56 +++++++++++++++----------- avm/res/app/container-app/main.json | 18 ++++++++- avm/res/app/container-app/version.json | 4 +- 4 files changed, 66 insertions(+), 28 deletions(-) diff --git a/avm/res/app/container-app/README.md b/avm/res/app/container-app/README.md index 236de18344..794ec770c0 100644 --- a/avm/res/app/container-app/README.md +++ b/avm/res/app/container-app/README.md @@ -484,6 +484,7 @@ module containerApp 'br/public:avm/res/app/container-app:' = { | [`scaleMinReplicas`](#parameter-scaleminreplicas) | int | Minimum number of container replicas. Defaults to 3 if not set. | | [`scaleRules`](#parameter-scalerules) | array | Scaling rules. | | [`secrets`](#parameter-secrets) | secureObject | The secrets of the Container App. | +| [`stickySessionsAffinity`](#parameter-stickysessionsaffinity) | string | Bool indicating if the Container App should enable session affinity. | | [`tags`](#parameter-tags) | object | Tags of the resource. | | [`trafficLabel`](#parameter-trafficlabel) | string | Associates a traffic label with a revision. Label name should be consist of lower case alphanumeric characters or dashes. | | [`trafficLatestRevision`](#parameter-trafficlatestrevision) | bool | Indicates that the traffic weight belongs to a latest stable revision. | @@ -834,6 +835,21 @@ The secrets of the Container App. - Type: secureObject - Default: `{}` +### Parameter: `stickySessionsAffinity` + +Bool indicating if the Container App should enable session affinity. + +- Required: No +- Type: string +- Default: `'none'` +- Allowed: + ```Bicep + [ + 'none' + 'sticky' + ] + ``` + ### Parameter: `tags` Tags of the resource. diff --git a/avm/res/app/container-app/main.bicep b/avm/res/app/container-app/main.bicep index 7ef6ed7246..8ff404eaf9 100644 --- a/avm/res/app/container-app/main.bicep +++ b/avm/res/app/container-app/main.bicep @@ -11,6 +11,13 @@ param location string = resourceGroup().location @description('Optional. Bool indicating if the App exposes an external HTTP endpoint.') param ingressExternal bool = true +@allowed([ + 'none' + 'sticky' +]) +@description('Optional. Bool indicating if the Container App should enable session affinity.') +param stickySessionsAffinity string = 'none' + @allowed([ 'auto' 'http' @@ -144,24 +151,23 @@ var builtInRoleNames = { ) } -resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = - if (enableTelemetry) { - name: '46d3xbcp.res.app-containerapp.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' - properties: { - mode: 'Incremental' - template: { - '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' - contentVersion: '1.0.0.0' - resources: [] - outputs: { - telemetry: { - type: 'String' - value: 'For more information, see https://aka.ms/avm/TelemetryInfo' - } +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { + name: '46d3xbcp.res.app-containerapp.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + outputs: { + telemetry: { + type: 'String' + value: 'For more information, see https://aka.ms/avm/TelemetryInfo' } } } } +} resource containerApp 'Microsoft.App/containerApps@2023-05-01' = { name: name @@ -180,6 +186,9 @@ resource containerApp 'Microsoft.App/containerApps@2023-05-01' = { external: ingressExternal ipSecurityRestrictions: !empty(ipSecurityRestrictions) ? ipSecurityRestrictions : null targetPort: ingressTargetPort + stickySessions: { + affinity: stickySessionsAffinity + } traffic: [ { label: trafficLabel @@ -209,17 +218,16 @@ resource containerApp 'Microsoft.App/containerApps@2023-05-01' = { } } -resource containerApp_lock 'Microsoft.Authorization/locks@2020-05-01' = - if (!empty(lock ?? {}) && lock.?kind != 'None') { - name: lock.?name ?? 'lock-${name}' - properties: { - level: lock.?kind ?? '' - notes: lock.?kind == 'CanNotDelete' - ? 'Cannot delete resource or child resources.' - : 'Cannot delete or modify the resource or child resources.' - } - scope: containerApp +resource containerApp_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' + ? 'Cannot delete resource or child resources.' + : 'Cannot delete or modify the resource or child resources.' } + scope: containerApp +} resource containerApp_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ for (roleAssignment, index) in (roleAssignments ?? []): { diff --git a/avm/res/app/container-app/main.json b/avm/res/app/container-app/main.json index 97c1bf140a..2139556b3b 100644 --- a/avm/res/app/container-app/main.json +++ b/avm/res/app/container-app/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "11829581770848647649" + "version": "0.27.1.19265", + "templateHash": "6545722573689550634" }, "name": "Container Apps", "description": "This module deploys a Container App.", @@ -149,6 +149,17 @@ "description": "Optional. Bool indicating if the App exposes an external HTTP endpoint." } }, + "stickySessionsAffinity": { + "type": "string", + "defaultValue": "none", + "allowedValues": [ + "none", + "sticky" + ], + "metadata": { + "description": "Optional. Bool indicating if the Container App should enable session affinity." + } + }, "ingressTransport": { "type": "string", "defaultValue": "auto", @@ -411,6 +422,9 @@ "external": "[parameters('ingressExternal')]", "ipSecurityRestrictions": "[if(not(empty(parameters('ipSecurityRestrictions'))), parameters('ipSecurityRestrictions'), null())]", "targetPort": "[parameters('ingressTargetPort')]", + "stickySessions": { + "affinity": "[parameters('stickySessionsAffinity')]" + }, "traffic": [ { "label": "[parameters('trafficLabel')]", diff --git a/avm/res/app/container-app/version.json b/avm/res/app/container-app/version.json index 9481fea58e..c177b1bb58 100644 --- a/avm/res/app/container-app/version.json +++ b/avm/res/app/container-app/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 From 4d86f2348be052522c1dd8c95a5f2b0ff454b05f Mon Sep 17 00:00:00 2001 From: ChrisSidebotham-MSFT <48600046+ChrisSidebotham@users.noreply.github.com> Date: Wed, 22 May 2024 09:26:32 +0100 Subject: [PATCH 06/18] feat: Import changes from #1123 for Key Vault - `avm/res/key-vault/vault` (#1986) ## Description I have migrated changes from 1123 into my own feature branch to close and completes the related tasks to 1123 #closes 1122 - adds tests for ec and rsa kty key parameters - adds checks for dependencies between keySize and curveName key parameters - Expanded UDT for Secrets - Exapanded UDT for Keys - added support for releasePolicy for key - resolves the conflicts from #1123 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.key-vault.vault](https://github.com/ChrisSidebotham/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml/badge.svg?branch=fblix-kv-updates-port)](https://github.com/ChrisSidebotham/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml) | ## Type of Change - [x] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [x] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [ ] I'm sure there are no other open Pull Requests for the same update/change (#1123 IS OPEN AND SHOULD BE DISCARDED) - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/key-vault/vault/README.md | 1216 ++++++++++++++--- .../key-vault/vault/access-policy/main.json | 4 +- avm/res/key-vault/vault/key/README.md | 8 + avm/res/key-vault/vault/key/main.bicep | 91 +- avm/res/key-vault/vault/key/main.json | 14 +- avm/res/key-vault/vault/main.bicep | 174 ++- avm/res/key-vault/vault/main.json | 353 ++++- avm/res/key-vault/vault/secret/main.json | 4 +- .../vault/tests/e2e/eckey/main.test.bicep | 84 ++ .../vault/tests/e2e/max/main.test.bicep | 60 +- .../vault/tests/e2e/rsakey/main.test.bicep | 84 ++ .../tests/e2e/waf-aligned/main.test.bicep | 20 +- avm/res/key-vault/vault/version.json | 4 +- 13 files changed, 1749 insertions(+), 367 deletions(-) create mode 100644 avm/res/key-vault/vault/tests/e2e/eckey/main.test.bicep create mode 100644 avm/res/key-vault/vault/tests/e2e/rsakey/main.test.bicep diff --git a/avm/res/key-vault/vault/README.md b/avm/res/key-vault/vault/README.md index f86a29fbc3..ac00599aa6 100644 --- a/avm/res/key-vault/vault/README.md +++ b/avm/res/key-vault/vault/README.md @@ -34,8 +34,10 @@ 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/key-vault/vault:`. - [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 only defaults](#example-2-using-only-defaults) +- [Using large parameter set](#example-3-using-large-parameter-set) +- [Using only defaults](#example-4-using-only-defaults) +- [WAF-aligned](#example-5-waf-aligned) ### Example 1: _Using only defaults_ @@ -89,7 +91,129 @@ module vault 'br/public:avm/res/key-vault/vault:' = {

-### Example 2: _Using large parameter set_ +### Example 2: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. + + +

+ +via Bicep module + +```bicep +module vault 'br/public:avm/res/key-vault/vault:' = { + name: 'vaultDeployment' + params: { + // Required parameters + name: 'kvvec002' + // Non-required parameters + enablePurgeProtection: false + enableRbacAuthorization: true + keys: [ + { + attributes: { + exp: 1725109032 + nbf: 10000 + } + kty: 'EC' + name: 'keyName' + rotationPolicy: { + attributes: { + expiryTime: 'P2Y' + } + lifetimeActions: [ + { + action: { + type: 'Rotate' + } + trigger: { + timeBeforeExpiry: 'P2M' + } + } + { + action: { + type: 'Notify' + } + trigger: { + timeBeforeExpiry: 'P30D' + } + } + ] + } + } + ] + location: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "value": "kvvec002" + }, + "enablePurgeProtection": { + "value": false + }, + "enableRbacAuthorization": { + "value": true + }, + "keys": { + "value": [ + { + "attributes": { + "exp": 1725109032, + "nbf": 10000 + }, + "kty": "EC", + "name": "keyName", + "rotationPolicy": { + "attributes": { + "expiryTime": "P2Y" + }, + "lifetimeActions": [ + { + "action": { + "type": "Rotate" + }, + "trigger": { + "timeBeforeExpiry": "P2M" + } + }, + { + "action": { + "type": "Notify" + }, + "trigger": { + "timeBeforeExpiry": "P30D" + } + } + ] + } + } + ] + }, + "location": { + "value": "" + } + } +} +``` + +
+

+ +### Example 3: _Using large parameter set_ This instance deploys the module with most of its features enabled. @@ -291,34 +415,32 @@ module vault 'br/public:avm/res/key-vault/vault:' = { roleDefinitionIdOrName: '' } ] - secrets: { - secureList: [ - { - attributesExp: 1702648632 - attributesNbf: 10000 - contentType: 'Something' - name: 'secretName' - roleAssignments: [ - { - principalId: '' - principalType: 'ServicePrincipal' - roleDefinitionIdOrName: 'Owner' - } - { - principalId: '' - principalType: 'ServicePrincipal' - roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' - } - { - principalId: '' - principalType: 'ServicePrincipal' - roleDefinitionIdOrName: '' - } - ] - value: 'secretValue' - } - ] - } + secrets: [ + { + attributesExp: 1702648632 + attributesNbf: 10000 + contentType: 'Something' + name: 'secretName' + roleAssignments: [ + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Owner' + } + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + } + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: '' + } + ] + value: 'secretValue' + } + ] softDeleteRetentionInDays: 7 tags: { Environment: 'Non-Prod' @@ -551,34 +673,32 @@ module vault 'br/public:avm/res/key-vault/vault:' = { ] }, "secrets": { - "value": { - "secureList": [ - { - "attributesExp": 1702648632, - "attributesNbf": 10000, - "contentType": "Something", - "name": "secretName", - "roleAssignments": [ - { - "principalId": "", - "principalType": "ServicePrincipal", - "roleDefinitionIdOrName": "Owner" - }, - { - "principalId": "", - "principalType": "ServicePrincipal", - "roleDefinitionIdOrName": "b24988ac-6180-42a0-ab88-20f7382dd24c" - }, - { - "principalId": "", - "principalType": "ServicePrincipal", - "roleDefinitionIdOrName": "" - } - ], - "value": "secretValue" - } - ] - } + "value": [ + { + "attributesExp": 1702648632, + "attributesNbf": 10000, + "contentType": "Something", + "name": "secretName", + "roleAssignments": [ + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Owner" + }, + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "b24988ac-6180-42a0-ab88-20f7382dd24c" + }, + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "" + } + ], + "value": "secretValue" + } + ] }, "softDeleteRetentionInDays": { "value": 7 @@ -597,9 +717,9 @@ module vault 'br/public:avm/res/key-vault/vault:' = {

-### Example 3: _WAF-aligned_ +### Example 4: _Using only defaults_ -This instance deploys the module in alignment with the best-practices of the Well-Architected Framework. +This instance deploys the module with the minimum set of required parameters.

@@ -611,23 +731,17 @@ module vault 'br/public:avm/res/key-vault/vault:' = { name: 'vaultDeployment' params: { // Required parameters - name: 'kvvwaf002' + name: 'kvvrsa002' // Non-required parameters - diagnosticSettings: [ - { - eventHubAuthorizationRuleResourceId: '' - eventHubName: '' - storageAccountResourceId: '' - workspaceResourceId: '' - } - ] enablePurgeProtection: false enableRbacAuthorization: true keys: [ { - attributesExp: 1725109032 - attributesNbf: 10000 - keySize: 4096 + attributes: { + exp: 1725109032 + nbf: 10000 + } + kty: 'RSA' name: 'keyName' rotationPolicy: { attributes: { @@ -655,40 +769,6 @@ module vault 'br/public:avm/res/key-vault/vault:' = { } ] location: '' - lock: { - kind: 'CanNotDelete' - name: 'myCustomLockName' - } - networkAcls: { - bypass: 'AzureServices' - defaultAction: 'Deny' - } - privateEndpoints: [ - { - privateDnsZoneResourceIds: [ - '' - ] - service: 'vault' - subnetResourceId: '' - } - ] - secrets: { - secureList: [ - { - attributesExp: 1702648632 - attributesNbf: 10000 - contentType: 'Something' - name: 'secretName' - value: 'secretValue' - } - ] - } - softDeleteRetentionInDays: 7 - tags: { - Environment: 'Non-Prod' - 'hidden-title': 'This is visible in the resource name' - Role: 'DeploymentValidation' - } } } ``` @@ -706,17 +786,7 @@ module vault 'br/public:avm/res/key-vault/vault:' = { "contentVersion": "1.0.0.0", "parameters": { "name": { - "value": "kvvwaf002" - }, - "diagnosticSettings": { - "value": [ - { - "eventHubAuthorizationRuleResourceId": "", - "eventHubName": "", - "storageAccountResourceId": "", - "workspaceResourceId": "" - } - ] + "value": "kvvrsa002" }, "enablePurgeProtection": { "value": false @@ -727,9 +797,11 @@ module vault 'br/public:avm/res/key-vault/vault:' = { "keys": { "value": [ { - "attributesExp": 1725109032, - "attributesNbf": 10000, - "keySize": 4096, + "attributes": { + "exp": 1725109032, + "nbf": 10000 + }, + "kty": "RSA", "name": "keyName", "rotationPolicy": { "attributes": { @@ -759,43 +831,209 @@ module vault 'br/public:avm/res/key-vault/vault:' = { }, "location": { "value": "" - }, - "lock": { - "value": { - "kind": "CanNotDelete", - "name": "myCustomLockName" - } - }, - "networkAcls": { - "value": { - "bypass": "AzureServices", - "defaultAction": "Deny" - } - }, - "privateEndpoints": { - "value": [ - { - "privateDnsZoneResourceIds": [ - "" - ], - "service": "vault", - "subnetResourceId": "" - } + } + } +} +``` + +
+

+ +### Example 5: _WAF-aligned_ + +This instance deploys the module in alignment with the best-practices of the Well-Architected Framework. + + +

+ +via Bicep module + +```bicep +module vault 'br/public:avm/res/key-vault/vault:' = { + name: 'vaultDeployment' + params: { + // Required parameters + name: 'kvvwaf002' + // Non-required parameters + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + enablePurgeProtection: false + enableRbacAuthorization: true + keys: [ + { + attributesExp: 1725109032 + attributesNbf: 10000 + keySize: 4096 + name: 'keyName' + rotationPolicy: { + attributes: { + expiryTime: 'P2Y' + } + lifetimeActions: [ + { + action: { + type: 'Rotate' + } + trigger: { + timeBeforeExpiry: 'P2M' + } + } + { + action: { + type: 'Notify' + } + trigger: { + timeBeforeExpiry: 'P30D' + } + } + ] + } + } + ] + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + } + privateEndpoints: [ + { + privateDnsZoneResourceIds: [ + '' + ] + service: 'vault' + subnetResourceId: '' + } + ] + secrets: [ + { + attributesExp: 1702648632 + attributesNbf: 10000 + contentType: 'Something' + name: 'secretName' + value: 'secretValue' + } + ] + softDeleteRetentionInDays: 7 + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "value": "kvvwaf002" + }, + "diagnosticSettings": { + "value": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } ] }, - "secrets": { - "value": { - "secureList": [ - { - "attributesExp": 1702648632, - "attributesNbf": 10000, - "contentType": "Something", - "name": "secretName", - "value": "secretValue" + "enablePurgeProtection": { + "value": false + }, + "enableRbacAuthorization": { + "value": true + }, + "keys": { + "value": [ + { + "attributesExp": 1725109032, + "attributesNbf": 10000, + "keySize": 4096, + "name": "keyName", + "rotationPolicy": { + "attributes": { + "expiryTime": "P2Y" + }, + "lifetimeActions": [ + { + "action": { + "type": "Rotate" + }, + "trigger": { + "timeBeforeExpiry": "P2M" + } + }, + { + "action": { + "type": "Notify" + }, + "trigger": { + "timeBeforeExpiry": "P30D" + } + } + ] } - ] + } + ] + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "networkAcls": { + "value": { + "bypass": "AzureServices", + "defaultAction": "Deny" } }, + "privateEndpoints": { + "value": [ + { + "privateDnsZoneResourceIds": [ + "" + ], + "service": "vault", + "subnetResourceId": "" + } + ] + }, + "secrets": { + "value": [ + { + "attributesExp": 1702648632, + "attributesNbf": 10000, + "contentType": "Something", + "name": "secretName", + "value": "secretValue" + } + ] + }, "softDeleteRetentionInDays": { "value": 7 }, @@ -843,7 +1081,7 @@ module vault 'br/public:avm/res/key-vault/vault:' = { | [`privateEndpoints`](#parameter-privateendpoints) | array | Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. | | [`publicNetworkAccess`](#parameter-publicnetworkaccess) | string | 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 and networkAcls are not set. | | [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | -| [`secrets`](#parameter-secrets) | secureObject | All secrets to create. | +| [`secrets`](#parameter-secrets) | array | All secrets to create. | | [`sku`](#parameter-sku) | string | Specifies the SKU for the vault. | | [`softDeleteRetentionInDays`](#parameter-softdeleteretentionindays) | int | softDelete data retention days. It accepts >=7 and <=90. | | [`tags`](#parameter-tags) | object | Resource tags. | @@ -1095,150 +1333,507 @@ The name of logs that will be streamed. "allLogs" includes all possible logs for | [`categoryGroup`](#parameter-diagnosticsettingslogcategoriesandgroupscategorygroup) | string | Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs. | | [`enabled`](#parameter-diagnosticsettingslogcategoriesandgroupsenabled) | bool | Enable or disable the category explicitly. Default is `true`. | -### Parameter: `diagnosticSettings.logCategoriesAndGroups.category` +### Parameter: `diagnosticSettings.logCategoriesAndGroups.category` + +Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.categoryGroup` + +Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `diagnosticSettings.marketplacePartnerResourceId` + +The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.metricCategories` + +The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-diagnosticsettingsmetriccategoriescategory) | string | Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`enabled`](#parameter-diagnosticsettingsmetriccategoriesenabled) | bool | Enable or disable the category explicitly. Default is `true`. | + +### Parameter: `diagnosticSettings.metricCategories.category` + +Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. + +- Required: Yes +- Type: string + +### Parameter: `diagnosticSettings.metricCategories.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `diagnosticSettings.name` + +The name of diagnostic setting. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.storageAccountResourceId` + +Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.workspaceResourceId` + +Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `enablePurgeProtection` + +Provide 'true' to enable Key Vault's purge protection feature. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `enableRbacAuthorization` + +Property that controls how data actions are authorized. When true, the key vault will use Role Based Access Control (RBAC) for authorization of data actions, and the access policies specified in vault properties will be ignored. When false, the key vault will use the access policies specified in vault properties, and any policy stored on Azure Resource Manager will be ignored. Note that management actions are always authorized with RBAC. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `enableSoftDelete` + +Switch to enable/disable Key Vault's soft delete feature. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `enableVaultForDeployment` + +Specifies if the vault is enabled for deployment by script or compute. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `enableVaultForDiskEncryption` + +Specifies if the azure platform has access to the vault for enabling disk encryption scenarios. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `enableVaultForTemplateDeployment` + +Specifies if the vault is enabled for a template deployment. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `keys` + +All keys to create. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-keysname) | string | The name of the key. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`attributes`](#parameter-keysattributes) | object | Contains attributes of the key. | +| [`curveName`](#parameter-keyscurvename) | string | The elliptic curve name. Only works if "keySize" equals "EC" or "EC-HSM". Default is "P-256". | +| [`keyOps`](#parameter-keyskeyops) | array | The allowed operations on this key. | +| [`keySize`](#parameter-keyskeysize) | int | The key size in bits. Only works if "keySize" equals "RSA" or "RSA-HSM". Default is "4096". | +| [`kty`](#parameter-keyskty) | string | The type of the key. Default is "EC". | +| [`releasePolicy`](#parameter-keysreleasepolicy) | object | Key release policy. | +| [`roleAssignments`](#parameter-keysroleassignments) | array | Array of role assignments to create. | +| [`rotationPolicy`](#parameter-keysrotationpolicy) | object | Key rotation policy. | +| [`tags`](#parameter-keystags) | object | Resource tags. | + +### Parameter: `keys.name` + +The name of the key. + +- Required: Yes +- Type: string + +### Parameter: `keys.attributes` + +Contains attributes of the key. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`enabled`](#parameter-keysattributesenabled) | bool | Defines whether the key is enabled or disabled. | +| [`exp`](#parameter-keysattributesexp) | int | Defines when the key will become invalid. Defined in seconds since 1970-01-01T00:00:00Z. | +| [`nbf`](#parameter-keysattributesnbf) | int | If set, defines the date from which onwards the key becomes valid. Defined in seconds since 1970-01-01T00:00:00Z. | + +### Parameter: `keys.attributes.enabled` + +Defines whether the key is enabled or disabled. + +- Required: No +- Type: bool + +### Parameter: `keys.attributes.exp` + +Defines when the key will become invalid. Defined in seconds since 1970-01-01T00:00:00Z. + +- Required: No +- Type: int + +### Parameter: `keys.attributes.nbf` + +If set, defines the date from which onwards the key becomes valid. Defined in seconds since 1970-01-01T00:00:00Z. + +- Required: No +- Type: int + +### Parameter: `keys.curveName` + +The elliptic curve name. Only works if "keySize" equals "EC" or "EC-HSM". Default is "P-256". + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'P-256' + 'P-256K' + 'P-384' + 'P-521' + ] + ``` + +### Parameter: `keys.keyOps` + +The allowed operations on this key. + +- Required: No +- Type: array +- Allowed: + ```Bicep + [ + 'decrypt' + 'encrypt' + 'import' + 'release' + 'sign' + 'unwrapKey' + 'verify' + 'wrapKey' + ] + ``` + +### Parameter: `keys.keySize` + +The key size in bits. Only works if "keySize" equals "RSA" or "RSA-HSM". Default is "4096". + +- Required: No +- Type: int +- Allowed: + ```Bicep + [ + 2048 + 3072 + 4096 + ] + ``` + +### Parameter: `keys.kty` + +The type of the key. Default is "EC". + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'EC' + 'EC-HSM' + 'RSA' + 'RSA-HSM' + ] + ``` + +### Parameter: `keys.releasePolicy` + +Key release policy. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`contentType`](#parameter-keysreleasepolicycontenttype) | string | Content type and version of key release policy. | +| [`data`](#parameter-keysreleasepolicydata) | string | Blob encoding the policy rules under which the key can be released. | + +### Parameter: `keys.releasePolicy.contentType` + +Content type and version of key release policy. + +- Required: No +- Type: string + +### Parameter: `keys.releasePolicy.data` + +Blob encoding the policy rules under which the key can be released. + +- Required: No +- Type: string + +### Parameter: `keys.roleAssignments` + +Array of role assignments to create. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`principalId`](#parameter-keysroleassignmentsprincipalid) | string | The principal ID of the principal (user/group/identity) to assign the role to. | +| [`roleDefinitionIdOrName`](#parameter-keysroleassignmentsroledefinitionidorname) | string | 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'. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`condition`](#parameter-keysroleassignmentscondition) | string | 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". | +| [`conditionVersion`](#parameter-keysroleassignmentsconditionversion) | string | Version of the condition. | +| [`delegatedManagedIdentityResourceId`](#parameter-keysroleassignmentsdelegatedmanagedidentityresourceid) | string | The Resource Id of the delegated managed identity resource. | +| [`description`](#parameter-keysroleassignmentsdescription) | string | The description of the role assignment. | +| [`principalType`](#parameter-keysroleassignmentsprincipaltype) | string | The principal type of the assigned principal ID. | + +### Parameter: `keys.roleAssignments.principalId` + +The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `keys.roleAssignments.roleDefinitionIdOrName` + +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'. + +- Required: Yes +- Type: string + +### Parameter: `keys.roleAssignments.condition` -Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. +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". - Required: No - Type: string -### Parameter: `diagnosticSettings.logCategoriesAndGroups.categoryGroup` +### Parameter: `keys.roleAssignments.conditionVersion` -Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs. +Version of the condition. - Required: No - Type: string +- Allowed: + ```Bicep + [ + '2.0' + ] + ``` -### Parameter: `diagnosticSettings.logCategoriesAndGroups.enabled` +### Parameter: `keys.roleAssignments.delegatedManagedIdentityResourceId` -Enable or disable the category explicitly. Default is `true`. +The Resource Id of the delegated managed identity resource. - Required: No -- Type: bool +- Type: string -### Parameter: `diagnosticSettings.marketplacePartnerResourceId` +### Parameter: `keys.roleAssignments.description` -The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. +The description of the role assignment. - Required: No - Type: string -### Parameter: `diagnosticSettings.metricCategories` +### Parameter: `keys.roleAssignments.principalType` -The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection. +The principal type of the assigned principal ID. - Required: No -- Type: array +- Type: string +- Allowed: + ```Bicep + [ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' + ] + ``` -**Required parameters** +### Parameter: `keys.rotationPolicy` -| Parameter | Type | Description | -| :-- | :-- | :-- | -| [`category`](#parameter-diagnosticsettingsmetriccategoriescategory) | string | Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. | +Key rotation policy. + +- Required: No +- Type: object **Optional parameters** | Parameter | Type | Description | | :-- | :-- | :-- | -| [`enabled`](#parameter-diagnosticsettingsmetriccategoriesenabled) | bool | Enable or disable the category explicitly. Default is `true`. | - -### Parameter: `diagnosticSettings.metricCategories.category` +| [`attributes`](#parameter-keysrotationpolicyattributes) | object | The attributes of key rotation policy. | +| [`lifetimeActions`](#parameter-keysrotationpolicylifetimeactions) | array | The lifetimeActions for key rotation action. | -Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. +### Parameter: `keys.rotationPolicy.attributes` -- Required: Yes -- Type: string +The attributes of key rotation policy. -### Parameter: `diagnosticSettings.metricCategories.enabled` +- Required: No +- Type: object -Enable or disable the category explicitly. Default is `true`. +**Optional parameters** -- Required: No -- Type: bool +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`expiryTime`](#parameter-keysrotationpolicyattributesexpirytime) | string | The expiration time for the new key version. It should be in ISO8601 format. Eg: "P90D", "P1Y". | -### Parameter: `diagnosticSettings.name` +### Parameter: `keys.rotationPolicy.attributes.expiryTime` -The name of diagnostic setting. +The expiration time for the new key version. It should be in ISO8601 format. Eg: "P90D", "P1Y". - Required: No - Type: string -### Parameter: `diagnosticSettings.storageAccountResourceId` +### Parameter: `keys.rotationPolicy.lifetimeActions` -Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. +The lifetimeActions for key rotation action. - Required: No -- Type: string - -### Parameter: `diagnosticSettings.workspaceResourceId` +- Type: array -Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. +**Optional parameters** -- Required: No -- Type: string +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`action`](#parameter-keysrotationpolicylifetimeactionsaction) | object | The action of key rotation policy lifetimeAction. | +| [`trigger`](#parameter-keysrotationpolicylifetimeactionstrigger) | object | The trigger of key rotation policy lifetimeAction. | -### Parameter: `enablePurgeProtection` +### Parameter: `keys.rotationPolicy.lifetimeActions.action` -Provide 'true' to enable Key Vault's purge protection feature. +The action of key rotation policy lifetimeAction. - Required: No -- Type: bool -- Default: `True` - -### Parameter: `enableRbacAuthorization` +- Type: object -Property that controls how data actions are authorized. When true, the key vault will use Role Based Access Control (RBAC) for authorization of data actions, and the access policies specified in vault properties will be ignored. When false, the key vault will use the access policies specified in vault properties, and any policy stored on Azure Resource Manager will be ignored. Note that management actions are always authorized with RBAC. +**Optional parameters** -- Required: No -- Type: bool -- Default: `True` +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`type`](#parameter-keysrotationpolicylifetimeactionsactiontype) | string | The type of action. | -### Parameter: `enableSoftDelete` +### Parameter: `keys.rotationPolicy.lifetimeActions.action.type` -Switch to enable/disable Key Vault's soft delete feature. +The type of action. - Required: No -- Type: bool -- Default: `True` +- Type: string +- Allowed: + ```Bicep + [ + 'Notify' + 'Rotate' + ] + ``` -### Parameter: `enableTelemetry` +### Parameter: `keys.rotationPolicy.lifetimeActions.trigger` -Enable/Disable usage telemetry for module. +The trigger of key rotation policy lifetimeAction. - Required: No -- Type: bool -- Default: `True` - -### Parameter: `enableVaultForDeployment` +- Type: object -Specifies if the vault is enabled for deployment by script or compute. +**Optional parameters** -- Required: No -- Type: bool -- Default: `True` +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`timeAfterCreate`](#parameter-keysrotationpolicylifetimeactionstriggertimeaftercreate) | string | The time duration after key creation to rotate the key. It only applies to rotate. It will be in ISO 8601 duration format. Eg: "P90D", "P1Y". | +| [`timeBeforeExpiry`](#parameter-keysrotationpolicylifetimeactionstriggertimebeforeexpiry) | string | The time duration before key expiring to rotate or notify. It will be in ISO 8601 duration format. Eg: "P90D", "P1Y". | -### Parameter: `enableVaultForDiskEncryption` +### Parameter: `keys.rotationPolicy.lifetimeActions.trigger.timeAfterCreate` -Specifies if the azure platform has access to the vault for enabling disk encryption scenarios. +The time duration after key creation to rotate the key. It only applies to rotate. It will be in ISO 8601 duration format. Eg: "P90D", "P1Y". - Required: No -- Type: bool -- Default: `True` +- Type: string -### Parameter: `enableVaultForTemplateDeployment` +### Parameter: `keys.rotationPolicy.lifetimeActions.trigger.timeBeforeExpiry` -Specifies if the vault is enabled for a template deployment. +The time duration before key expiring to rotate or notify. It will be in ISO 8601 duration format. Eg: "P90D", "P1Y". - Required: No -- Type: bool -- Default: `True` +- Type: string -### Parameter: `keys` +### Parameter: `keys.tags` -All keys to create. +Resource tags. - Required: No -- Type: array +- Type: object ### Parameter: `location` @@ -1744,7 +2339,176 @@ The principal type of the assigned principal ID. All secrets to create. - Required: No -- Type: secureObject +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-secretsname) | string | The name of the secret. | +| [`value`](#parameter-secretsvalue) | securestring | The value of the secret. NOTE: "value" will never be returned from the service, as APIs using this model are is intended for internal use in ARM deployments. Users should use the data-plane REST service for interaction with vault secrets. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`attributes`](#parameter-secretsattributes) | object | Contains attributes of the secret. | +| [`contentType`](#parameter-secretscontenttype) | string | The content type of the secret. | +| [`roleAssignments`](#parameter-secretsroleassignments) | array | Array of role assignments to create. | +| [`tags`](#parameter-secretstags) | object | Resource tags. | + +### Parameter: `secrets.name` + +The name of the secret. + +- Required: Yes +- Type: string + +### Parameter: `secrets.value` + +The value of the secret. NOTE: "value" will never be returned from the service, as APIs using this model are is intended for internal use in ARM deployments. Users should use the data-plane REST service for interaction with vault secrets. + +- Required: Yes +- Type: securestring + +### Parameter: `secrets.attributes` + +Contains attributes of the secret. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`enabled`](#parameter-secretsattributesenabled) | bool | Defines whether the secret is enabled or disabled. | +| [`exp`](#parameter-secretsattributesexp) | int | Defines when the secret will become invalid. Defined in seconds since 1970-01-01T00:00:00Z. | +| [`nbf`](#parameter-secretsattributesnbf) | int | If set, defines the date from which onwards the secret becomes valid. Defined in seconds since 1970-01-01T00:00:00Z. | + +### Parameter: `secrets.attributes.enabled` + +Defines whether the secret is enabled or disabled. + +- Required: No +- Type: bool + +### Parameter: `secrets.attributes.exp` + +Defines when the secret will become invalid. Defined in seconds since 1970-01-01T00:00:00Z. + +- Required: No +- Type: int + +### Parameter: `secrets.attributes.nbf` + +If set, defines the date from which onwards the secret becomes valid. Defined in seconds since 1970-01-01T00:00:00Z. + +- Required: No +- Type: int + +### Parameter: `secrets.contentType` + +The content type of the secret. + +- Required: No +- Type: string + +### Parameter: `secrets.roleAssignments` + +Array of role assignments to create. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`principalId`](#parameter-secretsroleassignmentsprincipalid) | string | The principal ID of the principal (user/group/identity) to assign the role to. | +| [`roleDefinitionIdOrName`](#parameter-secretsroleassignmentsroledefinitionidorname) | string | 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'. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`condition`](#parameter-secretsroleassignmentscondition) | string | 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". | +| [`conditionVersion`](#parameter-secretsroleassignmentsconditionversion) | string | Version of the condition. | +| [`delegatedManagedIdentityResourceId`](#parameter-secretsroleassignmentsdelegatedmanagedidentityresourceid) | string | The Resource Id of the delegated managed identity resource. | +| [`description`](#parameter-secretsroleassignmentsdescription) | string | The description of the role assignment. | +| [`principalType`](#parameter-secretsroleassignmentsprincipaltype) | string | The principal type of the assigned principal ID. | + +### Parameter: `secrets.roleAssignments.principalId` + +The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `secrets.roleAssignments.roleDefinitionIdOrName` + +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'. + +- Required: Yes +- Type: string + +### Parameter: `secrets.roleAssignments.condition` + +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". + +- Required: No +- Type: string + +### Parameter: `secrets.roleAssignments.conditionVersion` + +Version of the condition. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + '2.0' + ] + ``` + +### Parameter: `secrets.roleAssignments.delegatedManagedIdentityResourceId` + +The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `secrets.roleAssignments.description` + +The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `secrets.roleAssignments.principalType` + +The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' + ] + ``` + +### Parameter: `secrets.tags` + +Resource tags. + +- Required: No +- Type: object ### Parameter: `sku` diff --git a/avm/res/key-vault/vault/access-policy/main.json b/avm/res/key-vault/vault/access-policy/main.json index 115b5ecb85..47835eb508 100644 --- a/avm/res/key-vault/vault/access-policy/main.json +++ b/avm/res/key-vault/vault/access-policy/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "10878813547461142217" + "version": "0.27.1.19265", + "templateHash": "13379419349517171769" }, "name": "Key Vault Access Policies", "description": "This module deploys a Key Vault Access Policy.", diff --git a/avm/res/key-vault/vault/key/README.md b/avm/res/key-vault/vault/key/README.md index f67a9409cd..16a2230c28 100644 --- a/avm/res/key-vault/vault/key/README.md +++ b/avm/res/key-vault/vault/key/README.md @@ -42,6 +42,7 @@ This module deploys a Key Vault Key. | [`keyOps`](#parameter-keyops) | array | Array of JsonWebKeyOperation. | | [`keySize`](#parameter-keysize) | int | The key size in bits. For example: 2048, 3072, or 4096 for RSA. | | [`kty`](#parameter-kty) | string | The type of the key. | +| [`releasePolicy`](#parameter-releasepolicy) | object | Key release policy. | | [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | | [`rotationPolicy`](#parameter-rotationpolicy) | object | Key rotation policy properties object. | | [`tags`](#parameter-tags) | object | Resource tags. | @@ -142,6 +143,13 @@ The type of the key. ] ``` +### Parameter: `releasePolicy` + +Key release policy. + +- Required: No +- Type: object + ### Parameter: `roleAssignments` Array of role assignments to create. diff --git a/avm/res/key-vault/vault/key/main.bicep b/avm/res/key-vault/vault/key/main.bicep index 9c125673df..07c6c75dd5 100644 --- a/avm/res/key-vault/vault/key/main.bicep +++ b/avm/res/key-vault/vault/key/main.bicep @@ -53,6 +53,9 @@ param keySize int? ]) param kty string = 'EC' +@description('Optional. Key release policy.') +param releasePolicy object? + @description('Optional. Array of role assignments to create.') param roleAssignments roleAssignmentType @@ -61,19 +64,52 @@ param rotationPolicy object? var builtInRoleNames = { Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') - 'Key Vault Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483') - 'Key Vault Certificates Officer': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985') - 'Key Vault Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395') - 'Key Vault Crypto Officer': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603') - 'Key Vault Crypto Service Encryption User': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') - 'Key Vault Crypto User': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424') - 'Key Vault Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2') - 'Key Vault Secrets Officer': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7') - 'Key Vault Secrets User': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6') + 'Key Vault Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '00482a5a-887f-4fb3-b363-3b7fe8e74483' + ) + 'Key Vault Certificates Officer': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'a4417e6f-fecd-4de8-b567-7b0420556985' + ) + 'Key Vault Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f25e0fa2-a7c8-4377-a976-54943a77a395' + ) + 'Key Vault Crypto Officer': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '14b46e9e-c2b7-41b4-b07b-48a6ebf60603' + ) + 'Key Vault Crypto Service Encryption User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'e147488a-f6f5-4113-8e2d-b22465e65bf6' + ) + 'Key Vault Crypto User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '12338af0-0e69-4776-bea7-57ae8d297424' + ) + 'Key Vault Reader': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '21090545-7ca7-4776-b22c-e363652d74d2' + ) + 'Key Vault Secrets Officer': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7' + ) + 'Key Vault Secrets User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '4633458b-17de-408a-b874-0445c86b69e6' + ) Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') - 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') - 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' + ) + 'User Access Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' + ) } resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { @@ -94,23 +130,30 @@ resource key 'Microsoft.KeyVault/vaults/keys@2022-07-01' = { keyOps: keyOps keySize: keySize kty: kty - rotationPolicy: rotationPolicy + rotationPolicy: rotationPolicy ?? {} + release_policy: releasePolicy ?? {} } } -resource key_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { - name: guid(key.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) - properties: { - roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : contains(roleAssignment.roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/') ? roleAssignment.roleDefinitionIdOrName : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName) - principalId: roleAssignment.principalId - description: roleAssignment.?description - principalType: roleAssignment.?principalType - condition: roleAssignment.?condition - conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set - delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId +resource key_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(key.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) + ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] + : contains(roleAssignment.roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/') + ? roleAssignment.roleDefinitionIdOrName + : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName) + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: key } - scope: key -}] +] @description('The name of the key.') output name string = key.name diff --git a/avm/res/key-vault/vault/key/main.json b/avm/res/key-vault/vault/key/main.json index ada36d2d80..8c2db4264d 100644 --- a/avm/res/key-vault/vault/key/main.json +++ b/avm/res/key-vault/vault/key/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "5903918450419813264" + "version": "0.27.1.19265", + "templateHash": "8655662111651470037" }, "name": "Key Vault Keys", "description": "This module deploys a Key Vault Key.", @@ -170,6 +170,13 @@ "description": "Optional. The type of the key." } }, + "releasePolicy": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Key release policy." + } + }, "roleAssignments": { "$ref": "#/definitions/roleAssignmentType", "metadata": { @@ -224,7 +231,8 @@ "keyOps": "[parameters('keyOps')]", "keySize": "[parameters('keySize')]", "kty": "[parameters('kty')]", - "rotationPolicy": "[parameters('rotationPolicy')]" + "rotationPolicy": "[coalesce(parameters('rotationPolicy'), createObject())]", + "release_policy": "[coalesce(parameters('releasePolicy'), createObject())]" }, "dependsOn": [ "keyVault" diff --git a/avm/res/key-vault/vault/main.bicep b/avm/res/key-vault/vault/main.bicep index 57ee7e3197..a036c8220a 100644 --- a/avm/res/key-vault/vault/main.bicep +++ b/avm/res/key-vault/vault/main.bicep @@ -16,11 +16,10 @@ param location string = resourceGroup().location param accessPolicies accessPoliciesType @description('Optional. All secrets to create.') -@secure() -param secrets object? +param secrets secretsType? @description('Optional. All keys to create.') -param keys array? +param keys keysType? @description('Optional. Specifies if the vault is enabled for deployment by script or compute.') param enableVaultForDeployment bool = true @@ -145,30 +144,27 @@ var formattedAccessPolicies = [ } ] -var secretList = secrets.?secureList ?? [] - // ============ // // Dependencies // // ============ // -resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = - if (enableTelemetry) { - name: '46d3xbcp.res.keyvault-vault.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' - properties: { - mode: 'Incremental' - template: { - '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' - contentVersion: '1.0.0.0' - resources: [] - outputs: { - telemetry: { - type: 'String' - value: 'For more information, see https://aka.ms/avm/TelemetryInfo' - } +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { + name: '46d3xbcp.res.keyvault-vault.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + outputs: { + telemetry: { + type: 'String' + value: 'For more information, see https://aka.ms/avm/TelemetryInfo' } } } } +} resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { name: name @@ -203,17 +199,16 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { } } -resource keyVault_lock 'Microsoft.Authorization/locks@2020-05-01' = - if (!empty(lock ?? {}) && lock.?kind != 'None') { - name: lock.?name ?? 'lock-${name}' - properties: { - level: lock.?kind ?? '' - notes: lock.?kind == 'CanNotDelete' - ? 'Cannot delete resource or child resources.' - : 'Cannot delete or modify the resource or child resources.' - } - scope: keyVault +resource keyVault_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' + ? 'Cannot delete resource or child resources.' + : 'Cannot delete or modify the resource or child resources.' } + scope: keyVault +} resource keyVault_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [ for (diagnosticSetting, index) in (diagnosticSettings ?? []): { @@ -244,17 +239,16 @@ resource keyVault_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021 } ] -module keyVault_accessPolicies 'access-policy/main.bicep' = - if (!empty(accessPolicies)) { - name: '${uniqueString(deployment().name, location)}-KeyVault-AccessPolicies' - params: { - keyVaultName: keyVault.name - accessPolicies: accessPolicies - } +module keyVault_accessPolicies 'access-policy/main.bicep' = if (!empty(accessPolicies)) { + name: '${uniqueString(deployment().name, location)}-KeyVault-AccessPolicies' + params: { + keyVaultName: keyVault.name + accessPolicies: accessPolicies } +} module keyVault_secrets 'secret/main.bicep' = [ - for (secret, index) in secretList: { + for (secret, index) in (secrets ?? []): { name: '${uniqueString(deployment().name, location)}-KeyVault-Secret-${index}' params: { name: secret.name @@ -279,9 +273,10 @@ module keyVault_keys 'key/main.bicep' = [ attributesEnabled: key.?attributesEnabled attributesExp: key.?attributesExp attributesNbf: key.?attributesNbf - curveName: key.?curveName ?? 'P-256' + curveName: (key.?kty != 'RSA' && key.?kty != 'RSA-HSM') ? (key.?curveName ?? 'P-256') : null keyOps: key.?keyOps - keySize: key.?keySize + keySize: (key.?kty == 'RSA' || key.?kty == 'RSA-HSM') ? (key.?keySize ?? 4096) : null + releasePolicy: key.?releasePolicy ?? {} kty: key.?kty ?? 'EC' tags: key.?tags ?? tags roleAssignments: key.?roleAssignments @@ -616,3 +611,104 @@ type accessPoliciesType = { | 'update')[]? } }[]? + +type secretsType = { + @description('Required. The name of the secret.') + name: string + + @description('Optional. Resource tags.') + tags: object? + + @description('Optional. Contains attributes of the secret.') + attributes: { + @description('Optional. Defines whether the secret is enabled or disabled.') + enabled: bool? + + @description('Optional. Defines when the secret will become invalid. Defined in seconds since 1970-01-01T00:00:00Z.') + exp: int? + + @description('Optional. If set, defines the date from which onwards the secret becomes valid. Defined in seconds since 1970-01-01T00:00:00Z.') + nbf: int? + }? + @description('Optional. The content type of the secret.') + contentType: string? + + @description('Required. The value of the secret. NOTE: "value" will never be returned from the service, as APIs using this model are is intended for internal use in ARM deployments. Users should use the data-plane REST service for interaction with vault secrets.') + @secure() + value: string + + @description('Optional. Array of role assignments to create.') + roleAssignments: roleAssignmentType? +}[]? + +type keysType = { + @description('Required. The name of the key.') + name: string + + @description('Optional. Resource tags.') + tags: object? + + @description('Optional. Contains attributes of the key.') + attributes: { + @description('Optional. Defines whether the key is enabled or disabled.') + enabled: bool? + + @description('Optional. Defines when the key will become invalid. Defined in seconds since 1970-01-01T00:00:00Z.') + exp: int? + + @description('Optional. If set, defines the date from which onwards the key becomes valid. Defined in seconds since 1970-01-01T00:00:00Z.') + nbf: int? + }? + @description('Optional. The elliptic curve name. Only works if "keySize" equals "EC" or "EC-HSM". Default is "P-256".') + curveName: ('P-256' | 'P-256K' | 'P-384' | 'P-521')? + + @description('Optional. The allowed operations on this key.') + keyOps: ('decrypt' | 'encrypt' | 'import' | 'release' | 'sign' | 'unwrapKey' | 'verify' | 'wrapKey')[]? + + @description('Optional. The key size in bits. Only works if "keySize" equals "RSA" or "RSA-HSM". Default is "4096".') + keySize: (2048 | 3072 | 4096)? + + @description('Optional. The type of the key. Default is "EC".') + kty: ('EC' | 'EC-HSM' | 'RSA' | 'RSA-HSM')? + + @description('Optional. Key release policy.') + releasePolicy: { + @description('Optional. Content type and version of key release policy.') + contentType: string? + + @description('Optional. Blob encoding the policy rules under which the key can be released.') + data: string? + }? + + @description('Optional. Key rotation policy.') + rotationPolicy: rotationPoliciesType? + + @description('Optional. Array of role assignments to create.') + roleAssignments: roleAssignmentType? +}[]? + +type rotationPoliciesType = { + @description('Optional. The attributes of key rotation policy.') + attributes: { + @description('Optional. The expiration time for the new key version. It should be in ISO8601 format. Eg: "P90D", "P1Y".') + expiryTime: string? + }? + + @description('Optional. The lifetimeActions for key rotation action.') + lifetimeActions: { + @description('Optional. The action of key rotation policy lifetimeAction.') + action: { + @description('Optional. The type of action.') + type: ('Notify' | 'Rotate')? + }? + + @description('Optional. The trigger of key rotation policy lifetimeAction.') + trigger: { + @description('Optional. The time duration after key creation to rotate the key. It only applies to rotate. It will be in ISO 8601 duration format. Eg: "P90D", "P1Y".') + timeAfterCreate: string? + + @description('Optional. The time duration before key expiring to rotate or notify. It will be in ISO 8601 duration format. Eg: "P90D", "P1Y".') + timeBeforeExpiry: string? + }? + }[]? +}? diff --git a/avm/res/key-vault/vault/main.json b/avm/res/key-vault/vault/main.json index 9a39a19b56..1cf0c42833 100644 --- a/avm/res/key-vault/vault/main.json +++ b/avm/res/key-vault/vault/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "15417452365743513256" + "version": "0.27.1.19265", + "templateHash": "1390366924133725054" }, "name": "Key Vaults", "description": "This module deploys a Key Vault.", @@ -555,6 +555,299 @@ } }, "nullable": true + }, + "secretsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the secret." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributes": { + "type": "object", + "properties": { + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Defines whether the secret is enabled or disabled." + } + }, + "exp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Defines when the secret will become invalid. Defined in seconds since 1970-01-01T00:00:00Z." + } + }, + "nbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. If set, defines the date from which onwards the secret becomes valid. Defined in seconds since 1970-01-01T00:00:00Z." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Contains attributes of the secret." + } + }, + "contentType": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The content type of the secret." + } + }, + "value": { + "type": "securestring", + "metadata": { + "description": "Required. The value of the secret. NOTE: \"value\" will never be returned from the service, as APIs using this model are is intended for internal use in ARM deployments. Users should use the data-plane REST service for interaction with vault secrets." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + } + }, + "nullable": true + }, + "keysType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the key." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributes": { + "type": "object", + "properties": { + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Defines whether the key is enabled or disabled." + } + }, + "exp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Defines when the key will become invalid. Defined in seconds since 1970-01-01T00:00:00Z." + } + }, + "nbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. If set, defines the date from which onwards the key becomes valid. Defined in seconds since 1970-01-01T00:00:00Z." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Contains attributes of the key." + } + }, + "curveName": { + "type": "string", + "allowedValues": [ + "P-256", + "P-256K", + "P-384", + "P-521" + ], + "nullable": true, + "metadata": { + "description": "Optional. The elliptic curve name. Only works if \"keySize\" equals \"EC\" or \"EC-HSM\". Default is \"P-256\"." + } + }, + "keyOps": { + "type": "array", + "allowedValues": [ + "decrypt", + "encrypt", + "import", + "release", + "sign", + "unwrapKey", + "verify", + "wrapKey" + ], + "nullable": true, + "metadata": { + "description": "Optional. The allowed operations on this key." + } + }, + "keySize": { + "type": "int", + "allowedValues": [ + 2048, + 3072, + 4096 + ], + "nullable": true, + "metadata": { + "description": "Optional. The key size in bits. Only works if \"keySize\" equals \"RSA\" or \"RSA-HSM\". Default is \"4096\"." + } + }, + "kty": { + "type": "string", + "allowedValues": [ + "EC", + "EC-HSM", + "RSA", + "RSA-HSM" + ], + "nullable": true, + "metadata": { + "description": "Optional. The type of the key. Default is \"EC\"." + } + }, + "releasePolicy": { + "type": "object", + "properties": { + "contentType": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Content type and version of key release policy." + } + }, + "data": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Blob encoding the policy rules under which the key can be released." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Key release policy." + } + }, + "rotationPolicy": { + "$ref": "#/definitions/rotationPoliciesType", + "nullable": true, + "metadata": { + "description": "Optional. Key rotation policy." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + } + }, + "nullable": true + }, + "rotationPoliciesType": { + "type": "object", + "properties": { + "attributes": { + "type": "object", + "properties": { + "expiryTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The expiration time for the new key version. It should be in ISO8601 format. Eg: \"P90D\", \"P1Y\"." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The attributes of key rotation policy." + } + }, + "lifetimeActions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "action": { + "type": "object", + "properties": { + "type": { + "type": "string", + "allowedValues": [ + "Notify", + "Rotate" + ], + "nullable": true, + "metadata": { + "description": "Optional. The type of action." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The action of key rotation policy lifetimeAction." + } + }, + "trigger": { + "type": "object", + "properties": { + "timeAfterCreate": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The time duration after key creation to rotate the key. It only applies to rotate. It will be in ISO 8601 duration format. Eg: \"P90D\", \"P1Y\"." + } + }, + "timeBeforeExpiry": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The time duration before key expiring to rotate or notify. It will be in ISO 8601 duration format. Eg: \"P90D\", \"P1Y\"." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The trigger of key rotation policy lifetimeAction." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The lifetimeActions for key rotation action." + } + } + }, + "nullable": true } }, "parameters": { @@ -579,14 +872,14 @@ } }, "secrets": { - "type": "secureObject", + "$ref": "#/definitions/secretsType", "nullable": true, "metadata": { "description": "Optional. All secrets to create." } }, "keys": { - "type": "array", + "$ref": "#/definitions/keysType", "nullable": true, "metadata": { "description": "Optional. All keys to create." @@ -745,8 +1038,7 @@ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" - }, - "secretList": "[coalesce(tryGet(parameters('secrets'), 'secureList'), createArray())]" + } }, "resources": { "avmTelemetry": { @@ -896,8 +1188,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "10878813547461142217" + "version": "0.27.1.19265", + "templateHash": "13379419349517171769" }, "name": "Key Vault Access Policies", "description": "This module deploys a Key Vault Access Policy.", @@ -1119,7 +1411,7 @@ "keyVault_secrets": { "copy": { "name": "keyVault_secrets", - "count": "[length(variables('secretList'))]" + "count": "[length(coalesce(parameters('secrets'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -1131,31 +1423,31 @@ "mode": "Incremental", "parameters": { "name": { - "value": "[variables('secretList')[copyIndex()].name]" + "value": "[coalesce(parameters('secrets'), createArray())[copyIndex()].name]" }, "value": { - "value": "[variables('secretList')[copyIndex()].value]" + "value": "[coalesce(parameters('secrets'), createArray())[copyIndex()].value]" }, "keyVaultName": { "value": "[parameters('name')]" }, "attributesEnabled": { - "value": "[tryGet(variables('secretList')[copyIndex()], 'attributesEnabled')]" + "value": "[tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'attributesEnabled')]" }, "attributesExp": { - "value": "[tryGet(variables('secretList')[copyIndex()], 'attributesExp')]" + "value": "[tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'attributesExp')]" }, "attributesNbf": { - "value": "[tryGet(variables('secretList')[copyIndex()], 'attributesNbf')]" + "value": "[tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'attributesNbf')]" }, "contentType": { - "value": "[tryGet(variables('secretList')[copyIndex()], 'contentType')]" + "value": "[tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'contentType')]" }, "tags": { - "value": "[coalesce(tryGet(variables('secretList')[copyIndex()], 'tags'), parameters('tags'))]" + "value": "[coalesce(tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" }, "roleAssignments": { - "value": "[tryGet(variables('secretList')[copyIndex()], 'roleAssignments')]" + "value": "[tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'roleAssignments')]" } }, "template": { @@ -1165,8 +1457,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "1877278864243602204" + "version": "0.27.1.19265", + "templateHash": "6420465919494344964" }, "name": "Key Vault Secrets", "description": "This module deploys a Key Vault Secret.", @@ -1425,14 +1717,13 @@ "attributesNbf": { "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributesNbf')]" }, - "curveName": { - "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'curveName'), 'P-256')]" - }, + "curveName": "[if(and(not(equals(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'RSA')), not(equals(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'RSA-HSM'))), createObject('value', coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'curveName'), 'P-256')), createObject('value', null()))]", "keyOps": { "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'keyOps')]" }, - "keySize": { - "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'keySize')]" + "keySize": "[if(or(equals(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'RSA'), equals(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'RSA-HSM')), createObject('value', coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'keySize'), 4096)), createObject('value', null()))]", + "releasePolicy": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'releasePolicy'), createObject())]" }, "kty": { "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'EC')]" @@ -1454,8 +1745,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "5903918450419813264" + "version": "0.27.1.19265", + "templateHash": "8655662111651470037" }, "name": "Key Vault Keys", "description": "This module deploys a Key Vault Key.", @@ -1619,6 +1910,13 @@ "description": "Optional. The type of the key." } }, + "releasePolicy": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Key release policy." + } + }, "roleAssignments": { "$ref": "#/definitions/roleAssignmentType", "metadata": { @@ -1673,7 +1971,8 @@ "keyOps": "[parameters('keyOps')]", "keySize": "[parameters('keySize')]", "kty": "[parameters('kty')]", - "rotationPolicy": "[parameters('rotationPolicy')]" + "rotationPolicy": "[coalesce(parameters('rotationPolicy'), createObject())]", + "release_policy": "[coalesce(parameters('releasePolicy'), createObject())]" }, "dependsOn": [ "keyVault" diff --git a/avm/res/key-vault/vault/secret/main.json b/avm/res/key-vault/vault/secret/main.json index 8f7481aa2c..ace5934ef0 100644 --- a/avm/res/key-vault/vault/secret/main.json +++ b/avm/res/key-vault/vault/secret/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "1877278864243602204" + "version": "0.27.1.19265", + "templateHash": "6420465919494344964" }, "name": "Key Vault Secrets", "description": "This module deploys a Key Vault Secret.", diff --git a/avm/res/key-vault/vault/tests/e2e/eckey/main.test.bicep b/avm/res/key-vault/vault/tests/e2e/eckey/main.test.bicep new file mode 100644 index 0000000000..820d3bab2e --- /dev/null +++ b/avm/res/key-vault/vault/tests/e2e/eckey/main.test.bicep @@ -0,0 +1,84 @@ +targetScope = 'subscription' + +metadata name = 'Using only defaults' +metadata description = 'This instance deploys the module with the minimum set of required parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-keyvault.vaults-${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 = 'kvvec' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// 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}002' + location: resourceLocation + // Only for testing purposes + enablePurgeProtection: false + enableRbacAuthorization: true + keys: [ + { + attributes: { + exp: 1725109032 + nbf: 10000 + } + name: 'keyName' + kty: 'EC' + rotationPolicy: { + attributes: { + expiryTime: 'P2Y' + } + lifetimeActions: [ + { + trigger: { + timeBeforeExpiry: 'P2M' + } + action: { + type: 'Rotate' + } + } + { + trigger: { + timeBeforeExpiry: 'P30D' + } + action: { + type: 'Notify' + } + } + ] + } + } + ] + } + } +] diff --git a/avm/res/key-vault/vault/tests/e2e/max/main.test.bicep b/avm/res/key-vault/vault/tests/e2e/max/main.test.bicep index e76b304815..2cceaafc54 100644 --- a/avm/res/key-vault/vault/tests/e2e/max/main.test.bicep +++ b/avm/res/key-vault/vault/tests/e2e/max/main.test.bicep @@ -262,37 +262,35 @@ module testDeployment '../../../main.bicep' = [ principalType: 'ServicePrincipal' } ] - secrets: { - secureList: [ - { - attributesExp: 1702648632 - attributesNbf: 10000 - contentType: 'Something' - name: 'secretName' - roleAssignments: [ - { - roleDefinitionIdOrName: 'Owner' - principalId: nestedDependencies.outputs.managedIdentityPrincipalId - principalType: 'ServicePrincipal' - } - { - roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' - principalId: nestedDependencies.outputs.managedIdentityPrincipalId - principalType: 'ServicePrincipal' - } - { - roleDefinitionIdOrName: subscriptionResourceId( - 'Microsoft.Authorization/roleDefinitions', - 'acdd72a7-3385-48ef-bd42-f606fba81ae7' - ) - principalId: nestedDependencies.outputs.managedIdentityPrincipalId - principalType: 'ServicePrincipal' - } - ] - value: 'secretValue' - } - ] - } + secrets: [ + { + attributesExp: 1702648632 + attributesNbf: 10000 + contentType: 'Something' + name: 'secretName' + roleAssignments: [ + { + roleDefinitionIdOrName: 'Owner' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'acdd72a7-3385-48ef-bd42-f606fba81ae7' + ) + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + value: 'secretValue' + } + ] softDeleteRetentionInDays: 7 tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/avm/res/key-vault/vault/tests/e2e/rsakey/main.test.bicep b/avm/res/key-vault/vault/tests/e2e/rsakey/main.test.bicep new file mode 100644 index 0000000000..09a406942b --- /dev/null +++ b/avm/res/key-vault/vault/tests/e2e/rsakey/main.test.bicep @@ -0,0 +1,84 @@ +targetScope = 'subscription' + +metadata name = 'Using only defaults' +metadata description = 'This instance deploys the module with the minimum set of required parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-keyvault.vaults-${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 = 'kvvrsa' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// 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}002' + location: resourceLocation + // Only for testing purposes + enablePurgeProtection: false + enableRbacAuthorization: true + keys: [ + { + attributes: { + exp: 1725109032 + nbf: 10000 + } + name: 'keyName' + kty: 'RSA' + rotationPolicy: { + attributes: { + expiryTime: 'P2Y' + } + lifetimeActions: [ + { + trigger: { + timeBeforeExpiry: 'P2M' + } + action: { + type: 'Rotate' + } + } + { + trigger: { + timeBeforeExpiry: 'P30D' + } + action: { + type: 'Notify' + } + } + ] + } + } + ] + } + } +] diff --git a/avm/res/key-vault/vault/tests/e2e/waf-aligned/main.test.bicep b/avm/res/key-vault/vault/tests/e2e/waf-aligned/main.test.bicep index f821d7012e..1f8905b423 100644 --- a/avm/res/key-vault/vault/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/key-vault/vault/tests/e2e/waf-aligned/main.test.bicep @@ -126,17 +126,15 @@ module testDeployment '../../../main.bicep' = [ subnetResourceId: nestedDependencies.outputs.subnetResourceId } ] - secrets: { - secureList: [ - { - attributesExp: 1702648632 - attributesNbf: 10000 - contentType: 'Something' - name: 'secretName' - value: 'secretValue' - } - ] - } + secrets: [ + { + attributesExp: 1702648632 + attributesNbf: 10000 + contentType: 'Something' + name: 'secretName' + value: 'secretValue' + } + ] softDeleteRetentionInDays: 7 tags: { 'hidden-title': 'This is visible in the resource name' diff --git a/avm/res/key-vault/vault/version.json b/avm/res/key-vault/vault/version.json index a8eda31021..9ed3662aba 100644 --- a/avm/res/key-vault/vault/version.json +++ b/avm/res/key-vault/vault/version.json @@ -1,7 +1,7 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.5", + "version": "0.6", "pathFilters": [ "./main.json" ] -} \ No newline at end of file +} From de3e67d6315c5d32de9def20900d01e9fd13292f Mon Sep 17 00:00:00 2001 From: ChrisSidebotham-MSFT <48600046+ChrisSidebotham@users.noreply.github.com> Date: Wed, 22 May 2024 10:45:42 +0100 Subject: [PATCH 07/18] fix: Several Storage Account fixes included replacement for #1508 - `avm/res/storage/storage-account` (#1987) ## Description ported changes from #1508 following comment from core team and fblix Fixes #1508 Closes #1674 Closes #1731 Closes #1385 Closes #1346 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.storage.storage-account](https://github.com/ChrisSidebotham/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml/badge.svg?branch=storage-fix)](https://github.com/ChrisSidebotham/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [x] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change (#1508 is open but should be abandoned) - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/storage/storage-account/README.md | 322 ++++++++------ .../storage-account/blob-service/README.md | 4 +- .../container/immutability-policy/main.json | 4 +- .../blob-service/container/main.json | 8 +- .../storage-account/blob-service/main.bicep | 122 ++--- .../storage-account/blob-service/main.json | 16 +- .../storage-account/file-service/README.md | 2 +- .../storage-account/file-service/main.bicep | 80 ++-- .../storage-account/file-service/main.json | 22 +- .../file-service/share/main.bicep | 4 +- .../file-service/share/main.json | 12 +- .../storage-account/local-user/README.md | 2 +- .../storage-account/local-user/main.bicep | 5 +- .../storage-account/local-user/main.json | 8 +- avm/res/storage/storage-account/main.bicep | 228 +++++----- avm/res/storage/storage-account/main.json | 128 +++--- .../management-policy/main.json | 4 +- .../storage-account/queue-service/README.md | 4 +- .../storage-account/queue-service/main.bicep | 72 +-- .../storage-account/queue-service/main.json | 18 +- .../queue-service/queue/README.md | 2 +- .../queue-service/queue/main.bicep | 86 ++-- .../queue-service/queue/main.json | 10 +- .../storage-account/table-service/README.md | 4 +- .../storage-account/table-service/main.bicep | 70 +-- .../storage-account/table-service/main.json | 18 +- .../table-service/table/README.md | 2 +- .../table-service/table/main.bicep | 76 +++- .../table-service/table/main.json | 10 +- .../tests/e2e/changefeed/main.test.bicep | 52 +++ .../tests/e2e/waf-aligned/main.test.bicep | 418 +++++++++--------- avm/res/storage/storage-account/version.json | 4 +- 32 files changed, 1025 insertions(+), 792 deletions(-) create mode 100644 avm/res/storage/storage-account/tests/e2e/changefeed/main.test.bicep diff --git a/avm/res/storage/storage-account/README.md b/avm/res/storage/storage-account/README.md index 83fba9bd49..6f0250fa03 100644 --- a/avm/res/storage/storage-account/README.md +++ b/avm/res/storage/storage-account/README.md @@ -25,14 +25,14 @@ This module deploys a Storage Account. | `Microsoft.Storage/storageAccounts/blobServices` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices) | | `Microsoft.Storage/storageAccounts/blobServices/containers` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices/containers) | | `Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices/containers/immutabilityPolicies) | -| `Microsoft.Storage/storageAccounts/fileServices` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/fileServices) | +| `Microsoft.Storage/storageAccounts/fileServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/fileServices) | | `Microsoft.Storage/storageAccounts/fileServices/shares` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/fileServices/shares) | -| `Microsoft.Storage/storageAccounts/localUsers` | [2022-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-05-01/storageAccounts/localUsers) | +| `Microsoft.Storage/storageAccounts/localUsers` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/localUsers) | | `Microsoft.Storage/storageAccounts/managementPolicies` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/managementPolicies) | -| `Microsoft.Storage/storageAccounts/queueServices` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/queueServices) | -| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/queueServices/queues) | -| `Microsoft.Storage/storageAccounts/tableServices` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/tableServices) | -| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/tableServices/tables) | +| `Microsoft.Storage/storageAccounts/queueServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices) | +| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices/queues) | +| `Microsoft.Storage/storageAccounts/tableServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices) | +| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices/tables) | ## Usage examples @@ -44,13 +44,14 @@ The following section provides usage examples for the module, which were used to - [Deploying as a Blob Storage](#example-1-deploying-as-a-blob-storage) - [Deploying as a Block Blob Storage](#example-2-deploying-as-a-block-blob-storage) -- [Using only defaults](#example-3-using-only-defaults) -- [Using large parameter set](#example-4-using-large-parameter-set) -- [Deploying with a NFS File Share](#example-5-deploying-with-a-nfs-file-share) -- [Using Customer-Managed-Keys with System-Assigned identity](#example-6-using-customer-managed-keys-with-system-assigned-identity) -- [Using Customer-Managed-Keys with User-Assigned identity](#example-7-using-customer-managed-keys-with-user-assigned-identity) -- [Deploying as Storage Account version 1](#example-8-deploying-as-storage-account-version-1) -- [WAF-aligned](#example-9-waf-aligned) +- [Using only changefeed configuration](#example-3-using-only-changefeed-configuration) +- [Using only defaults](#example-4-using-only-defaults) +- [Using large parameter set](#example-5-using-large-parameter-set) +- [Deploying with a NFS File Share](#example-6-deploying-with-a-nfs-file-share) +- [Using Customer-Managed-Keys with System-Assigned identity](#example-7-using-customer-managed-keys-with-system-assigned-identity) +- [Using Customer-Managed-Keys with User-Assigned identity](#example-8-using-customer-managed-keys-with-user-assigned-identity) +- [Deploying as Storage Account version 1](#example-9-deploying-as-storage-account-version-1) +- [WAF-aligned](#example-10-waf-aligned) ### Example 1: _Deploying as a Blob Storage_ @@ -164,7 +165,67 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = {

-### Example 3: _Using only defaults_ +### Example 3: _Using only changefeed configuration_ + +This instance deploys the module with the minimum set of required parameters for the changefeed configuration. + + +

+ +via Bicep module + +```bicep +module storageAccount 'br/public:avm/res/storage/storage-account:' = { + name: 'storageAccountDeployment' + params: { + // Required parameters + name: 'ssachf001' + // Non-required parameters + allowBlobPublicAccess: false + blobServices: { + changeFeedEnabled: true + } + location: '' + } +} +``` + +
+

+ +

+ +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": "ssachf001" + }, + // Non-required parameters + "allowBlobPublicAccess": { + "value": false + }, + "blobServices": { + "value": { + "changeFeedEnabled": true + } + }, + "location": { + "value": "" + } + } +} +``` + +
+

+ +### Example 4: _Using only defaults_ This instance deploys the module with the minimum set of required parameters. @@ -226,7 +287,7 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = {

-### Example 4: _Using large parameter set_ +### Example 5: _Using large parameter set_ This instance deploys the module with most of its features enabled. @@ -1092,7 +1153,7 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = {

-### Example 5: _Deploying with a NFS File Share_ +### Example 6: _Deploying with a NFS File Share_ This instance deploys the module with a NFS File Share. @@ -1166,7 +1227,7 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = {

-### Example 6: _Using Customer-Managed-Keys with System-Assigned identity_ +### Example 7: _Using Customer-Managed-Keys with System-Assigned identity_ This instance deploys the module using Customer-Managed-Keys using a System-Assigned Identity. This required the service to be deployed twice, once as a pre-requisite to create the System-Assigned Identity, and once to use it for accessing the Customer-Managed-Key secret. @@ -1270,7 +1331,7 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = {

-### Example 7: _Using Customer-Managed-Keys with User-Assigned identity_ +### Example 8: _Using Customer-Managed-Keys with User-Assigned identity_ This instance deploys the module using Customer-Managed-Keys using a User-Assigned Identity to access the Customer-Managed-Key secret. @@ -1390,7 +1451,7 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = {

-### Example 8: _Deploying as Storage Account version 1_ +### Example 9: _Deploying as Storage Account version 1_ This instance deploys the module as Storage Account version 1. @@ -1442,7 +1503,7 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = {

-### Example 9: _WAF-aligned_ +### Example 10: _WAF-aligned_ This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. @@ -1663,7 +1724,7 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = { } requireInfrastructureEncryption: true sasExpirationPeriod: '180.00:00:00' - skuName: 'Standard_LRS' + skuName: 'Standard_ZRS' tableServices: { diagnosticSettings: [ { @@ -1954,7 +2015,7 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = { "value": "180.00:00:00" }, "skuName": { - "value": "Standard_LRS" + "value": "Standard_ZRS" }, "tableServices": { "value": { @@ -2004,7 +2065,6 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = { | Parameter | Type | Description | | :-- | :-- | :-- | | [`name`](#parameter-name) | string | Name of the Storage Account. Must be lower-case. | -| [`networkAcls`](#parameter-networkacls) | object | Networks ACLs, this value contains IPs to whitelist and/or Subnet information. If in use, bypass needs to be supplied. For security reasons, it is recommended to set the DefaultAction Deny. | **Conditional parameters** @@ -2034,6 +2094,7 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = { | [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | | [`fileServices`](#parameter-fileservices) | object | File service and shares to deploy. | | [`isLocalUserEnabled`](#parameter-islocaluserenabled) | bool | Enables local users feature, if set to true. | +| [`keyType`](#parameter-keytype) | string | The keyType to use with Queue & Table services. | | [`kind`](#parameter-kind) | string | Type of Storage Account to create. | | [`largeFileSharesState`](#parameter-largefilesharesstate) | string | Allow large file shares if sets to 'Enabled'. It cannot be disabled once it is enabled. Only supported on locally redundant and zone redundant file shares. It cannot be set on FileStorage storage accounts (storage accounts for premium file shares). | | [`localUsers`](#parameter-localusers) | array | Local users to deploy for SFTP authentication. | @@ -2042,6 +2103,7 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = { | [`managedIdentities`](#parameter-managedidentities) | object | The managed identity definition for this resource. | | [`managementPolicyRules`](#parameter-managementpolicyrules) | array | The Storage Account ManagementPolicies Rules. | | [`minimumTlsVersion`](#parameter-minimumtlsversion) | string | Set the minimum TLS version on request to storage. | +| [`networkAcls`](#parameter-networkacls) | object | Networks ACLs, this value contains IPs to whitelist and/or Subnet information. If in use, bypass needs to be supplied. For security reasons, it is recommended to set the DefaultAction Deny. | | [`privateEndpoints`](#parameter-privateendpoints) | array | Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. | | [`publicNetworkAccess`](#parameter-publicnetworkaccess) | string | 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 and networkAcls are not set. | | [`queueServices`](#parameter-queueservices) | object | Queue service and queues to create. | @@ -2060,111 +2122,6 @@ Name of the Storage Account. Must be lower-case. - Required: Yes - Type: string -### Parameter: `networkAcls` - -Networks ACLs, this value contains IPs to whitelist and/or Subnet information. If in use, bypass needs to be supplied. For security reasons, it is recommended to set the DefaultAction Deny. - -- Required: No -- Type: object -- Default: - ```Bicep - { - bypass: 'AzureServices' - defaultAction: 'Deny' - } - ``` - -**Required parameters** - -| Parameter | Type | Description | -| :-- | :-- | :-- | -| [`bypass`](#parameter-networkaclsbypass) | string | Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Possible values are any combination of Logging,Metrics,AzureServices (For example, "Logging, Metrics"), or None to bypass none of those traffics. | -| [`defaultAction`](#parameter-networkaclsdefaultaction) | string | Specifies the default action of allow or deny when no other rules match. | - -**Optional parameters** - -| Parameter | Type | Description | -| :-- | :-- | :-- | -| [`ipRules`](#parameter-networkaclsiprules) | array | Sets the IP ACL rules. | -| [`resourceAccessRules`](#parameter-networkaclsresourceaccessrules) | array | Sets the resource access rules. Array entries must consist of "tenantId" and "resourceId" fields only. | -| [`virtualNetworkRules`](#parameter-networkaclsvirtualnetworkrules) | array | Sets the virtual network rules. | - -### Parameter: `networkAcls.bypass` - -Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Possible values are any combination of Logging,Metrics,AzureServices (For example, "Logging, Metrics"), or None to bypass none of those traffics. - -- Required: Yes -- Type: string -- Allowed: - ```Bicep - [ - 'AzureServices' - 'AzureServices, Logging' - 'AzureServices, Logging, Metrics' - 'AzureServices, Metrics' - 'Logging' - 'Logging, Metrics' - 'Metrics' - 'None' - ] - ``` - -### Parameter: `networkAcls.defaultAction` - -Specifies the default action of allow or deny when no other rules match. - -- Required: Yes -- Type: string -- Allowed: - ```Bicep - [ - 'Allow' - 'Deny' - ] - ``` - -### Parameter: `networkAcls.ipRules` - -Sets the IP ACL rules. - -- Required: No -- Type: array - -### Parameter: `networkAcls.resourceAccessRules` - -Sets the resource access rules. Array entries must consist of "tenantId" and "resourceId" fields only. - -- Required: No -- Type: array - -**Required parameters** - -| Parameter | Type | Description | -| :-- | :-- | :-- | -| [`resourceId`](#parameter-networkaclsresourceaccessrulesresourceid) | string | The resource ID of the target service. Can also contain a wildcard, if multiple services e.g. in a resource group should be included. | -| [`tenantId`](#parameter-networkaclsresourceaccessrulestenantid) | string | The ID of the tenant in which the resource resides in. | - -### Parameter: `networkAcls.resourceAccessRules.resourceId` - -The resource ID of the target service. Can also contain a wildcard, if multiple services e.g. in a resource group should be included. - -- Required: Yes -- Type: string - -### Parameter: `networkAcls.resourceAccessRules.tenantId` - -The ID of the tenant in which the resource resides in. - -- Required: Yes -- Type: string - -### Parameter: `networkAcls.virtualNetworkRules` - -Sets the virtual network rules. - -- Required: No -- Type: array - ### Parameter: `accessTier` Required if the Storage Account kind is set to BlobStorage. The access tier is used for billing. The "Premium" access tier is the default value for premium block blobs storage account type and it cannot be changed for the premium block blobs storage account type. @@ -2483,6 +2440,20 @@ Enables local users feature, if set to true. - Type: bool - Default: `False` +### Parameter: `keyType` + +The keyType to use with Queue & Table services. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Account' + 'Service' + ] + ``` + ### Parameter: `kind` Type of Storage Account to create. @@ -2619,6 +2590,99 @@ Set the minimum TLS version on request to storage. ] ``` +### Parameter: `networkAcls` + +Networks ACLs, this value contains IPs to whitelist and/or Subnet information. If in use, bypass needs to be supplied. For security reasons, it is recommended to set the DefaultAction Deny. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`bypass`](#parameter-networkaclsbypass) | string | Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Possible values are any combination of Logging,Metrics,AzureServices (For example, "Logging, Metrics"), or None to bypass none of those traffics. | +| [`defaultAction`](#parameter-networkaclsdefaultaction) | string | Specifies the default action of allow or deny when no other rules match. | +| [`ipRules`](#parameter-networkaclsiprules) | array | Sets the IP ACL rules. | +| [`resourceAccessRules`](#parameter-networkaclsresourceaccessrules) | array | Sets the resource access rules. Array entries must consist of "tenantId" and "resourceId" fields only. | +| [`virtualNetworkRules`](#parameter-networkaclsvirtualnetworkrules) | array | Sets the virtual network rules. | + +### Parameter: `networkAcls.bypass` + +Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Possible values are any combination of Logging,Metrics,AzureServices (For example, "Logging, Metrics"), or None to bypass none of those traffics. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'AzureServices' + 'AzureServices, Logging' + 'AzureServices, Logging, Metrics' + 'AzureServices, Metrics' + 'Logging' + 'Logging, Metrics' + 'Metrics' + 'None' + ] + ``` + +### Parameter: `networkAcls.defaultAction` + +Specifies the default action of allow or deny when no other rules match. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Allow' + 'Deny' + ] + ``` + +### Parameter: `networkAcls.ipRules` + +Sets the IP ACL rules. + +- Required: No +- Type: array + +### Parameter: `networkAcls.resourceAccessRules` + +Sets the resource access rules. Array entries must consist of "tenantId" and "resourceId" fields only. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`resourceId`](#parameter-networkaclsresourceaccessrulesresourceid) | string | The resource ID of the target service. Can also contain a wildcard, if multiple services e.g. in a resource group should be included. | +| [`tenantId`](#parameter-networkaclsresourceaccessrulestenantid) | string | The ID of the tenant in which the resource resides in. | + +### Parameter: `networkAcls.resourceAccessRules.resourceId` + +The resource ID of the target service. Can also contain a wildcard, if multiple services e.g. in a resource group should be included. + +- Required: Yes +- Type: string + +### Parameter: `networkAcls.resourceAccessRules.tenantId` + +The ID of the tenant in which the resource resides in. + +- Required: Yes +- Type: string + +### Parameter: `networkAcls.virtualNetworkRules` + +Sets the virtual network rules. + +- Required: No +- Type: array + ### Parameter: `privateEndpoints` Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. @@ -3137,7 +3201,7 @@ This section gives you an overview of all local-referenced module files (i.e., o | Reference | Type | | :-- | :-- | -| `br/public:avm/res/network/private-endpoint:0.4.0` | Remote reference | +| `br/public:avm/res/network/private-endpoint:0.4.1` | Remote reference | ## Notes diff --git a/avm/res/storage/storage-account/blob-service/README.md b/avm/res/storage/storage-account/blob-service/README.md index f66c244ced..93e379cf70 100644 --- a/avm/res/storage/storage-account/blob-service/README.md +++ b/avm/res/storage/storage-account/blob-service/README.md @@ -34,7 +34,7 @@ This module deploys a Storage Account Blob Service. | :-- | :-- | :-- | | [`automaticSnapshotPolicyEnabled`](#parameter-automaticsnapshotpolicyenabled) | bool | Automatic Snapshot is enabled if set to true. | | [`changeFeedEnabled`](#parameter-changefeedenabled) | bool | The blob service properties for change feed events. Indicates whether change feed event logging is enabled for the Blob service. | -| [`changeFeedRetentionInDays`](#parameter-changefeedretentionindays) | int | Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. A "0" value indicates an infinite retention of the change feed. | +| [`changeFeedRetentionInDays`](#parameter-changefeedretentionindays) | int | Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. If left blank, it indicates an infinite retention of the change feed. | | [`containerDeleteRetentionPolicyAllowPermanentDelete`](#parameter-containerdeleteretentionpolicyallowpermanentdelete) | bool | This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share. | | [`containerDeleteRetentionPolicyDays`](#parameter-containerdeleteretentionpolicydays) | int | Indicates the number of days that the deleted item should be retained. | | [`containerDeleteRetentionPolicyEnabled`](#parameter-containerdeleteretentionpolicyenabled) | bool | The blob service properties for container soft delete. Indicates whether DeleteRetentionPolicy is enabled. | @@ -75,7 +75,7 @@ The blob service properties for change feed events. Indicates whether change fee ### Parameter: `changeFeedRetentionInDays` -Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. A "0" value indicates an infinite retention of the change feed. +Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. If left blank, it indicates an infinite retention of the change feed. - Required: No - Type: int diff --git a/avm/res/storage/storage-account/blob-service/container/immutability-policy/main.json b/avm/res/storage/storage-account/blob-service/container/immutability-policy/main.json index a45e614327..1d3a01403a 100644 --- a/avm/res/storage/storage-account/blob-service/container/immutability-policy/main.json +++ b/avm/res/storage/storage-account/blob-service/container/immutability-policy/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "12849754295459852309" + "version": "0.27.1.19265", + "templateHash": "7418870035820197377" }, "name": "Storage Account Blob Container Immutability Policies", "description": "This module deploys a Storage Account Blob Container Immutability Policy.", diff --git a/avm/res/storage/storage-account/blob-service/container/main.json b/avm/res/storage/storage-account/blob-service/container/main.json index 0d487d2827..d759f9ab84 100644 --- a/avm/res/storage/storage-account/blob-service/container/main.json +++ b/avm/res/storage/storage-account/blob-service/container/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "3805384021483033369" + "version": "0.27.1.19265", + "templateHash": "7167285049910521671" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -274,8 +274,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "12849754295459852309" + "version": "0.27.1.19265", + "templateHash": "7418870035820197377" }, "name": "Storage Account Blob Container Immutability Policies", "description": "This module deploys a Storage Account Blob Container Immutability Policy.", diff --git a/avm/res/storage/storage-account/blob-service/main.bicep b/avm/res/storage/storage-account/blob-service/main.bicep index 4a71845d32..c02c02f6fa 100644 --- a/avm/res/storage/storage-account/blob-service/main.bicep +++ b/avm/res/storage/storage-account/blob-service/main.bicep @@ -12,9 +12,9 @@ param automaticSnapshotPolicyEnabled bool = false @description('Optional. The blob service properties for change feed events. Indicates whether change feed event logging is enabled for the Blob service.') param changeFeedEnabled bool = false -@minValue(0) +@minValue(1) @maxValue(146000) -@description('Optional. Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. A "0" value indicates an infinite retention of the change feed.') +@description('Optional. Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. If left blank, it indicates an infinite retention of the change feed.') param changeFeedRetentionInDays int? @description('Optional. The blob service properties for container soft delete. Indicates whether DeleteRetentionPolicy is enabled.') @@ -76,14 +76,18 @@ resource blobServices 'Microsoft.Storage/storageAccounts/blobServices@2022-09-01 parent: storageAccount properties: { automaticSnapshotPolicyEnabled: automaticSnapshotPolicyEnabled - changeFeed: changeFeedEnabled ? { - enabled: true - retentionInDays: changeFeedRetentionInDays - } : null + changeFeed: changeFeedEnabled + ? { + enabled: true + retentionInDays: changeFeedRetentionInDays + } + : null containerDeleteRetentionPolicy: { enabled: containerDeleteRetentionPolicyEnabled days: containerDeleteRetentionPolicyDays - allowPermanentDelete: containerDeleteRetentionPolicyEnabled == true ? containerDeleteRetentionPolicyAllowPermanentDelete : null + allowPermanentDelete: containerDeleteRetentionPolicyEnabled == true + ? containerDeleteRetentionPolicyAllowPermanentDelete + : null } cors: { corsRules: corsRules @@ -95,57 +99,69 @@ resource blobServices 'Microsoft.Storage/storageAccounts/blobServices@2022-09-01 allowPermanentDelete: deleteRetentionPolicyEnabled && deleteRetentionPolicyAllowPermanentDelete ? true : null } isVersioningEnabled: isVersioningEnabled - lastAccessTimeTrackingPolicy: storageAccount.kind != 'Storage' ? { - enable: lastAccessTimeTrackingPolicyEnabled - name: lastAccessTimeTrackingPolicyEnabled == true ? 'AccessTimeTracking' : null - trackingGranularityInDays: lastAccessTimeTrackingPolicyEnabled == true ? 1 : null - } : null - restorePolicy: restorePolicyEnabled ? { - enabled: true - days: restorePolicyDays - } : null + lastAccessTimeTrackingPolicy: storageAccount.kind != 'Storage' + ? { + enable: lastAccessTimeTrackingPolicyEnabled + name: lastAccessTimeTrackingPolicyEnabled == true ? 'AccessTimeTracking' : null + trackingGranularityInDays: lastAccessTimeTrackingPolicyEnabled == true ? 1 : null + } + : null + restorePolicy: restorePolicyEnabled + ? { + enabled: true + days: restorePolicyDays + } + : null } } -resource blobServices_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): { - name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' - properties: { - storageAccountId: diagnosticSetting.?storageAccountResourceId - workspaceId: diagnosticSetting.?workspaceResourceId - eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId - eventHubName: diagnosticSetting.?eventHubName - metrics: [for group in (diagnosticSetting.?metricCategories ?? [ { category: 'AllMetrics' } ]): { - category: group.category - enabled: group.?enabled ?? true - timeGrain: null - }] - logs: [for group in (diagnosticSetting.?logCategoriesAndGroups ?? [ { categoryGroup: 'allLogs' } ]): { - categoryGroup: group.?categoryGroup - category: group.?category - enabled: group.?enabled ?? true - }] - marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId - logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType +resource blobServices_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [ + for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + metrics: [ + for group in (diagnosticSetting.?metricCategories ?? [{ category: 'AllMetrics' }]): { + category: group.category + enabled: group.?enabled ?? true + timeGrain: null + } + ] + logs: [ + for group in (diagnosticSetting.?logCategoriesAndGroups ?? [{ categoryGroup: 'allLogs' }]): { + categoryGroup: group.?categoryGroup + category: group.?category + enabled: group.?enabled ?? true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: blobServices } - scope: blobServices -}] - -module blobServices_container 'container/main.bicep' = [for (container, index) in (containers ?? []): { - name: '${deployment().name}-Container-${index}' - params: { - storageAccountName: storageAccount.name - name: container.name - defaultEncryptionScope: container.?defaultEncryptionScope - denyEncryptionScopeOverride: container.?denyEncryptionScopeOverride - enableNfsV3AllSquash: container.?enableNfsV3AllSquash - enableNfsV3RootSquash: container.?enableNfsV3RootSquash - immutableStorageWithVersioningEnabled: container.?immutableStorageWithVersioningEnabled - metadata: container.?metadata - publicAccess: container.?publicAccess - roleAssignments: container.?roleAssignments - immutabilityPolicyProperties: container.?immutabilityPolicyProperties +] + +module blobServices_container 'container/main.bicep' = [ + for (container, index) in (containers ?? []): { + name: '${deployment().name}-Container-${index}' + params: { + storageAccountName: storageAccount.name + name: container.name + defaultEncryptionScope: container.?defaultEncryptionScope + denyEncryptionScopeOverride: container.?denyEncryptionScopeOverride + enableNfsV3AllSquash: container.?enableNfsV3AllSquash + enableNfsV3RootSquash: container.?enableNfsV3RootSquash + immutableStorageWithVersioningEnabled: container.?immutableStorageWithVersioningEnabled + metadata: container.?metadata + publicAccess: container.?publicAccess + roleAssignments: container.?roleAssignments + immutabilityPolicyProperties: container.?immutabilityPolicyProperties + } } -}] +] @description('The name of the deployed blob service.') output name string = blobServices.name diff --git a/avm/res/storage/storage-account/blob-service/main.json b/avm/res/storage/storage-account/blob-service/main.json index 1ffa10179f..5ec121ad9a 100644 --- a/avm/res/storage/storage-account/blob-service/main.json +++ b/avm/res/storage/storage-account/blob-service/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "7278814029590745003" + "version": "0.27.1.19265", + "templateHash": "14376792680036937652" }, "name": "Storage Account blob Services", "description": "This module deploys a Storage Account Blob Service.", @@ -159,10 +159,10 @@ "changeFeedRetentionInDays": { "type": "int", "nullable": true, - "minValue": 0, + "minValue": 1, "maxValue": 146000, "metadata": { - "description": "Optional. Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. A \"0\" value indicates an infinite retention of the change feed." + "description": "Optional. Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. If left blank, it indicates an infinite retention of the change feed." } }, "containerDeleteRetentionPolicyEnabled": { @@ -403,8 +403,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "3805384021483033369" + "version": "0.27.1.19265", + "templateHash": "7167285049910521671" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -672,8 +672,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "12849754295459852309" + "version": "0.27.1.19265", + "templateHash": "7418870035820197377" }, "name": "Storage Account Blob Container Immutability Policies", "description": "This module deploys a Storage Account Blob Container Immutability Policy.", diff --git a/avm/res/storage/storage-account/file-service/README.md b/avm/res/storage/storage-account/file-service/README.md index dbcfac9607..06532a47bc 100644 --- a/avm/res/storage/storage-account/file-service/README.md +++ b/avm/res/storage/storage-account/file-service/README.md @@ -15,7 +15,7 @@ This module deploys a Storage Account File Share Service. | Resource Type | API Version | | :-- | :-- | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | -| `Microsoft.Storage/storageAccounts/fileServices` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/fileServices) | +| `Microsoft.Storage/storageAccounts/fileServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/fileServices) | | `Microsoft.Storage/storageAccounts/fileServices/shares` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/fileServices/shares) | ## Parameters diff --git a/avm/res/storage/storage-account/file-service/main.bicep b/avm/res/storage/storage-account/file-service/main.bicep index 541f8a475e..63954599a6 100644 --- a/avm/res/storage/storage-account/file-service/main.bicep +++ b/avm/res/storage/storage-account/file-service/main.bicep @@ -26,11 +26,11 @@ param shares array? var defaultShareAccessTier = storageAccount.kind == 'FileStorage' ? 'Premium' : 'TransactionOptimized' // default share accessTier depends on the Storage Account kind: 'Premium' for 'FileStorage' kind, 'TransactionOptimized' otherwise -resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-04-01' existing = { name: storageAccountName } -resource fileServices 'Microsoft.Storage/storageAccounts/fileServices@2021-09-01' = { +resource fileServices 'Microsoft.Storage/storageAccounts/fileServices@2023-04-01' = { name: name parent: storageAccount properties: { @@ -39,42 +39,50 @@ resource fileServices 'Microsoft.Storage/storageAccounts/fileServices@2021-09-01 } } -resource fileServices_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): { - name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' - properties: { - storageAccountId: diagnosticSetting.?storageAccountResourceId - workspaceId: diagnosticSetting.?workspaceResourceId - eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId - eventHubName: diagnosticSetting.?eventHubName - metrics: [for group in (diagnosticSetting.?metricCategories ?? [ { category: 'AllMetrics' } ]): { - category: group.category - enabled: group.?enabled ?? true - timeGrain: null - }] - logs: [for group in (diagnosticSetting.?logCategoriesAndGroups ?? [ { categoryGroup: 'allLogs' } ]): { - categoryGroup: group.?categoryGroup - category: group.?category - enabled: group.?enabled ?? true - }] - marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId - logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType +resource fileServices_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [ + for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + metrics: [ + for group in (diagnosticSetting.?metricCategories ?? [{ category: 'AllMetrics' }]): { + category: group.category + enabled: group.?enabled ?? true + timeGrain: null + } + ] + logs: [ + for group in (diagnosticSetting.?logCategoriesAndGroups ?? [{ categoryGroup: 'allLogs' }]): { + categoryGroup: group.?categoryGroup + category: group.?category + enabled: group.?enabled ?? true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: fileServices } - scope: fileServices -}] - -module fileServices_shares 'share/main.bicep' = [for (share, index) in (shares ?? []): { - name: '${deployment().name}-shares-${index}' - params: { - storageAccountName: storageAccount.name - fileServicesName: fileServices.name - name: share.name - accessTier: share.?accessTier ?? defaultShareAccessTier - enabledProtocols: share.?enabledProtocols - rootSquash: share.?rootSquash - shareQuota: share.?shareQuota - roleAssignments: share.?roleAssignments +] + +module fileServices_shares 'share/main.bicep' = [ + for (share, index) in (shares ?? []): { + name: '${deployment().name}-shares-${index}' + params: { + storageAccountName: storageAccount.name + fileServicesName: fileServices.name + name: share.name + accessTier: share.?accessTier ?? defaultShareAccessTier + enabledProtocols: share.?enabledProtocols + rootSquash: share.?rootSquash + shareQuota: share.?shareQuota + roleAssignments: share.?roleAssignments + } } -}] +] @description('The name of the deployed file share service.') output name string = fileServices.name diff --git a/avm/res/storage/storage-account/file-service/main.json b/avm/res/storage/storage-account/file-service/main.json index 31a38bc821..8a78aa2117 100644 --- a/avm/res/storage/storage-account/file-service/main.json +++ b/avm/res/storage/storage-account/file-service/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "4306536926065797375" + "version": "0.27.1.19265", + "templateHash": "2249966746510096150" }, "name": "Storage Account File Share Services", "description": "This module deploys a Storage Account File Share Service.", @@ -184,12 +184,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "fileServices": { "type": "Microsoft.Storage/storageAccounts/fileServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", "properties": { "protocolSettings": "[parameters('protocolSettings')]", @@ -264,7 +264,7 @@ "value": "[coalesce(parameters('shares'), createArray())[copyIndex()].name]" }, "accessTier": { - "value": "[coalesce(tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'accessTier'), if(equals(reference('storageAccount', '2021-09-01', 'full').kind, 'FileStorage'), 'Premium', 'TransactionOptimized'))]" + "value": "[coalesce(tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'accessTier'), if(equals(reference('storageAccount', '2023-04-01', 'full').kind, 'FileStorage'), 'Premium', 'TransactionOptimized'))]" }, "enabledProtocols": { "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'enabledProtocols')]" @@ -286,8 +286,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "13618261904162439978" + "version": "0.27.1.19265", + "templateHash": "7785463723571874765" }, "name": "Storage Account File Shares", "description": "This module deploys a Storage Account File Share.", @@ -436,7 +436,7 @@ "storageAccount::fileService": { "existing": true, "type": "Microsoft.Storage/storageAccounts/fileServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('fileServicesName'))]", "dependsOn": [ "storageAccount" @@ -445,7 +445,7 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "fileShare": { @@ -486,8 +486,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "6057169747302051267" + "version": "0.27.1.19265", + "templateHash": "8943543243348777065" } }, "parameters": { diff --git a/avm/res/storage/storage-account/file-service/share/main.bicep b/avm/res/storage/storage-account/file-service/share/main.bicep index c5201745c0..dc84824652 100644 --- a/avm/res/storage/storage-account/file-service/share/main.bicep +++ b/avm/res/storage/storage-account/file-service/share/main.bicep @@ -42,10 +42,10 @@ param rootSquash string = 'NoRootSquash' @description('Optional. Array of role assignments to create.') param roleAssignments roleAssignmentType -resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-04-01' existing = { name: storageAccountName - resource fileService 'fileServices@2021-09-01' existing = { + resource fileService 'fileServices@2023-04-01' existing = { name: fileServicesName } } diff --git a/avm/res/storage/storage-account/file-service/share/main.json b/avm/res/storage/storage-account/file-service/share/main.json index 5e5f8ff5c6..0c623fd5bf 100644 --- a/avm/res/storage/storage-account/file-service/share/main.json +++ b/avm/res/storage/storage-account/file-service/share/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "13618261904162439978" + "version": "0.27.1.19265", + "templateHash": "7785463723571874765" }, "name": "Storage Account File Shares", "description": "This module deploys a Storage Account File Share.", @@ -155,7 +155,7 @@ "storageAccount::fileService": { "existing": true, "type": "Microsoft.Storage/storageAccounts/fileServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('fileServicesName'))]", "dependsOn": [ "storageAccount" @@ -164,7 +164,7 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "fileShare": { @@ -205,8 +205,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "6057169747302051267" + "version": "0.27.1.19265", + "templateHash": "8943543243348777065" } }, "parameters": { diff --git a/avm/res/storage/storage-account/local-user/README.md b/avm/res/storage/storage-account/local-user/README.md index 778c80e1b0..281c98643f 100644 --- a/avm/res/storage/storage-account/local-user/README.md +++ b/avm/res/storage/storage-account/local-user/README.md @@ -14,7 +14,7 @@ This module deploys a Storage Account Local User, which is used for SFTP authent | Resource Type | API Version | | :-- | :-- | -| `Microsoft.Storage/storageAccounts/localUsers` | [2022-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-05-01/storageAccounts/localUsers) | +| `Microsoft.Storage/storageAccounts/localUsers` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/localUsers) | ## Parameters diff --git a/avm/res/storage/storage-account/local-user/main.bicep b/avm/res/storage/storage-account/local-user/main.bicep index 464546e9fb..605e5c2f99 100644 --- a/avm/res/storage/storage-account/local-user/main.bicep +++ b/avm/res/storage/storage-account/local-user/main.bicep @@ -27,11 +27,11 @@ param permissionScopes array @description('Optional. The local user SSH authorized keys for SFTP.') param sshAuthorizedKeys sshAuthorizedKeysType -resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-04-01' existing = { name: storageAccountName } -resource localUsers 'Microsoft.Storage/storageAccounts/localUsers@2022-05-01' = { +resource localUsers 'Microsoft.Storage/storageAccounts/localUsers@2023-04-01' = { name: name parent: storageAccount properties: { @@ -59,7 +59,6 @@ output resourceId string = localUsers.id @secure() type sshAuthorizedKeysType = { - @description('Optional. The list of SSH authorized keys.') secureList: { @description('Optional. Description used to store the function/usage of the key.') diff --git a/avm/res/storage/storage-account/local-user/main.json b/avm/res/storage/storage-account/local-user/main.json index c5936241dd..68088b74d3 100644 --- a/avm/res/storage/storage-account/local-user/main.json +++ b/avm/res/storage/storage-account/local-user/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "14593329616022153178" + "version": "0.27.1.19265", + "templateHash": "1776027493778195392" }, "name": "Storage Account Local Users", "description": "This module deploys a Storage Account Local User, which is used for SFTP authentication.", @@ -101,12 +101,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "localUsers": { "type": "Microsoft.Storage/storageAccounts/localUsers", - "apiVersion": "2022-05-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", "properties": { "hasSharedKey": "[parameters('hasSharedKey')]", diff --git a/avm/res/storage/storage-account/main.bicep b/avm/res/storage/storage-account/main.bicep index 58ba1d817e..d17193cf6d 100644 --- a/avm/res/storage/storage-account/main.bicep +++ b/avm/res/storage/storage-account/main.bicep @@ -68,11 +68,8 @@ param privateEndpoints privateEndpointType @description('Optional. The Storage Account ManagementPolicies Rules.') param managementPolicyRules array? -@description('Required. Networks ACLs, this value contains IPs to whitelist and/or Subnet information. If in use, bypass needs to be supplied. For security reasons, it is recommended to set the DefaultAction Deny.') -param networkAcls networkAclsType = { - bypass: 'AzureServices' - defaultAction: 'Deny' -} +@description('Optional. Networks ACLs, this value contains IPs to whitelist and/or Subnet information. If in use, bypass needs to be supplied. For security reasons, it is recommended to set the DefaultAction Deny.') +param networkAcls networkAclsType? @description('Optional. A Boolean indicating whether or not the service applies a secondary layer of encryption with platform managed keys for data at rest. For security reasons, it is recommended to set it to true.') param requireInfrastructureEncryption bool = true @@ -176,6 +173,13 @@ param customerManagedKey customerManagedKeyType @description('Optional. The SAS expiration period. DD.HH:MM:SS.') param sasExpirationPeriod string = '' +@description('Optional. The keyType to use with Queue & Table services.') +@allowed([ + 'Account' + 'Service' +]) +param keyType string? + var supportsBlobService = kind == 'BlockBlobStorage' || kind == 'BlobStorage' || kind == 'StorageV2' || kind == 'Storage' var supportsFileService = kind == 'FileStorage' || kind == 'StorageV2' || kind == 'Storage' @@ -276,47 +280,43 @@ var builtInRoleNames = { ) } -resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = - if (enableTelemetry) { - name: '46d3xbcp.res.storage-storageaccount.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' - properties: { - mode: 'Incremental' - template: { - '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' - contentVersion: '1.0.0.0' - resources: [] - outputs: { - telemetry: { - type: 'String' - value: 'For more information, see https://aka.ms/avm/TelemetryInfo' - } +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { + name: '46d3xbcp.res.storage-storageaccount.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + outputs: { + telemetry: { + type: 'String' + value: 'For more information, see https://aka.ms/avm/TelemetryInfo' } } } } +} -resource cMKKeyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = - if (!empty(customerManagedKey.?keyVaultResourceId)) { - name: last(split((customerManagedKey.?keyVaultResourceId ?? 'dummyVault'), '/')) - scope: resourceGroup( - split((customerManagedKey.?keyVaultResourceId ?? '//'), '/')[2], - split((customerManagedKey.?keyVaultResourceId ?? '////'), '/')[4] - ) +resource cMKKeyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId)) { + name: last(split((customerManagedKey.?keyVaultResourceId ?? 'dummyVault'), '/')) + scope: resourceGroup( + split((customerManagedKey.?keyVaultResourceId ?? '//'), '/')[2], + split((customerManagedKey.?keyVaultResourceId ?? '////'), '/')[4] + ) - resource cMKKey 'keys@2023-02-01' existing = - if (!empty(customerManagedKey.?keyVaultResourceId) && !empty(customerManagedKey.?keyName)) { - name: customerManagedKey.?keyName ?? 'dummyKey' - } + resource cMKKey 'keys@2023-02-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId) && !empty(customerManagedKey.?keyName)) { + name: customerManagedKey.?keyName ?? 'dummyKey' } +} -resource cMKUserAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = - if (!empty(customerManagedKey.?userAssignedIdentityResourceId)) { - name: last(split(customerManagedKey.?userAssignedIdentityResourceId ?? 'dummyMsi', '/')) - scope: resourceGroup( - split((customerManagedKey.?userAssignedIdentityResourceId ?? '//'), '/')[2], - split((customerManagedKey.?userAssignedIdentityResourceId ?? '////'), '/')[4] - ) - } +resource cMKUserAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(customerManagedKey.?userAssignedIdentityResourceId)) { + name: last(split(customerManagedKey.?userAssignedIdentityResourceId ?? 'dummyMsi', '/')) + scope: resourceGroup( + split((customerManagedKey.?userAssignedIdentityResourceId ?? '//'), '/')[2], + split((customerManagedKey.?userAssignedIdentityResourceId ?? '////'), '/')[4] + ) +} resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = { name: name @@ -354,9 +354,11 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = { : null table: { enabled: true + keyType: keyType } queue: { enabled: true + keyType: keyType } } keyvaultproperties: !empty(customerManagedKey) @@ -397,11 +399,15 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = { ? { resourceAccessRules: networkAcls.?resourceAccessRules bypass: networkAcls.?bypass - defaultAction: networkAcls.?defaultAction + defaultAction: networkAcls.?defaultAction ?? 'Deny' virtualNetworkRules: networkAcls.?virtualNetworkRules ipRules: networkAcls.?ipRules } - : null + : { + // New default case that enables the firewall by default + bypass: 'AzureServices' + defaultAction: 'Deny' + } allowBlobPublicAccess: allowBlobPublicAccess publicNetworkAccess: !empty(publicNetworkAccess) ? any(publicNetworkAccess) @@ -434,17 +440,16 @@ resource storageAccount_diagnosticSettings 'Microsoft.Insights/diagnosticSetting } ] -resource storageAccount_lock 'Microsoft.Authorization/locks@2020-05-01' = - if (!empty(lock ?? {}) && lock.?kind != 'None') { - name: lock.?name ?? 'lock-${name}' - properties: { - level: lock.?kind ?? '' - notes: lock.?kind == 'CanNotDelete' - ? 'Cannot delete resource or child resources.' - : 'Cannot delete or modify the resource or child resources.' - } - scope: storageAccount +resource storageAccount_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' + ? 'Cannot delete resource or child resources.' + : 'Cannot delete or modify the resource or child resources.' } + scope: storageAccount +} resource storageAccount_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ for (roleAssignment, index) in (roleAssignments ?? []): { @@ -466,7 +471,7 @@ resource storageAccount_roleAssignments 'Microsoft.Authorization/roleAssignments } ] -module storageAccount_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.4.0' = [ +module storageAccount_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.4.1' = [ for (privateEndpoint, index) in (privateEndpoints ?? []): { name: '${uniqueString(deployment().name, location)}-StorageAccount-PrivateEndpoint-${index}' params: { @@ -519,17 +524,16 @@ module storageAccount_privateEndpoints 'br/public:avm/res/network/private-endpoi ] // Lifecycle Policy -module storageAccount_managementPolicies 'management-policy/main.bicep' = - if (!empty(managementPolicyRules ?? [])) { - name: '${uniqueString(deployment().name, location)}-Storage-ManagementPolicies' - params: { - storageAccountName: storageAccount.name - rules: managementPolicyRules ?? [] - } - dependsOn: [ - storageAccount_blobServices // To ensure the lastAccessTimeTrackingPolicy is set first (if used in rule) - ] +module storageAccount_managementPolicies 'management-policy/main.bicep' = if (!empty(managementPolicyRules ?? [])) { + name: '${uniqueString(deployment().name, location)}-Storage-ManagementPolicies' + params: { + storageAccountName: storageAccount.name + rules: managementPolicyRules ?? [] } + dependsOn: [ + storageAccount_blobServices // To ensure the lastAccessTimeTrackingPolicy is set first (if used in rule) + ] +} // SFTP user settings module storageAccount_localUsers 'local-user/main.bicep' = [ @@ -549,65 +553,61 @@ module storageAccount_localUsers 'local-user/main.bicep' = [ ] // Containers -module storageAccount_blobServices 'blob-service/main.bicep' = - if (!empty(blobServices)) { - name: '${uniqueString(deployment().name, location)}-Storage-BlobServices' - params: { - storageAccountName: storageAccount.name - containers: blobServices.?containers - automaticSnapshotPolicyEnabled: blobServices.?automaticSnapshotPolicyEnabled - changeFeedEnabled: blobServices.?changeFeedEnabled - changeFeedRetentionInDays: blobServices.?changeFeedRetentionInDays - containerDeleteRetentionPolicyEnabled: blobServices.?containerDeleteRetentionPolicyEnabled - containerDeleteRetentionPolicyDays: blobServices.?containerDeleteRetentionPolicyDays - containerDeleteRetentionPolicyAllowPermanentDelete: blobServices.?containerDeleteRetentionPolicyAllowPermanentDelete - corsRules: blobServices.?corsRules - defaultServiceVersion: blobServices.?defaultServiceVersion - deleteRetentionPolicyAllowPermanentDelete: blobServices.?deleteRetentionPolicyAllowPermanentDelete - deleteRetentionPolicyEnabled: blobServices.?deleteRetentionPolicyEnabled - deleteRetentionPolicyDays: blobServices.?deleteRetentionPolicyDays - isVersioningEnabled: blobServices.?isVersioningEnabled - lastAccessTimeTrackingPolicyEnabled: blobServices.?lastAccessTimeTrackingPolicyEnabled - restorePolicyEnabled: blobServices.?restorePolicyEnabled - restorePolicyDays: blobServices.?restorePolicyDays - diagnosticSettings: blobServices.?diagnosticSettings - } +module storageAccount_blobServices 'blob-service/main.bicep' = if (!empty(blobServices)) { + name: '${uniqueString(deployment().name, location)}-Storage-BlobServices' + params: { + storageAccountName: storageAccount.name + containers: blobServices.?containers + automaticSnapshotPolicyEnabled: blobServices.?automaticSnapshotPolicyEnabled + changeFeedEnabled: blobServices.?changeFeedEnabled + changeFeedRetentionInDays: blobServices.?changeFeedRetentionInDays + containerDeleteRetentionPolicyEnabled: blobServices.?containerDeleteRetentionPolicyEnabled + containerDeleteRetentionPolicyDays: blobServices.?containerDeleteRetentionPolicyDays + containerDeleteRetentionPolicyAllowPermanentDelete: blobServices.?containerDeleteRetentionPolicyAllowPermanentDelete + corsRules: blobServices.?corsRules + defaultServiceVersion: blobServices.?defaultServiceVersion + deleteRetentionPolicyAllowPermanentDelete: blobServices.?deleteRetentionPolicyAllowPermanentDelete + deleteRetentionPolicyEnabled: blobServices.?deleteRetentionPolicyEnabled + deleteRetentionPolicyDays: blobServices.?deleteRetentionPolicyDays + isVersioningEnabled: blobServices.?isVersioningEnabled + lastAccessTimeTrackingPolicyEnabled: blobServices.?lastAccessTimeTrackingPolicyEnabled + restorePolicyEnabled: blobServices.?restorePolicyEnabled + restorePolicyDays: blobServices.?restorePolicyDays + diagnosticSettings: blobServices.?diagnosticSettings } +} // File Shares -module storageAccount_fileServices 'file-service/main.bicep' = - if (!empty(fileServices)) { - name: '${uniqueString(deployment().name, location)}-Storage-FileServices' - params: { - storageAccountName: storageAccount.name - diagnosticSettings: fileServices.?diagnosticSettings - protocolSettings: fileServices.?protocolSettings - shareDeleteRetentionPolicy: fileServices.?shareDeleteRetentionPolicy - shares: fileServices.?shares - } +module storageAccount_fileServices 'file-service/main.bicep' = if (!empty(fileServices)) { + name: '${uniqueString(deployment().name, location)}-Storage-FileServices' + params: { + storageAccountName: storageAccount.name + diagnosticSettings: fileServices.?diagnosticSettings + protocolSettings: fileServices.?protocolSettings + shareDeleteRetentionPolicy: fileServices.?shareDeleteRetentionPolicy + shares: fileServices.?shares } +} // Queue -module storageAccount_queueServices 'queue-service/main.bicep' = - if (!empty(queueServices)) { - name: '${uniqueString(deployment().name, location)}-Storage-QueueServices' - params: { - storageAccountName: storageAccount.name - diagnosticSettings: queueServices.?diagnosticSettings - queues: queueServices.?queues - } +module storageAccount_queueServices 'queue-service/main.bicep' = if (!empty(queueServices)) { + name: '${uniqueString(deployment().name, location)}-Storage-QueueServices' + params: { + storageAccountName: storageAccount.name + diagnosticSettings: queueServices.?diagnosticSettings + queues: queueServices.?queues } +} // Table -module storageAccount_tableServices 'table-service/main.bicep' = - if (!empty(tableServices)) { - name: '${uniqueString(deployment().name, location)}-Storage-TableServices' - params: { - storageAccountName: storageAccount.name - diagnosticSettings: tableServices.?diagnosticSettings - tables: tableServices.?tables - } +module storageAccount_tableServices 'table-service/main.bicep' = if (!empty(tableServices)) { + name: '${uniqueString(deployment().name, location)}-Storage-TableServices' + params: { + storageAccountName: storageAccount.name + diagnosticSettings: tableServices.?diagnosticSettings + tables: tableServices.?tables } +} @description('The resource ID of the deployed storage account.') output resourceId string = storageAccount.id @@ -682,7 +682,7 @@ type networkAclsType = { resourceId: string }[]? - @description('Required. Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Possible values are any combination of Logging,Metrics,AzureServices (For example, "Logging, Metrics"), or None to bypass none of those traffics.') + @description('Optional. Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Possible values are any combination of Logging,Metrics,AzureServices (For example, "Logging, Metrics"), or None to bypass none of those traffics.') bypass: ( | 'None' | 'AzureServices' @@ -691,7 +691,7 @@ type networkAclsType = { | 'AzureServices, Logging' | 'AzureServices, Metrics' | 'AzureServices, Logging, Metrics' - | 'Logging, Metrics') + | 'Logging, Metrics')? @description('Optional. Sets the virtual network rules.') virtualNetworkRules: array? @@ -699,8 +699,8 @@ type networkAclsType = { @description('Optional. Sets the IP ACL rules.') ipRules: array? - @description('Required. Specifies the default action of allow or deny when no other rules match.') - defaultAction: ('Allow' | 'Deny') + @description('Optional. Specifies the default action of allow or deny when no other rules match.') + defaultAction: ('Allow' | 'Deny')? } type privateEndpointType = { diff --git a/avm/res/storage/storage-account/main.json b/avm/res/storage/storage-account/main.json index 91eb9c9f57..2e541bd98a 100644 --- a/avm/res/storage/storage-account/main.json +++ b/avm/res/storage/storage-account/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "8863225181818962940" + "version": "0.27.1.19265", + "templateHash": "8254230526893780963" }, "name": "Storage Accounts", "description": "This module deploys a Storage Account.", @@ -166,8 +166,9 @@ "Metrics", "None" ], + "nullable": true, "metadata": { - "description": "Required. Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Possible values are any combination of Logging,Metrics,AzureServices (For example, \"Logging, Metrics\"), or None to bypass none of those traffics." + "description": "Optional. Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Possible values are any combination of Logging,Metrics,AzureServices (For example, \"Logging, Metrics\"), or None to bypass none of those traffics." } }, "virtualNetworkRules": { @@ -190,8 +191,9 @@ "Allow", "Deny" ], + "nullable": true, "metadata": { - "description": "Required. Specifies the default action of allow or deny when no other rules match." + "description": "Optional. Specifies the default action of allow or deny when no other rules match." } } } @@ -615,12 +617,9 @@ }, "networkAcls": { "$ref": "#/definitions/networkAclsType", - "defaultValue": { - "bypass": "AzureServices", - "defaultAction": "Deny" - }, + "nullable": true, "metadata": { - "description": "Required. Networks ACLs, this value contains IPs to whitelist and/or Subnet information. If in use, bypass needs to be supplied. For security reasons, it is recommended to set the DefaultAction Deny." + "description": "Optional. Networks ACLs, this value contains IPs to whitelist and/or Subnet information. If in use, bypass needs to be supplied. For security reasons, it is recommended to set the DefaultAction Deny." } }, "requireInfrastructureEncryption": { @@ -814,6 +813,17 @@ "metadata": { "description": "Optional. The SAS expiration period. DD.HH:MM:SS." } + }, + "keyType": { + "type": "string", + "nullable": true, + "allowedValues": [ + "Account", + "Service" + ], + "metadata": { + "description": "Optional. The keyType to use with Queue & Table services." + } } }, "variables": { @@ -919,7 +929,7 @@ }, "dnsEndpointType": "[if(not(empty(parameters('dnsEndpointType'))), parameters('dnsEndpointType'), null())]", "isLocalUserEnabled": "[parameters('isLocalUserEnabled')]", - "encryption": "[union(createObject('keySource', if(not(empty(parameters('customerManagedKey'))), 'Microsoft.Keyvault', 'Microsoft.Storage'), 'services', createObject('blob', if(variables('supportsBlobService'), createObject('enabled', true()), null()), 'file', if(variables('supportsFileService'), createObject('enabled', true()), null()), 'table', createObject('enabled', true()), 'queue', createObject('enabled', true())), 'keyvaultproperties', if(not(empty(parameters('customerManagedKey'))), createObject('keyname', parameters('customerManagedKey').keyName, 'keyvaulturi', reference('cMKKeyVault').vaultUri, 'keyversion', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'keyVersion'), ''))), parameters('customerManagedKey').keyVersion, last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/')))), null()), 'identity', createObject('userAssignedIdentity', if(not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'))), 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()))), if(parameters('requireInfrastructureEncryption'), createObject('requireInfrastructureEncryption', if(not(equals(parameters('kind'), 'Storage')), parameters('requireInfrastructureEncryption'), null())), createObject()))]", + "encryption": "[union(createObject('keySource', if(not(empty(parameters('customerManagedKey'))), 'Microsoft.Keyvault', 'Microsoft.Storage'), 'services', createObject('blob', if(variables('supportsBlobService'), createObject('enabled', true()), null()), 'file', if(variables('supportsFileService'), createObject('enabled', true()), null()), 'table', createObject('enabled', true(), 'keyType', parameters('keyType')), 'queue', createObject('enabled', true(), 'keyType', parameters('keyType'))), 'keyvaultproperties', if(not(empty(parameters('customerManagedKey'))), createObject('keyname', parameters('customerManagedKey').keyName, 'keyvaulturi', reference('cMKKeyVault').vaultUri, 'keyversion', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'keyVersion'), ''))), parameters('customerManagedKey').keyVersion, last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/')))), null()), 'identity', createObject('userAssignedIdentity', if(not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'))), 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()))), if(parameters('requireInfrastructureEncryption'), createObject('requireInfrastructureEncryption', if(not(equals(parameters('kind'), 'Storage')), parameters('requireInfrastructureEncryption'), null())), createObject()))]", "accessTier": "[if(and(not(equals(parameters('kind'), 'Storage')), not(equals(parameters('kind'), 'BlockBlobStorage'))), parameters('accessTier'), null())]", "sasPolicy": "[if(not(empty(parameters('sasExpirationPeriod'))), createObject('expirationAction', 'Log', 'sasExpirationPeriod', parameters('sasExpirationPeriod')), null())]", "supportsHttpsTrafficOnly": "[parameters('supportsHttpsTrafficOnly')]", @@ -928,7 +938,7 @@ "isNfsV3Enabled": "[if(parameters('enableNfsV3'), parameters('enableNfsV3'), '')]", "largeFileSharesState": "[if(or(equals(parameters('skuName'), 'Standard_LRS'), equals(parameters('skuName'), 'Standard_ZRS')), parameters('largeFileSharesState'), null())]", "minimumTlsVersion": "[parameters('minimumTlsVersion')]", - "networkAcls": "[if(not(empty(parameters('networkAcls'))), createObject('resourceAccessRules', tryGet(parameters('networkAcls'), 'resourceAccessRules'), 'bypass', tryGet(parameters('networkAcls'), 'bypass'), 'defaultAction', tryGet(parameters('networkAcls'), 'defaultAction'), 'virtualNetworkRules', tryGet(parameters('networkAcls'), 'virtualNetworkRules'), 'ipRules', tryGet(parameters('networkAcls'), 'ipRules')), null())]", + "networkAcls": "[if(not(empty(parameters('networkAcls'))), createObject('resourceAccessRules', tryGet(parameters('networkAcls'), 'resourceAccessRules'), 'bypass', tryGet(parameters('networkAcls'), 'bypass'), 'defaultAction', coalesce(tryGet(parameters('networkAcls'), 'defaultAction'), 'Deny'), 'virtualNetworkRules', tryGet(parameters('networkAcls'), 'virtualNetworkRules'), 'ipRules', tryGet(parameters('networkAcls'), 'ipRules')), createObject('bypass', 'AzureServices', 'defaultAction', 'Deny'))]", "allowBlobPublicAccess": "[parameters('allowBlobPublicAccess')]", "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(not(empty(parameters('privateEndpoints'))), empty(parameters('networkAcls'))), 'Disabled', null()))]", "azureFilesIdentityBasedAuthentication": "[if(not(empty(parameters('azureFilesIdentityBasedAuthentication'))), parameters('azureFilesIdentityBasedAuthentication'), null())]" @@ -1069,8 +1079,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.24.24.22086", - "templateHash": "2592884001616184297" + "version": "0.25.53.49325", + "templateHash": "4120048060064073955" }, "name": "Private Endpoints", "description": "This module deploys a Private Endpoint.", @@ -1435,7 +1445,7 @@ "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2023-07-01", - "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.4.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.4.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -1540,8 +1550,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.24.24.22086", - "templateHash": "9321937464667207030" + "version": "0.25.53.49325", + "templateHash": "11244630631275470040" }, "name": "Private Endpoint Private DNS Zone Groups", "description": "This module deploys a Private Endpoint Private DNS Zone Group.", @@ -1691,8 +1701,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "4153955640795225346" + "version": "0.27.1.19265", + "templateHash": "9379936754195214669" }, "name": "Storage Account Management Policies", "description": "This module deploys a Storage Account Management Policy.", @@ -1801,8 +1811,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "14593329616022153178" + "version": "0.27.1.19265", + "templateHash": "1776027493778195392" }, "name": "Storage Account Local Users", "description": "This module deploys a Storage Account Local User, which is used for SFTP authentication.", @@ -1897,12 +1907,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "localUsers": { "type": "Microsoft.Storage/storageAccounts/localUsers", - "apiVersion": "2022-05-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", "properties": { "hasSharedKey": "[parameters('hasSharedKey')]", @@ -2019,8 +2029,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "7278814029590745003" + "version": "0.27.1.19265", + "templateHash": "14376792680036937652" }, "name": "Storage Account blob Services", "description": "This module deploys a Storage Account Blob Service.", @@ -2173,10 +2183,10 @@ "changeFeedRetentionInDays": { "type": "int", "nullable": true, - "minValue": 0, + "minValue": 1, "maxValue": 146000, "metadata": { - "description": "Optional. Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. A \"0\" value indicates an infinite retention of the change feed." + "description": "Optional. Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. If left blank, it indicates an infinite retention of the change feed." } }, "containerDeleteRetentionPolicyEnabled": { @@ -2417,8 +2427,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "3805384021483033369" + "version": "0.27.1.19265", + "templateHash": "7167285049910521671" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -2686,8 +2696,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "12849754295459852309" + "version": "0.27.1.19265", + "templateHash": "7418870035820197377" }, "name": "Storage Account Blob Container Immutability Policies", "description": "This module deploys a Storage Account Blob Container Immutability Policy.", @@ -2865,8 +2875,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "4306536926065797375" + "version": "0.27.1.19265", + "templateHash": "2249966746510096150" }, "name": "Storage Account File Share Services", "description": "This module deploys a Storage Account File Share Service.", @@ -3044,12 +3054,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "fileServices": { "type": "Microsoft.Storage/storageAccounts/fileServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", "properties": { "protocolSettings": "[parameters('protocolSettings')]", @@ -3124,7 +3134,7 @@ "value": "[coalesce(parameters('shares'), createArray())[copyIndex()].name]" }, "accessTier": { - "value": "[coalesce(tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'accessTier'), if(equals(reference('storageAccount', '2021-09-01', 'full').kind, 'FileStorage'), 'Premium', 'TransactionOptimized'))]" + "value": "[coalesce(tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'accessTier'), if(equals(reference('storageAccount', '2023-04-01', 'full').kind, 'FileStorage'), 'Premium', 'TransactionOptimized'))]" }, "enabledProtocols": { "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'enabledProtocols')]" @@ -3146,8 +3156,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "13618261904162439978" + "version": "0.27.1.19265", + "templateHash": "7785463723571874765" }, "name": "Storage Account File Shares", "description": "This module deploys a Storage Account File Share.", @@ -3296,7 +3306,7 @@ "storageAccount::fileService": { "existing": true, "type": "Microsoft.Storage/storageAccounts/fileServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('fileServicesName'))]", "dependsOn": [ "storageAccount" @@ -3305,7 +3315,7 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "fileShare": { @@ -3346,8 +3356,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "6057169747302051267" + "version": "0.27.1.19265", + "templateHash": "8943543243348777065" } }, "parameters": { @@ -3615,8 +3625,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "4494965320132257914" + "version": "0.27.1.19265", + "templateHash": "8505361210488414487" }, "name": "Storage Account Queue Services", "description": "This module deploys a Storage Account Queue Service.", @@ -3773,12 +3783,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "queueServices": { "type": "Microsoft.Storage/storageAccounts/queueServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", "properties": {}, "dependsOn": [ @@ -3860,8 +3870,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "2870814699283520878" + "version": "0.27.1.19265", + "templateHash": "11111133794570176541" }, "name": "Storage Account Queues", "description": "This module deploys a Storage Account Queue.", @@ -3984,7 +3994,7 @@ "storageAccount::queueServices": { "existing": true, "type": "Microsoft.Storage/storageAccounts/queueServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", "dependsOn": [ "storageAccount" @@ -3993,12 +4003,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "queue": { "type": "Microsoft.Storage/storageAccounts/queueServices/queues", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", "properties": { "metadata": "[parameters('metadata')]" @@ -4117,8 +4127,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "6806949296172999226" + "version": "0.27.1.19265", + "templateHash": "6164468877254681063" }, "name": "Storage Account Table Services", "description": "This module deploys a Storage Account Table Service.", @@ -4275,12 +4285,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "tableServices": { "type": "Microsoft.Storage/storageAccounts/tableServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", "properties": {}, "dependsOn": [ @@ -4359,8 +4369,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "5583637725561111612" + "version": "0.27.1.19265", + "templateHash": "10885620503239852206" }, "name": "Storage Account Table", "description": "This module deploys a Storage Account Table.", @@ -4474,7 +4484,7 @@ "storageAccount::tableServices": { "existing": true, "type": "Microsoft.Storage/storageAccounts/tableServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", "dependsOn": [ "storageAccount" @@ -4483,12 +4493,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "table": { "type": "Microsoft.Storage/storageAccounts/tableServices/tables", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", "dependsOn": [ "storageAccount::tableServices" diff --git a/avm/res/storage/storage-account/management-policy/main.json b/avm/res/storage/storage-account/management-policy/main.json index d751651d08..e2dda43057 100644 --- a/avm/res/storage/storage-account/management-policy/main.json +++ b/avm/res/storage/storage-account/management-policy/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "4153955640795225346" + "version": "0.27.1.19265", + "templateHash": "9379936754195214669" }, "name": "Storage Account Management Policies", "description": "This module deploys a Storage Account Management Policy.", diff --git a/avm/res/storage/storage-account/queue-service/README.md b/avm/res/storage/storage-account/queue-service/README.md index 74a831bd19..eec41a0e4c 100644 --- a/avm/res/storage/storage-account/queue-service/README.md +++ b/avm/res/storage/storage-account/queue-service/README.md @@ -16,8 +16,8 @@ This module deploys a Storage Account Queue Service. | :-- | :-- | | `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.Storage/storageAccounts/queueServices` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/queueServices) | -| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/queueServices/queues) | +| `Microsoft.Storage/storageAccounts/queueServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices) | +| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices/queues) | ## Parameters diff --git a/avm/res/storage/storage-account/queue-service/main.bicep b/avm/res/storage/storage-account/queue-service/main.bicep index 046bbd45f3..8bb3d4f8f2 100644 --- a/avm/res/storage/storage-account/queue-service/main.bicep +++ b/avm/res/storage/storage-account/queue-service/main.bicep @@ -15,48 +15,56 @@ param diagnosticSettings diagnosticSettingType // The name of the blob services var name = 'default' -resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-04-01' existing = { name: storageAccountName } -resource queueServices 'Microsoft.Storage/storageAccounts/queueServices@2021-09-01' = { +resource queueServices 'Microsoft.Storage/storageAccounts/queueServices@2023-04-01' = { name: name parent: storageAccount properties: {} } -resource queueServices_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): { - name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' - properties: { - storageAccountId: diagnosticSetting.?storageAccountResourceId - workspaceId: diagnosticSetting.?workspaceResourceId - eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId - eventHubName: diagnosticSetting.?eventHubName - metrics: [for group in (diagnosticSetting.?metricCategories ?? [ { category: 'AllMetrics' } ]): { - category: group.category - enabled: group.?enabled ?? true - timeGrain: null - }] - logs: [for group in (diagnosticSetting.?logCategoriesAndGroups ?? [ { categoryGroup: 'allLogs' } ]): { - categoryGroup: group.?categoryGroup - category: group.?category - enabled: group.?enabled ?? true - }] - marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId - logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType +resource queueServices_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [ + for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + metrics: [ + for group in (diagnosticSetting.?metricCategories ?? [{ category: 'AllMetrics' }]): { + category: group.category + enabled: group.?enabled ?? true + timeGrain: null + } + ] + logs: [ + for group in (diagnosticSetting.?logCategoriesAndGroups ?? [{ categoryGroup: 'allLogs' }]): { + categoryGroup: group.?categoryGroup + category: group.?category + enabled: group.?enabled ?? true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: queueServices } - scope: queueServices -}] - -module queueServices_queues 'queue/main.bicep' = [for (queue, index) in (queues ?? []): { - name: '${deployment().name}-Queue-${index}' - params: { - storageAccountName: storageAccount.name - name: queue.name - metadata: queue.?metadata - roleAssignments: queue.?roleAssignments +] + +module queueServices_queues 'queue/main.bicep' = [ + for (queue, index) in (queues ?? []): { + name: '${deployment().name}-Queue-${index}' + params: { + storageAccountName: storageAccount.name + name: queue.name + metadata: queue.?metadata + roleAssignments: queue.?roleAssignments + } } -}] +] @description('The name of the deployed file share service.') output name string = queueServices.name diff --git a/avm/res/storage/storage-account/queue-service/main.json b/avm/res/storage/storage-account/queue-service/main.json index b1ddfd7258..a605a234ac 100644 --- a/avm/res/storage/storage-account/queue-service/main.json +++ b/avm/res/storage/storage-account/queue-service/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "4494965320132257914" + "version": "0.27.1.19265", + "templateHash": "8505361210488414487" }, "name": "Storage Account Queue Services", "description": "This module deploys a Storage Account Queue Service.", @@ -163,12 +163,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "queueServices": { "type": "Microsoft.Storage/storageAccounts/queueServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", "properties": {}, "dependsOn": [ @@ -250,8 +250,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "2870814699283520878" + "version": "0.27.1.19265", + "templateHash": "11111133794570176541" }, "name": "Storage Account Queues", "description": "This module deploys a Storage Account Queue.", @@ -374,7 +374,7 @@ "storageAccount::queueServices": { "existing": true, "type": "Microsoft.Storage/storageAccounts/queueServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", "dependsOn": [ "storageAccount" @@ -383,12 +383,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "queue": { "type": "Microsoft.Storage/storageAccounts/queueServices/queues", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", "properties": { "metadata": "[parameters('metadata')]" diff --git a/avm/res/storage/storage-account/queue-service/queue/README.md b/avm/res/storage/storage-account/queue-service/queue/README.md index 7e393ed284..760a6f70a4 100644 --- a/avm/res/storage/storage-account/queue-service/queue/README.md +++ b/avm/res/storage/storage-account/queue-service/queue/README.md @@ -15,7 +15,7 @@ This module deploys a Storage Account Queue. | Resource Type | API Version | | :-- | :-- | | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | -| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/queueServices/queues) | +| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices/queues) | ## Parameters diff --git a/avm/res/storage/storage-account/queue-service/queue/main.bicep b/avm/res/storage/storage-account/queue-service/queue/main.bicep index 73a0decb98..9b1aba392c 100644 --- a/avm/res/storage/storage-account/queue-service/queue/main.bicep +++ b/avm/res/storage/storage-account/queue-service/queue/main.bicep @@ -19,27 +19,57 @@ var builtInRoleNames = { Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') - 'Reader and Data Access': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349') - 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') - 'Storage Account Backup Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1') - 'Storage Account Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab') - 'Storage Account Key Operator Service Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12') - 'Storage Queue Data Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88') - 'Storage Queue Data Message Processor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8a0f0c08-91a1-4084-bc3d-661d67233fed') - 'Storage Queue Data Message Sender': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a') - 'Storage Queue Data Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '19e7f393-937e-4f77-808e-94535e297925') - 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') + 'Reader and Data Access': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'c12c1c16-33a1-487b-954d-41c89c60f349' + ) + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' + ) + 'Storage Account Backup Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1' + ) + 'Storage Account Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '17d1049b-9a84-46fb-8f53-869881c3d3ab' + ) + 'Storage Account Key Operator Service Role': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '81a9662b-bebf-436f-a333-f67b29880f12' + ) + 'Storage Queue Data Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '974c5e8b-45b9-4653-ba55-5f855dd0fb88' + ) + 'Storage Queue Data Message Processor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '8a0f0c08-91a1-4084-bc3d-661d67233fed' + ) + 'Storage Queue Data Message Sender': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a' + ) + 'Storage Queue Data Reader': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '19e7f393-937e-4f77-808e-94535e297925' + ) + 'User Access Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' + ) } -resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-04-01' existing = { name: storageAccountName - resource queueServices 'queueServices@2021-09-01' existing = { + resource queueServices 'queueServices@2023-04-01' existing = { name: 'default' } } -resource queue 'Microsoft.Storage/storageAccounts/queueServices/queues@2021-09-01' = { +resource queue 'Microsoft.Storage/storageAccounts/queueServices/queues@2023-04-01' = { name: name parent: storageAccount::queueServices properties: { @@ -47,19 +77,25 @@ resource queue 'Microsoft.Storage/storageAccounts/queueServices/queues@2021-09-0 } } -resource queue_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { - name: guid(queue.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) - properties: { - roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : contains(roleAssignment.roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/') ? roleAssignment.roleDefinitionIdOrName : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName) - principalId: roleAssignment.principalId - description: roleAssignment.?description - principalType: roleAssignment.?principalType - condition: roleAssignment.?condition - conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set - delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId +resource queue_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(queue.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) + ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] + : contains(roleAssignment.roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/') + ? roleAssignment.roleDefinitionIdOrName + : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName) + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: queue } - scope: queue -}] +] @description('The name of the deployed queue.') output name string = queue.name diff --git a/avm/res/storage/storage-account/queue-service/queue/main.json b/avm/res/storage/storage-account/queue-service/queue/main.json index 7463266ec4..751b9fdd67 100644 --- a/avm/res/storage/storage-account/queue-service/queue/main.json +++ b/avm/res/storage/storage-account/queue-service/queue/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "2870814699283520878" + "version": "0.27.1.19265", + "templateHash": "11111133794570176541" }, "name": "Storage Account Queues", "description": "This module deploys a Storage Account Queue.", @@ -129,7 +129,7 @@ "storageAccount::queueServices": { "existing": true, "type": "Microsoft.Storage/storageAccounts/queueServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", "dependsOn": [ "storageAccount" @@ -138,12 +138,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "queue": { "type": "Microsoft.Storage/storageAccounts/queueServices/queues", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", "properties": { "metadata": "[parameters('metadata')]" diff --git a/avm/res/storage/storage-account/table-service/README.md b/avm/res/storage/storage-account/table-service/README.md index 2d118753cb..b7c9ae7c1c 100644 --- a/avm/res/storage/storage-account/table-service/README.md +++ b/avm/res/storage/storage-account/table-service/README.md @@ -16,8 +16,8 @@ This module deploys a Storage Account Table Service. | :-- | :-- | | `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.Storage/storageAccounts/tableServices` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/tableServices) | -| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/tableServices/tables) | +| `Microsoft.Storage/storageAccounts/tableServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices) | +| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices/tables) | ## Parameters diff --git a/avm/res/storage/storage-account/table-service/main.bicep b/avm/res/storage/storage-account/table-service/main.bicep index 0ff60b095b..061dbb9ca0 100644 --- a/avm/res/storage/storage-account/table-service/main.bicep +++ b/avm/res/storage/storage-account/table-service/main.bicep @@ -15,47 +15,55 @@ param diagnosticSettings diagnosticSettingType // The name of the table service var name = 'default' -resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-04-01' existing = { name: storageAccountName } -resource tableServices 'Microsoft.Storage/storageAccounts/tableServices@2021-09-01' = { +resource tableServices 'Microsoft.Storage/storageAccounts/tableServices@2023-04-01' = { name: name parent: storageAccount properties: {} } -resource tableServices_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): { - name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' - properties: { - storageAccountId: diagnosticSetting.?storageAccountResourceId - workspaceId: diagnosticSetting.?workspaceResourceId - eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId - eventHubName: diagnosticSetting.?eventHubName - metrics: [for group in (diagnosticSetting.?metricCategories ?? [ { category: 'AllMetrics' } ]): { - category: group.category - enabled: group.?enabled ?? true - timeGrain: null - }] - logs: [for group in (diagnosticSetting.?logCategoriesAndGroups ?? [ { categoryGroup: 'allLogs' } ]): { - categoryGroup: group.?categoryGroup - category: group.?category - enabled: group.?enabled ?? true - }] - marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId - logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType +resource tableServices_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [ + for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + metrics: [ + for group in (diagnosticSetting.?metricCategories ?? [{ category: 'AllMetrics' }]): { + category: group.category + enabled: group.?enabled ?? true + timeGrain: null + } + ] + logs: [ + for group in (diagnosticSetting.?logCategoriesAndGroups ?? [{ categoryGroup: 'allLogs' }]): { + categoryGroup: group.?categoryGroup + category: group.?category + enabled: group.?enabled ?? true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: tableServices } - scope: tableServices -}] - -module tableServices_tables 'table/main.bicep' = [for (table, index) in tables: { - name: '${deployment().name}-Table-${index}' - params: { - name: table.name - storageAccountName: storageAccount.name - roleAssignments: table.?roleAssignments +] + +module tableServices_tables 'table/main.bicep' = [ + for (table, index) in tables: { + name: '${deployment().name}-Table-${index}' + params: { + name: table.name + storageAccountName: storageAccount.name + roleAssignments: table.?roleAssignments + } } -}] +] @description('The name of the deployed table service.') output name string = tableServices.name diff --git a/avm/res/storage/storage-account/table-service/main.json b/avm/res/storage/storage-account/table-service/main.json index cd4f72dbef..98e3d5924b 100644 --- a/avm/res/storage/storage-account/table-service/main.json +++ b/avm/res/storage/storage-account/table-service/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "6806949296172999226" + "version": "0.27.1.19265", + "templateHash": "6164468877254681063" }, "name": "Storage Account Table Services", "description": "This module deploys a Storage Account Table Service.", @@ -163,12 +163,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "tableServices": { "type": "Microsoft.Storage/storageAccounts/tableServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", "properties": {}, "dependsOn": [ @@ -247,8 +247,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "5583637725561111612" + "version": "0.27.1.19265", + "templateHash": "10885620503239852206" }, "name": "Storage Account Table", "description": "This module deploys a Storage Account Table.", @@ -362,7 +362,7 @@ "storageAccount::tableServices": { "existing": true, "type": "Microsoft.Storage/storageAccounts/tableServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", "dependsOn": [ "storageAccount" @@ -371,12 +371,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "table": { "type": "Microsoft.Storage/storageAccounts/tableServices/tables", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", "dependsOn": [ "storageAccount::tableServices" diff --git a/avm/res/storage/storage-account/table-service/table/README.md b/avm/res/storage/storage-account/table-service/table/README.md index 16945292b2..d3b39dff00 100644 --- a/avm/res/storage/storage-account/table-service/table/README.md +++ b/avm/res/storage/storage-account/table-service/table/README.md @@ -15,7 +15,7 @@ This module deploys a Storage Account Table. | Resource Type | API Version | | :-- | :-- | | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | -| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/tableServices/tables) | +| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices/tables) | ## Parameters diff --git a/avm/res/storage/storage-account/table-service/table/main.bicep b/avm/res/storage/storage-account/table-service/table/main.bicep index a413238f5c..6553a048a3 100644 --- a/avm/res/storage/storage-account/table-service/table/main.bicep +++ b/avm/res/storage/storage-account/table-service/table/main.bicep @@ -16,42 +16,72 @@ var builtInRoleNames = { Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') - 'Reader and Data Access': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349') - 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') - 'Storage Account Backup Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1') - 'Storage Account Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab') - 'Storage Account Key Operator Service Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12') - 'Storage Table Data Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3') - 'Storage Table Data Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76199698-9eea-4c19-bc75-cec21354c6b6') - 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') + 'Reader and Data Access': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'c12c1c16-33a1-487b-954d-41c89c60f349' + ) + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' + ) + 'Storage Account Backup Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1' + ) + 'Storage Account Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '17d1049b-9a84-46fb-8f53-869881c3d3ab' + ) + 'Storage Account Key Operator Service Role': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '81a9662b-bebf-436f-a333-f67b29880f12' + ) + 'Storage Table Data Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3' + ) + 'Storage Table Data Reader': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '76199698-9eea-4c19-bc75-cec21354c6b6' + ) + 'User Access Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' + ) } -resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-04-01' existing = { name: storageAccountName - resource tableServices 'tableServices@2021-09-01' existing = { + resource tableServices 'tableServices@2023-04-01' existing = { name: 'default' } } -resource table 'Microsoft.Storage/storageAccounts/tableServices/tables@2021-09-01' = { +resource table 'Microsoft.Storage/storageAccounts/tableServices/tables@2023-04-01' = { name: name parent: storageAccount::tableServices } -resource table_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { - name: guid(table.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) - properties: { - roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : contains(roleAssignment.roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/') ? roleAssignment.roleDefinitionIdOrName : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName) - principalId: roleAssignment.principalId - description: roleAssignment.?description - principalType: roleAssignment.?principalType - condition: roleAssignment.?condition - conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set - delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId +resource table_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(table.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) + ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] + : contains(roleAssignment.roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/') + ? roleAssignment.roleDefinitionIdOrName + : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName) + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: table } - scope: table -}] +] @description('The name of the deployed file share service.') output name string = table.name diff --git a/avm/res/storage/storage-account/table-service/table/main.json b/avm/res/storage/storage-account/table-service/table/main.json index 0abcc4d463..894889268d 100644 --- a/avm/res/storage/storage-account/table-service/table/main.json +++ b/avm/res/storage/storage-account/table-service/table/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "5583637725561111612" + "version": "0.27.1.19265", + "templateHash": "10885620503239852206" }, "name": "Storage Account Table", "description": "This module deploys a Storage Account Table.", @@ -120,7 +120,7 @@ "storageAccount::tableServices": { "existing": true, "type": "Microsoft.Storage/storageAccounts/tableServices", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", "dependsOn": [ "storageAccount" @@ -129,12 +129,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[parameters('storageAccountName')]" }, "table": { "type": "Microsoft.Storage/storageAccounts/tableServices/tables", - "apiVersion": "2021-09-01", + "apiVersion": "2023-04-01", "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", "dependsOn": [ "storageAccount::tableServices" diff --git a/avm/res/storage/storage-account/tests/e2e/changefeed/main.test.bicep b/avm/res/storage/storage-account/tests/e2e/changefeed/main.test.bicep new file mode 100644 index 0000000000..d4ab99d73a --- /dev/null +++ b/avm/res/storage/storage-account/tests/e2e/changefeed/main.test.bicep @@ -0,0 +1,52 @@ +targetScope = 'subscription' + +metadata name = 'Using only changefeed configuration' +metadata description = 'This instance deploys the module with the minimum set of required parameters for the changefeed configuration.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-storage.storageaccounts-${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 = 'ssachf' + +@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 +} + +// ============== // +// 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' + allowBlobPublicAccess: false + location: resourceLocation + blobServices: { + changeFeedEnabled: true + } + } + } +] diff --git a/avm/res/storage/storage-account/tests/e2e/waf-aligned/main.test.bicep b/avm/res/storage/storage-account/tests/e2e/waf-aligned/main.test.bicep index 0fdbb91dff..e610d70df3 100644 --- a/avm/res/storage/storage-account/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/storage/storage-account/tests/e2e/waf-aligned/main.test.bicep @@ -60,158 +60,194 @@ module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/t // ============== // @batchSize(1) -module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { - scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' - params: { - location: resourceLocation - name: '${namePrefix}${serviceShort}001' - skuName: 'Standard_LRS' - allowBlobPublicAccess: false - requireInfrastructureEncryption: true - largeFileSharesState: 'Enabled' - enableHierarchicalNamespace: true - enableSftp: true - enableNfsV3: true - privateEndpoints: [ - { - service: 'blob' - subnetResourceId: nestedDependencies.outputs.subnetResourceId - privateDnsZoneResourceIds: [ - nestedDependencies.outputs.privateDNSZoneResourceId - ] - tags: { - 'hidden-title': 'This is visible in the resource name' - Environment: 'Non-Prod' - Role: 'DeploymentValidation' - } - } - ] - networkAcls: { - bypass: 'AzureServices' - defaultAction: 'Deny' - virtualNetworkRules: [ - { - action: 'Allow' - id: nestedDependencies.outputs.subnetResourceId - } - ] - ipRules: [ +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}${serviceShort}001' + skuName: 'Standard_ZRS' + allowBlobPublicAccess: false + requireInfrastructureEncryption: true + largeFileSharesState: 'Enabled' + enableHierarchicalNamespace: true + enableSftp: true + enableNfsV3: true + privateEndpoints: [ { - action: 'Allow' - value: '1.1.1.1' + service: 'blob' + subnetResourceId: nestedDependencies.outputs.subnetResourceId + privateDnsZoneResourceIds: [ + nestedDependencies.outputs.privateDNSZoneResourceId + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } } ] - } - localUsers: [ - { - storageAccountName: '${namePrefix}${serviceShort}001' - name: 'testuser' - hasSharedKey: false - hasSshKey: true - hasSshPassword: false - homeDirectory: 'avdscripts' - permissionScopes: [ + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + virtualNetworkRules: [ { - permissions: 'r' - service: 'blob' - resourceName: 'avdscripts' + action: 'Allow' + id: nestedDependencies.outputs.subnetResourceId + } + ] + ipRules: [ + { + action: 'Allow' + value: '1.1.1.1' } ] } - ] - blobServices: { - lastAccessTimeTrackingPolicyEnabled: true - diagnosticSettings: [ + localUsers: [ { - name: 'customSetting' - metricCategories: [ + storageAccountName: '${namePrefix}${serviceShort}001' + name: 'testuser' + hasSharedKey: false + hasSshKey: true + hasSshPassword: false + homeDirectory: 'avdscripts' + permissionScopes: [ { - category: 'AllMetrics' + permissions: 'r' + service: 'blob' + resourceName: 'avdscripts' } ] - eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName - eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId - storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId - workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId } ] - containers: [ - { - name: 'avdscripts' - enableNfsV3AllSquash: true - enableNfsV3RootSquash: true - publicAccess: 'None' - } - { - name: 'archivecontainer' - publicAccess: 'None' - metadata: { - testKey: 'testValue' + blobServices: { + lastAccessTimeTrackingPolicyEnabled: true + diagnosticSettings: [ + { + name: 'customSetting' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId } - enableWORM: true - WORMRetention: 666 - allowProtectedAppendWrites: false - } - ] - automaticSnapshotPolicyEnabled: true - containerDeleteRetentionPolicyEnabled: true - containerDeleteRetentionPolicyDays: 10 - deleteRetentionPolicyEnabled: true - deleteRetentionPolicyDays: 9 - } - fileServices: { - diagnosticSettings: [ - { - name: 'customSetting' - metricCategories: [ - { - category: 'AllMetrics' + ] + containers: [ + { + name: 'avdscripts' + enableNfsV3AllSquash: true + enableNfsV3RootSquash: true + publicAccess: 'None' + } + { + name: 'archivecontainer' + publicAccess: 'None' + metadata: { + testKey: 'testValue' } - ] - eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName - eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId - storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId - workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId - } - ] - shares: [ - { - name: 'avdprofiles' - accessTier: 'Hot' - shareQuota: 5120 - } - { - name: 'avdprofiles2' - shareQuota: 102400 - } - ] - } - tableServices: { - diagnosticSettings: [ - { - name: 'customSetting' - metricCategories: [ - { - category: 'AllMetrics' + enableWORM: true + WORMRetention: 666 + allowProtectedAppendWrites: false + } + ] + automaticSnapshotPolicyEnabled: true + containerDeleteRetentionPolicyEnabled: true + containerDeleteRetentionPolicyDays: 10 + deleteRetentionPolicyEnabled: true + deleteRetentionPolicyDays: 9 + } + fileServices: { + diagnosticSettings: [ + { + name: 'customSetting' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + shares: [ + { + name: 'avdprofiles' + accessTier: 'Hot' + shareQuota: 5120 + } + { + name: 'avdprofiles2' + shareQuota: 102400 + } + ] + } + tableServices: { + diagnosticSettings: [ + { + name: 'customSetting' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + tables: [ + { + name: 'table1' + } + { + name: 'table2' + } + ] + } + queueServices: { + diagnosticSettings: [ + { + name: 'customSetting' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + queues: [ + { + name: 'queue1' + metadata: { + key1: 'value1' + key2: 'value2' } - ] - eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName - eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId - storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId - workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId - } - ] - tables: [ - { - name: 'table1' - } - { - name: 'table2' - } - ] - } - queueServices: { + } + { + name: 'queue2' + metadata: {} + } + ] + } + sasExpirationPeriod: '180.00:00:00' + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] + } diagnosticSettings: [ { name: 'customSetting' @@ -226,83 +262,49 @@ module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId } ] - queues: [ - { - name: 'queue1' - metadata: { - key1: 'value1' - key2: 'value2' - } - } + managementPolicyRules: [ { - name: 'queue2' - metadata: {} - } - ] - } - sasExpirationPeriod: '180.00:00:00' - managedIdentities: { - systemAssigned: true - userAssignedResourceIds: [ - nestedDependencies.outputs.managedIdentityResourceId - ] - } - diagnosticSettings: [ - { - name: 'customSetting' - metricCategories: [ - { - category: 'AllMetrics' - } - ] - eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName - eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId - storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId - workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId - } - ] - managementPolicyRules: [ - { - enabled: true - name: 'FirstRule' - type: 'Lifecycle' - definition: { - actions: { - baseBlob: { - delete: { - daysAfterModificationGreaterThan: 30 - } - tierToCool: { - daysAfterLastAccessTimeGreaterThan: 5 + enabled: true + name: 'FirstRule' + type: 'Lifecycle' + definition: { + actions: { + baseBlob: { + delete: { + daysAfterModificationGreaterThan: 30 + } + tierToCool: { + daysAfterLastAccessTimeGreaterThan: 5 + } } } - } - filters: { - blobIndexMatch: [ - { - name: 'BlobIndex' - op: '==' - value: '1' - } - ] - blobTypes: [ - 'blockBlob' - ] - prefixMatch: [ - 'sample-container/log' - ] + filters: { + blobIndexMatch: [ + { + name: 'BlobIndex' + op: '==' + value: '1' + } + ] + blobTypes: [ + 'blockBlob' + ] + prefixMatch: [ + 'sample-container/log' + ] + } } } + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' } - ] - tags: { - 'hidden-title': 'This is visible in the resource name' - Environment: 'Non-Prod' - Role: 'DeploymentValidation' } + dependsOn: [ + nestedDependencies + diagnosticDependencies + ] } - dependsOn: [ - nestedDependencies - diagnosticDependencies - ] -}] +] diff --git a/avm/res/storage/storage-account/version.json b/avm/res/storage/storage-account/version.json index 0f81d22abc..20e1887127 100644 --- a/avm/res/storage/storage-account/version.json +++ b/avm/res/storage/storage-account/version.json @@ -1,7 +1,7 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.8", + "version": "0.9", "pathFilters": [ "./main.json" ] -} \ No newline at end of file +} From e00c43fa5bbb67ffb74853af2e96da4264c82d81 Mon Sep 17 00:00:00 2001 From: ChrisSidebotham-MSFT <48600046+ChrisSidebotham@users.noreply.github.com> Date: Wed, 22 May 2024 12:03:41 +0100 Subject: [PATCH 08/18] fix: Added missing built-in roles for key vault (#2005) ## Description Closes #1459 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.key-vault.vault](https://github.com/ChrisSidebotham/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml/badge.svg?branch=kv-role-updates)](https://github.com/ChrisSidebotham/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/key-vault/vault/key/main.bicep | 4 + avm/res/key-vault/vault/key/main.json | 3 +- avm/res/key-vault/vault/main.bicep | 4 + avm/res/key-vault/vault/main.json | 9 ++- avm/res/key-vault/vault/secret/main.bicep | 89 +++++++++++++++++------ avm/res/key-vault/vault/secret/main.json | 3 +- 6 files changed, 84 insertions(+), 28 deletions(-) diff --git a/avm/res/key-vault/vault/key/main.bicep b/avm/res/key-vault/vault/key/main.bicep index 07c6c75dd5..c6b5ee3819 100644 --- a/avm/res/key-vault/vault/key/main.bicep +++ b/avm/res/key-vault/vault/key/main.bicep @@ -72,6 +72,10 @@ var builtInRoleNames = { 'Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985' ) + 'Key Vault Certificate User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba' + ) 'Key Vault Contributor': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395' diff --git a/avm/res/key-vault/vault/key/main.json b/avm/res/key-vault/vault/key/main.json index 8c2db4264d..91962de233 100644 --- a/avm/res/key-vault/vault/key/main.json +++ b/avm/res/key-vault/vault/key/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "8655662111651470037" + "templateHash": "7648379973162364092" }, "name": "Key Vault Keys", "description": "This module deploys a Key Vault Key.", @@ -196,6 +196,7 @@ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Certificate User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba')]", "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", diff --git a/avm/res/key-vault/vault/main.bicep b/avm/res/key-vault/vault/main.bicep index a036c8220a..f7ed34f500 100644 --- a/avm/res/key-vault/vault/main.bicep +++ b/avm/res/key-vault/vault/main.bicep @@ -95,6 +95,10 @@ var builtInRoleNames = { 'Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985' ) + 'Key Vault Certificate User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba' + ) 'Key Vault Contributor': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395' diff --git a/avm/res/key-vault/vault/main.json b/avm/res/key-vault/vault/main.json index 1cf0c42833..0a867b281c 100644 --- a/avm/res/key-vault/vault/main.json +++ b/avm/res/key-vault/vault/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "1390366924133725054" + "templateHash": "17896878469622609898" }, "name": "Key Vaults", "description": "This module deploys a Key Vault.", @@ -1027,6 +1027,7 @@ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Certificate User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba')]", "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", @@ -1458,7 +1459,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "6420465919494344964" + "templateHash": "1160382186999187070" }, "name": "Key Vault Secrets", "description": "This module deploys a Key Vault Secret.", @@ -1598,6 +1599,7 @@ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Certificate User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba')]", "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", @@ -1746,7 +1748,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "8655662111651470037" + "templateHash": "7648379973162364092" }, "name": "Key Vault Keys", "description": "This module deploys a Key Vault Key.", @@ -1936,6 +1938,7 @@ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Certificate User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba')]", "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", diff --git a/avm/res/key-vault/vault/secret/main.bicep b/avm/res/key-vault/vault/secret/main.bicep index a2da23db1f..3dd83b47b6 100644 --- a/avm/res/key-vault/vault/secret/main.bicep +++ b/avm/res/key-vault/vault/secret/main.bicep @@ -33,19 +33,56 @@ param roleAssignments roleAssignmentType var builtInRoleNames = { Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') - 'Key Vault Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483') - 'Key Vault Certificates Officer': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985') - 'Key Vault Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395') - 'Key Vault Crypto Officer': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603') - 'Key Vault Crypto Service Encryption User': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') - 'Key Vault Crypto User': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424') - 'Key Vault Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2') - 'Key Vault Secrets Officer': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7') - 'Key Vault Secrets User': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6') + 'Key Vault Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '00482a5a-887f-4fb3-b363-3b7fe8e74483' + ) + 'Key Vault Certificates Officer': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'a4417e6f-fecd-4de8-b567-7b0420556985' + ) + 'Key Vault Certificate User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba' + ) + 'Key Vault Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f25e0fa2-a7c8-4377-a976-54943a77a395' + ) + 'Key Vault Crypto Officer': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '14b46e9e-c2b7-41b4-b07b-48a6ebf60603' + ) + 'Key Vault Crypto Service Encryption User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'e147488a-f6f5-4113-8e2d-b22465e65bf6' + ) + 'Key Vault Crypto User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '12338af0-0e69-4776-bea7-57ae8d297424' + ) + 'Key Vault Reader': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '21090545-7ca7-4776-b22c-e363652d74d2' + ) + 'Key Vault Secrets Officer': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7' + ) + 'Key Vault Secrets User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '4633458b-17de-408a-b874-0445c86b69e6' + ) Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') - 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') - 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' + ) + 'User Access Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' + ) } resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { @@ -67,19 +104,25 @@ resource secret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { } } -resource secret_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { - name: guid(secret.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) - properties: { - roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : contains(roleAssignment.roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/') ? roleAssignment.roleDefinitionIdOrName : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName) - principalId: roleAssignment.principalId - description: roleAssignment.?description - principalType: roleAssignment.?principalType - condition: roleAssignment.?condition - conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set - delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId +resource secret_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(secret.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) + ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] + : contains(roleAssignment.roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/') + ? roleAssignment.roleDefinitionIdOrName + : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName) + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: secret } - scope: secret -}] +] @description('The name of the secret.') output name string = secret.name diff --git a/avm/res/key-vault/vault/secret/main.json b/avm/res/key-vault/vault/secret/main.json index ace5934ef0..b311bb75a0 100644 --- a/avm/res/key-vault/vault/secret/main.json +++ b/avm/res/key-vault/vault/secret/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "6420465919494344964" + "templateHash": "1160382186999187070" }, "name": "Key Vault Secrets", "description": "This module deploys a Key Vault Secret.", @@ -146,6 +146,7 @@ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Certificate User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba')]", "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", From 088951e85b2107be9111b38b39f15382648bb8e2 Mon Sep 17 00:00:00 2001 From: ChrisSidebotham-MSFT <48600046+ChrisSidebotham@users.noreply.github.com> Date: Wed, 22 May 2024 15:14:31 +0100 Subject: [PATCH 09/18] fix: `ddosProtectionPlan` now optional in `ddosSettingsType` for PublicIPAddress (#2006) ## Description Closes #1940 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.network.public-ip-address](https://github.com/ChrisSidebotham/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-address.yml/badge.svg?branch=public-i-p-ddos)](https://github.com/ChrisSidebotham/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-address.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/network/public-ip-address/README.md | 35 ++++++++------ avm/res/network/public-ip-address/main.bicep | 50 ++++++++++---------- avm/res/network/public-ip-address/main.json | 7 +-- 3 files changed, 48 insertions(+), 44 deletions(-) diff --git a/avm/res/network/public-ip-address/README.md b/avm/res/network/public-ip-address/README.md index 7e00e44faa..ded4ce7179 100644 --- a/avm/res/network/public-ip-address/README.md +++ b/avm/res/network/public-ip-address/README.md @@ -459,14 +459,32 @@ The DDoS protection plan configuration associated with the public IP address. | Parameter | Type | Description | | :-- | :-- | :-- | -| [`ddosProtectionPlan`](#parameter-ddossettingsddosprotectionplan) | object | The DDoS protection plan associated with the public IP address. | | [`protectionMode`](#parameter-ddossettingsprotectionmode) | string | The DDoS protection policy customizations. | +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`ddosProtectionPlan`](#parameter-ddossettingsddosprotectionplan) | object | The DDoS protection plan associated with the public IP address. | + +### Parameter: `ddosSettings.protectionMode` + +The DDoS protection policy customizations. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'Enabled' + ] + ``` + ### Parameter: `ddosSettings.ddosProtectionPlan` The DDoS protection plan associated with the public IP address. -- Required: Yes +- Required: No - Type: object **Required parameters** @@ -482,19 +500,6 @@ The resource ID of the DDOS protection plan associated with the public IP addres - Required: Yes - Type: string -### Parameter: `ddosSettings.protectionMode` - -The DDoS protection policy customizations. - -- Required: Yes -- Type: string -- Allowed: - ```Bicep - [ - 'Enabled' - ] - ``` - ### Parameter: `diagnosticSettings` The diagnostic settings of the service. diff --git a/avm/res/network/public-ip-address/main.bicep b/avm/res/network/public-ip-address/main.bicep index 8b2bb3d8e7..b7970c5543 100644 --- a/avm/res/network/public-ip-address/main.bicep +++ b/avm/res/network/public-ip-address/main.bicep @@ -109,24 +109,23 @@ var builtInRoleNames = { ) } -resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = - if (enableTelemetry) { - name: '46d3xbcp.res.network-publicipaddress.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' - properties: { - mode: 'Incremental' - template: { - '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' - contentVersion: '1.0.0.0' - resources: [] - outputs: { - telemetry: { - type: 'String' - value: 'For more information, see https://aka.ms/avm/TelemetryInfo' - } +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { + name: '46d3xbcp.res.network-publicipaddress.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + outputs: { + telemetry: { + type: 'String' + value: 'For more information, see https://aka.ms/avm/TelemetryInfo' } } } } +} resource publicIpAddress 'Microsoft.Network/publicIPAddresses@2023-09-01' = { name: name @@ -152,17 +151,16 @@ resource publicIpAddress 'Microsoft.Network/publicIPAddresses@2023-09-01' = { } } -resource publicIpAddress_lock 'Microsoft.Authorization/locks@2020-05-01' = - if (!empty(lock ?? {}) && lock.?kind != 'None') { - name: lock.?name ?? 'lock-${name}' - properties: { - level: lock.?kind ?? '' - notes: lock.?kind == 'CanNotDelete' - ? 'Cannot delete resource or child resources.' - : 'Cannot delete or modify the resource or child resources.' - } - scope: publicIpAddress +resource publicIpAddress_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' + ? 'Cannot delete resource or child resources.' + : 'Cannot delete or modify the resource or child resources.' } + scope: publicIpAddress +} resource publicIpAddress_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ for (roleAssignment, index) in (roleAssignments ?? []): { @@ -278,11 +276,11 @@ type dnsSettingsType = { } type ddosSettingsType = { - @description('Required. The DDoS protection plan associated with the public IP address.') + @description('Optional. The DDoS protection plan associated with the public IP address.') ddosProtectionPlan: { @description('Required. The resource ID of the DDOS protection plan associated with the public IP address.') id: string - } + }? @description('Required. The DDoS protection policy customizations.') protectionMode: 'Enabled' } diff --git a/avm/res/network/public-ip-address/main.json b/avm/res/network/public-ip-address/main.json index 9bb75070c4..17558f4852 100644 --- a/avm/res/network/public-ip-address/main.json +++ b/avm/res/network/public-ip-address/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "1650798617817777444" + "version": "0.27.1.19265", + "templateHash": "7382635530474884069" }, "name": "Public IP Addresses", "description": "This module deploys a Public IP Address.", @@ -155,8 +155,9 @@ } } }, + "nullable": true, "metadata": { - "description": "Required. The DDoS protection plan associated with the public IP address." + "description": "Optional. The DDoS protection plan associated with the public IP address." } }, "protectionMode": { From da17f0dedc779337f4ed24374c9c2e5ca634b1df Mon Sep 17 00:00:00 2001 From: "Axel B. Andersen" Date: Wed, 22 May 2024 16:18:56 +0200 Subject: [PATCH 10/18] [Feature Request] Add a possibility to specify partition key version for Hash partition key type (#1921) ## Description SQL database container defaults to 2 if omitted. This fixes that issue Fixes #1852 (closed by accident) ## Pipeline Reference | Pipeline | | -------- | |[![avm.res.document-db.database-account](https://github.com/Agazoth/bicep-registry-modules/actions/workflows/avm.res.document-db.database-account.yml/badge.svg?branch=DocumentDBDefaultForHash)](https://github.com/Agazoth/bicep-registry-modules/actions/workflows/avm.res.document-db.database-account.yml)| ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Bryan <117635118+bryansan-msft@users.noreply.github.com> --- .../document-db/database-account/README.md | 31 ++++++++++++++++ .../gremlin-database/graph/main.json | 4 +-- .../gremlin-database/main.json | 8 ++--- .../document-db/database-account/main.bicep | 3 ++ .../document-db/database-account/main.json | 36 +++++++++++++++---- .../mongodb-database/collection/main.json | 4 +-- .../mongodb-database/main.json | 8 ++--- .../sql-database/container/README.md | 17 ++++++++- .../sql-database/container/main.bicep | 6 ++-- .../sql-database/container/main.json | 20 ++++++++--- .../database-account/sql-database/main.bicep | 1 + .../database-account/sql-database/main.json | 27 ++++++++++---- .../tests/e2e/sqldb/main.test.bicep | 8 +++++ 13 files changed, 140 insertions(+), 33 deletions(-) diff --git a/avm/res/document-db/database-account/README.md b/avm/res/document-db/database-account/README.md index 73c60b1d9b..e381d85ccd 100644 --- a/avm/res/document-db/database-account/README.md +++ b/avm/res/document-db/database-account/README.md @@ -1742,6 +1742,14 @@ module databaseAccount 'br/public:avm/res/document-db/database-account: 'myPartitionKey1' ] } + { + kind: 'Hash' + name: 'container-005' + paths: [ + 'myPartitionKey1' + ] + version: 2 + } ] name: 'all-partition-key-types' } @@ -2020,6 +2028,14 @@ module databaseAccount 'br/public:avm/res/document-db/database-account: "paths": [ "myPartitionKey1" ] + }, + { + "kind": "Hash", + "name": "container-005", + "paths": [ + "myPartitionKey1" + ], + "version": 2 } ], "name": "all-partition-key-types" @@ -3384,6 +3400,7 @@ Array of containers to deploy in the SQL database. | [`kind`](#parameter-sqldatabasescontainerskind) | string | Default to Hash. Indicates the kind of algorithm used for partitioning. | | [`throughput`](#parameter-sqldatabasescontainersthroughput) | int | Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. | | [`uniqueKeyPolicyKeys`](#parameter-sqldatabasescontainersuniquekeypolicykeys) | array | The unique key policy configuration containing a list of unique keys that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service. | +| [`version`](#parameter-sqldatabasescontainersversion) | int | Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition. | ### Parameter: `sqlDatabases.containers.name` @@ -3516,6 +3533,20 @@ List of paths must be unique for each document in the Azure Cosmos DB service. - Required: Yes - Type: array +### Parameter: `sqlDatabases.containers.version` + +Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition. + +- Required: No +- Type: int +- Allowed: + ```Bicep + [ + 1 + 2 + ] + ``` + ### Parameter: `sqlDatabases.throughput` Default to 400. Request units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. diff --git a/avm/res/document-db/database-account/gremlin-database/graph/main.json b/avm/res/document-db/database-account/gremlin-database/graph/main.json index 1779026f4f..2d4f0f12fa 100644 --- a/avm/res/document-db/database-account/gremlin-database/graph/main.json +++ b/avm/res/document-db/database-account/gremlin-database/graph/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "2823004405398378621" + "version": "0.27.1.19265", + "templateHash": "3987394297554402770" }, "name": "DocumentDB Database Accounts Gremlin Databases Graphs", "description": "This module deploys a DocumentDB Database Accounts Gremlin Database Graph.", diff --git a/avm/res/document-db/database-account/gremlin-database/main.json b/avm/res/document-db/database-account/gremlin-database/main.json index c3cd9ee8c7..57548e1867 100644 --- a/avm/res/document-db/database-account/gremlin-database/main.json +++ b/avm/res/document-db/database-account/gremlin-database/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "13414524346849431368" + "version": "0.27.1.19265", + "templateHash": "6889435067791947905" }, "name": "DocumentDB Database Account Gremlin Databases", "description": "This module deploys a Gremlin Database within a CosmosDB Account.", @@ -109,8 +109,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "2823004405398378621" + "version": "0.27.1.19265", + "templateHash": "3987394297554402770" }, "name": "DocumentDB Database Accounts Gremlin Databases Graphs", "description": "This module deploys a DocumentDB Database Accounts Gremlin Database Graph.", diff --git a/avm/res/document-db/database-account/main.bicep b/avm/res/document-db/database-account/main.bicep index 9e95fc47a2..3e8626e9c9 100644 --- a/avm/res/document-db/database-account/main.bicep +++ b/avm/res/document-db/database-account/main.bicep @@ -772,6 +772,9 @@ type sqlDatabaseType = { @description('Optional. Default to Hash. Indicates the kind of algorithm used for partitioning.') kind: ('Hash' | 'MultiHash')? + @description('Optional. Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition.') + version: (1 | 2)? + @description('Optional. Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used.') throughput: int? diff --git a/avm/res/document-db/database-account/main.json b/avm/res/document-db/database-account/main.json index f5121fe2cd..ec16d7c7a8 100644 --- a/avm/res/document-db/database-account/main.json +++ b/avm/res/document-db/database-account/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "8399904898851436683" + "templateHash": "5516885052191994610" }, "name": "DocumentDB Database Accounts", "description": "This module deploys a DocumentDB Database Account.", @@ -587,6 +587,17 @@ "description": "Optional. Default to Hash. Indicates the kind of algorithm used for partitioning." } }, + "version": { + "type": "int", + "allowedValues": [ + 1, + 2 + ], + "nullable": true, + "metadata": { + "description": "Optional. Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition." + } + }, "throughput": { "type": "int", "nullable": true, @@ -1250,7 +1261,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "15534269553484112693" + "templateHash": "8319829662998768775" }, "name": "DocumentDB Database Account SQL Databases", "description": "This module deploys a SQL Database in a CosmosDB Account.", @@ -1364,6 +1375,9 @@ "kind": { "value": "[tryGet(parameters('containers')[copyIndex()], 'kind')]" }, + "version": { + "value": "[tryGet(parameters('containers')[copyIndex()], 'version')]" + }, "paths": { "value": "[tryGet(parameters('containers')[copyIndex()], 'paths')]" }, @@ -1380,7 +1394,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "12755071159425783033" + "templateHash": "3824205620211058652" }, "name": "DocumentDB Database Account SQL Database Containers", "description": "This module deploys a SQL Database Container in a CosmosDB Account.", @@ -1480,12 +1494,22 @@ "defaultValue": "Hash", "allowedValues": [ "Hash", - "MultiHash", - "Range" + "MultiHash" ], "metadata": { "description": "Optional. Default to Hash. Indicates the kind of algorithm used for partitioning." } + }, + "version": { + "type": "int", + "defaultValue": 1, + "allowedValues": [ + 1, + 2 + ], + "metadata": { + "description": "Optional. Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition." + } } }, "variables": { @@ -1496,7 +1520,7 @@ "input": "[if(startsWith(parameters('paths')[copyIndex('partitionKeyPaths')], '/'), parameters('paths')[copyIndex('partitionKeyPaths')], format('/{0}', parameters('paths')[copyIndex('partitionKeyPaths')]))]" } ], - "containerResourceParams": "[union(createObject('conflictResolutionPolicy', parameters('conflictResolutionPolicy'), 'defaultTtl', parameters('defaultTtl'), 'id', parameters('name'), 'indexingPolicy', if(not(empty(parameters('indexingPolicy'))), parameters('indexingPolicy'), null()), 'partitionKey', createObject('paths', variables('partitionKeyPaths'), 'kind', parameters('kind'), 'version', if(equals(parameters('kind'), 'MultiHash'), 2, 1)), 'uniqueKeyPolicy', if(not(empty(parameters('uniqueKeyPolicyKeys'))), createObject('uniqueKeys', parameters('uniqueKeyPolicyKeys')), null())), if(not(equals(parameters('analyticalStorageTtl'), 0)), createObject('analyticalStorageTtl', parameters('analyticalStorageTtl')), createObject()))]" + "containerResourceParams": "[union(createObject('conflictResolutionPolicy', parameters('conflictResolutionPolicy'), 'defaultTtl', parameters('defaultTtl'), 'id', parameters('name'), 'indexingPolicy', if(not(empty(parameters('indexingPolicy'))), parameters('indexingPolicy'), null()), 'partitionKey', createObject('paths', variables('partitionKeyPaths'), 'kind', parameters('kind'), 'version', if(equals(parameters('kind'), 'MultiHash'), 2, parameters('version'))), 'uniqueKeyPolicy', if(not(empty(parameters('uniqueKeyPolicyKeys'))), createObject('uniqueKeys', parameters('uniqueKeyPolicyKeys')), null())), if(not(equals(parameters('analyticalStorageTtl'), 0)), createObject('analyticalStorageTtl', parameters('analyticalStorageTtl')), createObject()))]" }, "resources": { "databaseAccount::sqlDatabase": { diff --git a/avm/res/document-db/database-account/mongodb-database/collection/main.json b/avm/res/document-db/database-account/mongodb-database/collection/main.json index ae74132b53..eeb2943ec8 100644 --- a/avm/res/document-db/database-account/mongodb-database/collection/main.json +++ b/avm/res/document-db/database-account/mongodb-database/collection/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "1989910090472113779" + "version": "0.27.1.19265", + "templateHash": "6235322895830297683" }, "name": "DocumentDB Database Account MongoDB Database Collections", "description": "This module deploys a MongoDB Database Collection.", diff --git a/avm/res/document-db/database-account/mongodb-database/main.json b/avm/res/document-db/database-account/mongodb-database/main.json index 781bd56a97..a862d99d20 100644 --- a/avm/res/document-db/database-account/mongodb-database/main.json +++ b/avm/res/document-db/database-account/mongodb-database/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "7706794569430424680" + "version": "0.27.1.19265", + "templateHash": "1527748615955553712" }, "name": "DocumentDB Database Account MongoDB Databases", "description": "This module deploys a MongoDB Database within a CosmosDB Account.", @@ -108,8 +108,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "1989910090472113779" + "version": "0.27.1.19265", + "templateHash": "6235322895830297683" }, "name": "DocumentDB Database Account MongoDB Database Collections", "description": "This module deploys a MongoDB Database Collection.", diff --git a/avm/res/document-db/database-account/sql-database/container/README.md b/avm/res/document-db/database-account/sql-database/container/README.md index c8227d7ff2..122e23a6fd 100644 --- a/avm/res/document-db/database-account/sql-database/container/README.md +++ b/avm/res/document-db/database-account/sql-database/container/README.md @@ -46,6 +46,7 @@ This module deploys a SQL Database Container in a CosmosDB Account. | [`tags`](#parameter-tags) | object | Tags of the SQL Database resource. | | [`throughput`](#parameter-throughput) | int | Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. | | [`uniqueKeyPolicyKeys`](#parameter-uniquekeypolicykeys) | array | The unique key policy configuration containing a list of unique keys that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service. | +| [`version`](#parameter-version) | int | Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition. | ### Parameter: `name` @@ -126,7 +127,6 @@ Default to Hash. Indicates the kind of algorithm used for partitioning. [ 'Hash' 'MultiHash' - 'Range' ] ``` @@ -153,6 +153,21 @@ The unique key policy configuration containing a list of unique keys that enforc - Type: array - Default: `[]` +### Parameter: `version` + +Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition. + +- Required: No +- Type: int +- Default: `1` +- Allowed: + ```Bicep + [ + 1 + 2 + ] + ``` + ## Outputs diff --git a/avm/res/document-db/database-account/sql-database/container/main.bicep b/avm/res/document-db/database-account/sql-database/container/main.bicep index 97f4a59e5b..902ee2ec8f 100644 --- a/avm/res/document-db/database-account/sql-database/container/main.bicep +++ b/avm/res/document-db/database-account/sql-database/container/main.bicep @@ -47,9 +47,11 @@ param uniqueKeyPolicyKeys array = [] @allowed([ 'Hash' 'MultiHash' - 'Range' ]) param kind string = 'Hash' +@description('Optional. Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition.') +@allowed([1, 2]) +param version int = 1 var partitionKeyPaths = [for path in paths: startsWith(path, '/') ? path : '/${path}'] @@ -62,7 +64,7 @@ var containerResourceParams = union( partitionKey: { paths: partitionKeyPaths kind: kind - version: kind == 'MultiHash' ? 2 : 1 + version: kind == 'MultiHash' ? 2 : version } uniqueKeyPolicy: !empty(uniqueKeyPolicyKeys) ? { diff --git a/avm/res/document-db/database-account/sql-database/container/main.json b/avm/res/document-db/database-account/sql-database/container/main.json index 815512145c..f882248bfc 100644 --- a/avm/res/document-db/database-account/sql-database/container/main.json +++ b/avm/res/document-db/database-account/sql-database/container/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "11250469246171251281" + "version": "0.27.1.19265", + "templateHash": "3824205620211058652" }, "name": "DocumentDB Database Account SQL Database Containers", "description": "This module deploys a SQL Database Container in a CosmosDB Account.", @@ -106,12 +106,22 @@ "defaultValue": "Hash", "allowedValues": [ "Hash", - "MultiHash", - "Range" + "MultiHash" ], "metadata": { "description": "Optional. Default to Hash. Indicates the kind of algorithm used for partitioning." } + }, + "version": { + "type": "int", + "defaultValue": 1, + "allowedValues": [ + 1, + 2 + ], + "metadata": { + "description": "Optional. Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition." + } } }, "variables": { @@ -122,7 +132,7 @@ "input": "[if(startsWith(parameters('paths')[copyIndex('partitionKeyPaths')], '/'), parameters('paths')[copyIndex('partitionKeyPaths')], format('/{0}', parameters('paths')[copyIndex('partitionKeyPaths')]))]" } ], - "containerResourceParams": "[union(createObject('conflictResolutionPolicy', parameters('conflictResolutionPolicy'), 'defaultTtl', parameters('defaultTtl'), 'id', parameters('name'), 'indexingPolicy', if(not(empty(parameters('indexingPolicy'))), parameters('indexingPolicy'), null()), 'partitionKey', createObject('paths', variables('partitionKeyPaths'), 'kind', parameters('kind'), 'version', if(equals(parameters('kind'), 'MultiHash'), 2, 1)), 'uniqueKeyPolicy', if(not(empty(parameters('uniqueKeyPolicyKeys'))), createObject('uniqueKeys', parameters('uniqueKeyPolicyKeys')), null())), if(not(equals(parameters('analyticalStorageTtl'), 0)), createObject('analyticalStorageTtl', parameters('analyticalStorageTtl')), createObject()))]" + "containerResourceParams": "[union(createObject('conflictResolutionPolicy', parameters('conflictResolutionPolicy'), 'defaultTtl', parameters('defaultTtl'), 'id', parameters('name'), 'indexingPolicy', if(not(empty(parameters('indexingPolicy'))), parameters('indexingPolicy'), null()), 'partitionKey', createObject('paths', variables('partitionKeyPaths'), 'kind', parameters('kind'), 'version', if(equals(parameters('kind'), 'MultiHash'), 2, parameters('version'))), 'uniqueKeyPolicy', if(not(empty(parameters('uniqueKeyPolicyKeys'))), createObject('uniqueKeys', parameters('uniqueKeyPolicyKeys')), null())), if(not(equals(parameters('analyticalStorageTtl'), 0)), createObject('analyticalStorageTtl', parameters('analyticalStorageTtl')), createObject()))]" }, "resources": { "databaseAccount::sqlDatabase": { diff --git a/avm/res/document-db/database-account/sql-database/main.bicep b/avm/res/document-db/database-account/sql-database/main.bicep index a62ea3f09f..d79d8f1802 100644 --- a/avm/res/document-db/database-account/sql-database/main.bicep +++ b/avm/res/document-db/database-account/sql-database/main.bicep @@ -58,6 +58,7 @@ module container 'container/main.bicep' = [ defaultTtl: container.?defaultTtl indexingPolicy: container.?indexingPolicy kind: container.?kind + version: container.?version paths: container.?paths throughput: (throughput != null || autoscaleSettingsMaxThroughput != null) && container.?throughput == null ? -1 diff --git a/avm/res/document-db/database-account/sql-database/main.json b/avm/res/document-db/database-account/sql-database/main.json index 0419417fa5..3bce557b29 100644 --- a/avm/res/document-db/database-account/sql-database/main.json +++ b/avm/res/document-db/database-account/sql-database/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "3826907101153138616" + "version": "0.27.1.19265", + "templateHash": "8319829662998768775" }, "name": "DocumentDB Database Account SQL Databases", "description": "This module deploys a SQL Database in a CosmosDB Account.", @@ -120,6 +120,9 @@ "kind": { "value": "[tryGet(parameters('containers')[copyIndex()], 'kind')]" }, + "version": { + "value": "[tryGet(parameters('containers')[copyIndex()], 'version')]" + }, "paths": { "value": "[tryGet(parameters('containers')[copyIndex()], 'paths')]" }, @@ -135,8 +138,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "11250469246171251281" + "version": "0.27.1.19265", + "templateHash": "3824205620211058652" }, "name": "DocumentDB Database Account SQL Database Containers", "description": "This module deploys a SQL Database Container in a CosmosDB Account.", @@ -236,12 +239,22 @@ "defaultValue": "Hash", "allowedValues": [ "Hash", - "MultiHash", - "Range" + "MultiHash" ], "metadata": { "description": "Optional. Default to Hash. Indicates the kind of algorithm used for partitioning." } + }, + "version": { + "type": "int", + "defaultValue": 1, + "allowedValues": [ + 1, + 2 + ], + "metadata": { + "description": "Optional. Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition." + } } }, "variables": { @@ -252,7 +265,7 @@ "input": "[if(startsWith(parameters('paths')[copyIndex('partitionKeyPaths')], '/'), parameters('paths')[copyIndex('partitionKeyPaths')], format('/{0}', parameters('paths')[copyIndex('partitionKeyPaths')]))]" } ], - "containerResourceParams": "[union(createObject('conflictResolutionPolicy', parameters('conflictResolutionPolicy'), 'defaultTtl', parameters('defaultTtl'), 'id', parameters('name'), 'indexingPolicy', if(not(empty(parameters('indexingPolicy'))), parameters('indexingPolicy'), null()), 'partitionKey', createObject('paths', variables('partitionKeyPaths'), 'kind', parameters('kind'), 'version', if(equals(parameters('kind'), 'MultiHash'), 2, 1)), 'uniqueKeyPolicy', if(not(empty(parameters('uniqueKeyPolicyKeys'))), createObject('uniqueKeys', parameters('uniqueKeyPolicyKeys')), null())), if(not(equals(parameters('analyticalStorageTtl'), 0)), createObject('analyticalStorageTtl', parameters('analyticalStorageTtl')), createObject()))]" + "containerResourceParams": "[union(createObject('conflictResolutionPolicy', parameters('conflictResolutionPolicy'), 'defaultTtl', parameters('defaultTtl'), 'id', parameters('name'), 'indexingPolicy', if(not(empty(parameters('indexingPolicy'))), parameters('indexingPolicy'), null()), 'partitionKey', createObject('paths', variables('partitionKeyPaths'), 'kind', parameters('kind'), 'version', if(equals(parameters('kind'), 'MultiHash'), 2, parameters('version'))), 'uniqueKeyPolicy', if(not(empty(parameters('uniqueKeyPolicyKeys'))), createObject('uniqueKeys', parameters('uniqueKeyPolicyKeys')), null())), if(not(equals(parameters('analyticalStorageTtl'), 0)), createObject('analyticalStorageTtl', parameters('analyticalStorageTtl')), createObject()))]" }, "resources": { "databaseAccount::sqlDatabase": { diff --git a/avm/res/document-db/database-account/tests/e2e/sqldb/main.test.bicep b/avm/res/document-db/database-account/tests/e2e/sqldb/main.test.bicep index 6662bd82a2..4d9362e8aa 100644 --- a/avm/res/document-db/database-account/tests/e2e/sqldb/main.test.bicep +++ b/avm/res/document-db/database-account/tests/e2e/sqldb/main.test.bicep @@ -278,6 +278,14 @@ module testDeployment '../../../main.bicep' = { 'myPartitionKey1' ] } + { + name: 'container-005' + kind: 'Hash' + version: 2 + paths: [ + 'myPartitionKey1' + ] + } ] name: 'all-partition-key-types' } From 5bd6a8734d6b5e0149f5e64bf71ccfa17743b00d Mon Sep 17 00:00:00 2001 From: MichielVanHerreweghe <169037533+MichielVanHerreweghe@users.noreply.github.com> Date: Wed, 22 May 2024 19:25:37 +0200 Subject: [PATCH 11/18] feat: Implemented Container user-defined type - `avm/res/app/container-app` (#1944) ## Description Implemented the Container object and subsequent sub objects for Intellisense support. ## Pipeline Reference | Pipeline | | -------- | [![avm.res.app.container-app](https://github.com/oZakari/bicep-registry-modules/actions/workflows/avm.res.app.container-app.yml/badge.svg?branch=users%2Fmichielvanherreweghe%2Ffully-implement-container-type)](https://github.com/oZakari/bicep-registry-modules/actions/workflows/avm.res.app.container-app.yml) ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [x] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Zach Trocinski <30884663+oZakari@users.noreply.github.com> Co-authored-by: Alexander Sehr Co-authored-by: Zach Trocinski --- avm/res/app/container-app/README.md | 331 +++++++++++++++++++++++++ avm/res/app/container-app/main.bicep | 124 ++++++++- avm/res/app/container-app/main.json | 293 +++++++++++++++++++++- avm/res/app/container-app/version.json | 12 +- 4 files changed, 752 insertions(+), 8 deletions(-) diff --git a/avm/res/app/container-app/README.md b/avm/res/app/container-app/README.md index 794ec770c0..e7035c0b37 100644 --- a/avm/res/app/container-app/README.md +++ b/avm/res/app/container-app/README.md @@ -500,6 +500,337 @@ List of container definitions for the Container App. - Required: Yes - Type: array +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`image`](#parameter-containersimage) | string | Container image tag. | +| [`resources`](#parameter-containersresources) | object | Container resource requirements. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`args`](#parameter-containersargs) | array | Container start command arguments. | +| [`command`](#parameter-containerscommand) | array | Container start command. | +| [`env`](#parameter-containersenv) | array | Container environment variables. | +| [`name`](#parameter-containersname) | string | Custom container name. | +| [`probes`](#parameter-containersprobes) | array | List of probes for the container. | +| [`volumeMounts`](#parameter-containersvolumemounts) | array | Container volume mounts. | + +### Parameter: `containers.image` + +Container image tag. + +- Required: Yes +- Type: string + +### Parameter: `containers.resources` + +Container resource requirements. + +- Required: Yes +- Type: object + +### Parameter: `containers.args` + +Container start command arguments. + +- Required: No +- Type: array + +### Parameter: `containers.command` + +Container start command. + +- Required: No +- Type: array + +### Parameter: `containers.env` + +Container environment variables. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-containersenvname) | string | Environment variable name. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`secretRef`](#parameter-containersenvsecretref) | string | Name of the Container App secret from which to pull the environment variable value. | +| [`value`](#parameter-containersenvvalue) | string | Non-secret environment variable value. | + +### Parameter: `containers.env.name` + +Environment variable name. + +- Required: Yes +- Type: string + +### Parameter: `containers.env.secretRef` + +Name of the Container App secret from which to pull the environment variable value. + +- Required: No +- Type: string + +### Parameter: `containers.env.value` + +Non-secret environment variable value. + +- Required: No +- Type: string + +### Parameter: `containers.name` + +Custom container name. + +- Required: No +- Type: string + +### Parameter: `containers.probes` + +List of probes for the container. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`failureThreshold`](#parameter-containersprobesfailurethreshold) | int | Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. | +| [`httpGet`](#parameter-containersprobeshttpget) | object | HTTPGet specifies the http request to perform. | +| [`initialDelaySeconds`](#parameter-containersprobesinitialdelayseconds) | int | Number of seconds after the container has started before liveness probes are initiated. | +| [`periodSeconds`](#parameter-containersprobesperiodseconds) | int | How often (in seconds) to perform the probe. Default to 10 seconds. | +| [`successThreshold`](#parameter-containersprobessuccessthreshold) | int | Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. | +| [`tcpSocket`](#parameter-containersprobestcpsocket) | object | TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported. | +| [`terminationGracePeriodSeconds`](#parameter-containersprobesterminationgraceperiodseconds) | int | Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is an alpha field and requires enabling ProbeTerminationGracePeriod feature gate. Maximum value is 3600 seconds (1 hour). | +| [`timeoutSeconds`](#parameter-containersprobestimeoutseconds) | int | Number of seconds after which the probe times out. Defaults to 1 second. | +| [`type`](#parameter-containersprobestype) | string | The type of probe. | + +### Parameter: `containers.probes.failureThreshold` + +Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. + +- Required: No +- Type: int + +### Parameter: `containers.probes.httpGet` + +HTTPGet specifies the http request to perform. + +- Required: No +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`path`](#parameter-containersprobeshttpgetpath) | string | Path to access on the HTTP server. | +| [`port`](#parameter-containersprobeshttpgetport) | int | Name or number of the port to access on the container. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`host`](#parameter-containersprobeshttpgethost) | string | Host name to connect to. Defaults to the pod IP. | +| [`httpHeaders`](#parameter-containersprobeshttpgethttpheaders) | array | HTTP headers to set in the request. | +| [`scheme`](#parameter-containersprobeshttpgetscheme) | string | Scheme to use for connecting to the host. Defaults to HTTP. | + +### Parameter: `containers.probes.httpGet.path` + +Path to access on the HTTP server. + +- Required: Yes +- Type: string + +### Parameter: `containers.probes.httpGet.port` + +Name or number of the port to access on the container. + +- Required: Yes +- Type: int + +### Parameter: `containers.probes.httpGet.host` + +Host name to connect to. Defaults to the pod IP. + +- Required: No +- Type: string + +### Parameter: `containers.probes.httpGet.httpHeaders` + +HTTP headers to set in the request. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-containersprobeshttpgethttpheadersname) | string | Name of the header. | +| [`value`](#parameter-containersprobeshttpgethttpheadersvalue) | string | Value of the header. | + +### Parameter: `containers.probes.httpGet.httpHeaders.name` + +Name of the header. + +- Required: Yes +- Type: string + +### Parameter: `containers.probes.httpGet.httpHeaders.value` + +Value of the header. + +- Required: Yes +- Type: string + +### Parameter: `containers.probes.httpGet.scheme` + +Scheme to use for connecting to the host. Defaults to HTTP. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'HTTP' + 'HTTPS' + ] + ``` + +### Parameter: `containers.probes.initialDelaySeconds` + +Number of seconds after the container has started before liveness probes are initiated. + +- Required: No +- Type: int + +### Parameter: `containers.probes.periodSeconds` + +How often (in seconds) to perform the probe. Default to 10 seconds. + +- Required: No +- Type: int + +### Parameter: `containers.probes.successThreshold` + +Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. + +- Required: No +- Type: int + +### Parameter: `containers.probes.tcpSocket` + +TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported. + +- Required: No +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`port`](#parameter-containersprobestcpsocketport) | int | Number of the port to access on the container. Name must be an IANA_SVC_NAME. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`host`](#parameter-containersprobestcpsockethost) | string | Host name to connect to, defaults to the pod IP. | + +### Parameter: `containers.probes.tcpSocket.port` + +Number of the port to access on the container. Name must be an IANA_SVC_NAME. + +- Required: Yes +- Type: int + +### Parameter: `containers.probes.tcpSocket.host` + +Host name to connect to, defaults to the pod IP. + +- Required: No +- Type: string + +### Parameter: `containers.probes.terminationGracePeriodSeconds` + +Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is an alpha field and requires enabling ProbeTerminationGracePeriod feature gate. Maximum value is 3600 seconds (1 hour). + +- Required: No +- Type: int + +### Parameter: `containers.probes.timeoutSeconds` + +Number of seconds after which the probe times out. Defaults to 1 second. + +- Required: No +- Type: int + +### Parameter: `containers.probes.type` + +The type of probe. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Liveness' + 'Readiness' + 'Startup' + ] + ``` + +### Parameter: `containers.volumeMounts` + +Container volume mounts. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`mountPath`](#parameter-containersvolumemountsmountpath) | string | Path within the container at which the volume should be mounted.Must not contain ':'. | +| [`volumeName`](#parameter-containersvolumemountsvolumename) | string | This must match the Name of a Volume. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`subPath`](#parameter-containersvolumemountssubpath) | string | Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root). | + +### Parameter: `containers.volumeMounts.mountPath` + +Path within the container at which the volume should be mounted.Must not contain ':'. + +- Required: Yes +- Type: string + +### Parameter: `containers.volumeMounts.volumeName` + +This must match the Name of a Volume. + +- Required: Yes +- Type: string + +### Parameter: `containers.volumeMounts.subPath` + +Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root). + +- Required: No +- Type: string + ### Parameter: `environmentId` Resource ID of environment. diff --git a/avm/res/app/container-app/main.bicep b/avm/res/app/container-app/main.bicep index 8ff404eaf9..58910114f0 100644 --- a/avm/res/app/container-app/main.bicep +++ b/avm/res/app/container-app/main.bicep @@ -98,7 +98,7 @@ param dapr object = {} param maxInactiveRevisions int = 0 @description('Required. List of container definitions for the Container App.') -param containers array +param containers container[] @description('Optional. List of specialized containers that run before app containers.') param initContainersTemplate array = [] @@ -309,3 +309,125 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type container = { + @description('Optional. Container start command arguments.') + args: string[]? + + @description('Optional. Container start command.') + command: string[]? + + @description('Optional. Container environment variables.') + env: environmentVar[]? + + @description('Required. Container image tag.') + image: string + + @description('Optional. Custom container name.') + name: string? + + @description('Optional. List of probes for the container.') + probes: containerAppProbe[]? + + @description('Required. Container resource requirements.') + resources: object + + @description('Optional. Container volume mounts.') + volumeMounts: volumeMount[]? +} + +type environmentVar = { + @description('Required. Environment variable name.') + name: string + + @description('Optional. Name of the Container App secret from which to pull the environment variable value.') + secretRef: string? + + @description('Optional. Non-secret environment variable value.') + value: string? +} + +type containerAppProbe = { + @description('Optional. Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3.') + @minValue(1) + @maxValue(10) + failureThreshold: int? + + @description('Optional. HTTPGet specifies the http request to perform.') + httpGet: containerAppProbeHttpGet? + + @description('Optional. Number of seconds after the container has started before liveness probes are initiated.') + @minValue(1) + @maxValue(60) + initialDelaySeconds: int? + + @description('Optional. How often (in seconds) to perform the probe. Default to 10 seconds.') + @minValue(1) + @maxValue(240) + periodSeconds: int? + + @description('Optional. Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup.') + @minValue(1) + @maxValue(10) + successThreshold: int? + + @description('Optional. TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported.') + tcpSocket: containerAppProbeTcpSocket? + + @description('Optional. Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod\'s terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is an alpha field and requires enabling ProbeTerminationGracePeriod feature gate. Maximum value is 3600 seconds (1 hour).') + terminationGracePeriodSeconds: int? + + @description('Optional. Number of seconds after which the probe times out. Defaults to 1 second.') + @minValue(1) + @maxValue(240) + timeoutSeconds: int? + + @description('Optional. The type of probe.') + type: ('Liveness' | 'Startup' | 'Readiness')? +} + +type containerAppProbeHttpGet = { + @description('Optional. Host name to connect to. Defaults to the pod IP.') + host: string? + + @description('Optional. HTTP headers to set in the request.') + httpHeaders: containerAppProbeHttpGetHeadersItem[]? + + @description('Required. Path to access on the HTTP server.') + path: string + + @description('Required. Name or number of the port to access on the container.') + port: int + + @description('Optional. Scheme to use for connecting to the host. Defaults to HTTP.') + scheme: ('HTTP' | 'HTTPS')? +} + +type containerAppProbeHttpGetHeadersItem = { + @description('Required. Name of the header.') + name: string + + @description('Required. Value of the header.') + value: string +} + +type containerAppProbeTcpSocket = { + @description('Optional. Host name to connect to, defaults to the pod IP.') + host: string? + + @description('Required. Number of the port to access on the container. Name must be an IANA_SVC_NAME.') + @minValue(1) + @maxValue(65535) + port: int +} + +type volumeMount = { + @description('Required. Path within the container at which the volume should be mounted.Must not contain \':\'.') + mountPath: string + + @description('Optional. Path within the volume from which the container\'s volume should be mounted. Defaults to "" (volume\'s root).') + subPath: string? + + @description('Required. This must match the Name of a Volume.') + volumeName: string +} diff --git a/avm/res/app/container-app/main.json b/avm/res/app/container-app/main.json index 2139556b3b..205c0c602d 100644 --- a/avm/res/app/container-app/main.json +++ b/avm/res/app/container-app/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "6545722573689550634" + "templateHash": "9180046963184653073" }, "name": "Container Apps", "description": "This module deploys a Container App.", @@ -126,6 +126,294 @@ } }, "nullable": true + }, + "container": { + "type": "object", + "properties": { + "args": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Container start command arguments." + } + }, + "command": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Container start command." + } + }, + "env": { + "type": "array", + "items": { + "$ref": "#/definitions/environmentVar" + }, + "nullable": true, + "metadata": { + "description": "Optional. Container environment variables." + } + }, + "image": { + "type": "string", + "metadata": { + "description": "Required. Container image tag." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Custom container name." + } + }, + "probes": { + "type": "array", + "items": { + "$ref": "#/definitions/containerAppProbe" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of probes for the container." + } + }, + "resources": { + "type": "object", + "metadata": { + "description": "Required. Container resource requirements." + } + }, + "volumeMounts": { + "type": "array", + "items": { + "$ref": "#/definitions/volumeMount" + }, + "nullable": true, + "metadata": { + "description": "Optional. Container volume mounts." + } + } + } + }, + "environmentVar": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Environment variable name." + } + }, + "secretRef": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the Container App secret from which to pull the environment variable value." + } + }, + "value": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Non-secret environment variable value." + } + } + } + }, + "containerAppProbe": { + "type": "object", + "properties": { + "failureThreshold": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 10, + "metadata": { + "description": "Optional. Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3." + } + }, + "httpGet": { + "$ref": "#/definitions/containerAppProbeHttpGet", + "nullable": true, + "metadata": { + "description": "Optional. HTTPGet specifies the http request to perform." + } + }, + "initialDelaySeconds": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 60, + "metadata": { + "description": "Optional. Number of seconds after the container has started before liveness probes are initiated." + } + }, + "periodSeconds": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 240, + "metadata": { + "description": "Optional. How often (in seconds) to perform the probe. Default to 10 seconds." + } + }, + "successThreshold": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 10, + "metadata": { + "description": "Optional. Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup." + } + }, + "tcpSocket": { + "$ref": "#/definitions/containerAppProbeTcpSocket", + "nullable": true, + "metadata": { + "description": "Optional. TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported." + } + }, + "terminationGracePeriodSeconds": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is an alpha field and requires enabling ProbeTerminationGracePeriod feature gate. Maximum value is 3600 seconds (1 hour)." + } + }, + "timeoutSeconds": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 240, + "metadata": { + "description": "Optional. Number of seconds after which the probe times out. Defaults to 1 second." + } + }, + "type": { + "type": "string", + "allowedValues": [ + "Liveness", + "Readiness", + "Startup" + ], + "nullable": true, + "metadata": { + "description": "Optional. The type of probe." + } + } + } + }, + "containerAppProbeHttpGet": { + "type": "object", + "properties": { + "host": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Host name to connect to. Defaults to the pod IP." + } + }, + "httpHeaders": { + "type": "array", + "items": { + "$ref": "#/definitions/containerAppProbeHttpGetHeadersItem" + }, + "nullable": true, + "metadata": { + "description": "Optional. HTTP headers to set in the request." + } + }, + "path": { + "type": "string", + "metadata": { + "description": "Required. Path to access on the HTTP server." + } + }, + "port": { + "type": "int", + "metadata": { + "description": "Required. Name or number of the port to access on the container." + } + }, + "scheme": { + "type": "string", + "allowedValues": [ + "HTTP", + "HTTPS" + ], + "nullable": true, + "metadata": { + "description": "Optional. Scheme to use for connecting to the host. Defaults to HTTP." + } + } + } + }, + "containerAppProbeHttpGetHeadersItem": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the header." + } + }, + "value": { + "type": "string", + "metadata": { + "description": "Required. Value of the header." + } + } + } + }, + "containerAppProbeTcpSocket": { + "type": "object", + "properties": { + "host": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Host name to connect to, defaults to the pod IP." + } + }, + "port": { + "type": "int", + "minValue": 1, + "maxValue": 65535, + "metadata": { + "description": "Required. Number of the port to access on the container. Name must be an IANA_SVC_NAME." + } + } + } + }, + "volumeMount": { + "type": "object", + "properties": { + "mountPath": { + "type": "string", + "metadata": { + "description": "Required. Path within the container at which the volume should be mounted.Must not contain ':'." + } + }, + "subPath": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Path within the volume from which the container's volume should be mounted. Defaults to \"\" (volume's root)." + } + }, + "volumeName": { + "type": "string", + "metadata": { + "description": "Required. This must match the Name of a Volume." + } + } + } } }, "parameters": { @@ -329,6 +617,9 @@ }, "containers": { "type": "array", + "items": { + "$ref": "#/definitions/container" + }, "metadata": { "description": "Required. List of container definitions for the Container App." } diff --git a/avm/res/app/container-app/version.json b/avm/res/app/container-app/version.json index c177b1bb58..aa34c4d0f5 100644 --- a/avm/res/app/container-app/version.json +++ b/avm/res/app/container-app/version.json @@ -1,7 +1,7 @@ { - "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.3", - "pathFilters": [ - "./main.json" - ] -} \ No newline at end of file + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json" + ] +} From fc9a1cd89bde9abd66ff48472e9f24fae4a92a67 Mon Sep 17 00:00:00 2001 From: Erika Gressi <56914614+eriqua@users.noreply.github.com> Date: Thu, 23 May 2024 01:06:35 +0200 Subject: [PATCH 12/18] fix: sql mi missing v2 formatting (#1857) ## Description Reformatting sql mi bicep template and test files to v2 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.sql.managed-instance](https://github.com/eriqua/bicep-registry-modules/actions/workflows/avm.res.sql.managed-instance.yml/badge.svg?branch=fix%2Fsqlmi-v2-format)](https://github.com/eriqua/bicep-registry-modules/actions/workflows/avm.res.sql.managed-instance.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- .../sql/managed-instance/database/main.bicep | 73 +++-- avm/res/sql/managed-instance/key/main.bicep | 4 +- avm/res/sql/managed-instance/main.bicep | 296 +++++++++++------- .../tests/e2e/defaults/main.test.bicep | 22 +- .../tests/e2e/max/dependencies.bicep | 5 +- .../tests/e2e/max/main.test.bicep | 217 ++++++------- .../tests/e2e/vulnAssm/main.test.bicep | 68 ++-- .../tests/e2e/waf-aligned/dependencies.bicep | 5 +- .../tests/e2e/waf-aligned/main.test.bicep | 182 +++++------ .../vulnerability-assessment/main.bicep | 19 +- .../nested_storageRoleAssignment.bicep | 5 +- 11 files changed, 506 insertions(+), 390 deletions(-) diff --git a/avm/res/sql/managed-instance/database/main.bicep b/avm/res/sql/managed-instance/database/main.bicep index a5180004e9..e61bd18dcd 100644 --- a/avm/res/sql/managed-instance/database/main.bicep +++ b/avm/res/sql/managed-instance/database/main.bicep @@ -83,18 +83,23 @@ resource database 'Microsoft.Sql/managedInstances/databases@2023-08-01-preview' restorableDroppedDatabaseId: empty(restorableDroppedDatabaseId) ? null : restorableDroppedDatabaseId storageContainerSasToken: empty(storageContainerSasToken) ? null : storageContainerSasToken recoverableDatabaseId: empty(recoverableDatabaseId) ? null : recoverableDatabaseId - longTermRetentionBackupResourceId: empty(longTermRetentionBackupResourceId) ? null : longTermRetentionBackupResourceId + longTermRetentionBackupResourceId: empty(longTermRetentionBackupResourceId) + ? null + : longTermRetentionBackupResourceId } } -resource database_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { - name: lock.?name ?? 'lock-${name}' - properties: { - level: lock.?kind ?? '' - notes: lock.?kind == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot delete or modify the resource or child resources.' +resource database_lock 'Microsoft.Authorization/locks@2020-05-01' = + if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' + ? 'Cannot delete resource or child resources.' + : 'Cannot delete or modify the resource or child resources.' + } + scope: database } - scope: database -} resource database_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [ for (diagnosticSetting, index) in (diagnosticSettings ?? []): { @@ -105,7 +110,7 @@ resource database_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021 eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId eventHubName: diagnosticSetting.?eventHubName logs: [ - for group in (diagnosticSetting.?logCategoriesAndGroups ?? [{ categoryGroup: 'allLogs' } ]): { + for group in (diagnosticSetting.?logCategoriesAndGroups ?? [{ categoryGroup: 'allLogs' }]): { categoryGroup: group.?categoryGroup category: group.?category enabled: group.?enabled ?? true @@ -118,28 +123,40 @@ resource database_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021 } ] -module database_backupShortTermRetentionPolicy 'backup-short-term-retention-policy/main.bicep' = if (!empty(backupShortTermRetentionPoliciesObj)) { - name: '${deployment().name}-BackupShortTRetPol' - params: { - managedInstanceName: managedInstanceName - databaseName: last(split(database.name, '/'))! - name: backupShortTermRetentionPoliciesObj.name - retentionDays: contains(backupShortTermRetentionPoliciesObj, 'retentionDays') ? backupShortTermRetentionPoliciesObj.retentionDays : 35 +module database_backupShortTermRetentionPolicy 'backup-short-term-retention-policy/main.bicep' = + if (!empty(backupShortTermRetentionPoliciesObj)) { + name: '${deployment().name}-BackupShortTRetPol' + params: { + managedInstanceName: managedInstanceName + databaseName: last(split(database.name, '/'))! + name: backupShortTermRetentionPoliciesObj.name + retentionDays: contains(backupShortTermRetentionPoliciesObj, 'retentionDays') + ? backupShortTermRetentionPoliciesObj.retentionDays + : 35 + } } -} -module database_backupLongTermRetentionPolicy 'backup-long-term-retention-policy/main.bicep' = if (!empty(backupLongTermRetentionPoliciesObj)) { - name: '${deployment().name}-BackupLongTRetPol' - params: { - managedInstanceName: managedInstanceName - databaseName: last(split(database.name, '/'))! - name: backupLongTermRetentionPoliciesObj.name - weekOfYear: contains(backupLongTermRetentionPoliciesObj, 'weekOfYear') ? backupLongTermRetentionPoliciesObj.weekOfYear : 5 - weeklyRetention: contains(backupLongTermRetentionPoliciesObj, 'weeklyRetention') ? backupLongTermRetentionPoliciesObj.weeklyRetention : 'P1M' - monthlyRetention: contains(backupLongTermRetentionPoliciesObj, 'monthlyRetention') ? backupLongTermRetentionPoliciesObj.monthlyRetention : 'P1Y' - yearlyRetention: contains(backupLongTermRetentionPoliciesObj, 'yearlyRetention') ? backupLongTermRetentionPoliciesObj.yearlyRetention : 'P5Y' +module database_backupLongTermRetentionPolicy 'backup-long-term-retention-policy/main.bicep' = + if (!empty(backupLongTermRetentionPoliciesObj)) { + name: '${deployment().name}-BackupLongTRetPol' + params: { + managedInstanceName: managedInstanceName + databaseName: last(split(database.name, '/'))! + name: backupLongTermRetentionPoliciesObj.name + weekOfYear: contains(backupLongTermRetentionPoliciesObj, 'weekOfYear') + ? backupLongTermRetentionPoliciesObj.weekOfYear + : 5 + weeklyRetention: contains(backupLongTermRetentionPoliciesObj, 'weeklyRetention') + ? backupLongTermRetentionPoliciesObj.weeklyRetention + : 'P1M' + monthlyRetention: contains(backupLongTermRetentionPoliciesObj, 'monthlyRetention') + ? backupLongTermRetentionPoliciesObj.monthlyRetention + : 'P1Y' + yearlyRetention: contains(backupLongTermRetentionPoliciesObj, 'yearlyRetention') + ? backupLongTermRetentionPoliciesObj.yearlyRetention + : 'P5Y' + } } -} @description('The name of the deployed database.') output name string = database.name diff --git a/avm/res/sql/managed-instance/key/main.bicep b/avm/res/sql/managed-instance/key/main.bicep index d95ac84f4b..5c853ac70d 100644 --- a/avm/res/sql/managed-instance/key/main.bicep +++ b/avm/res/sql/managed-instance/key/main.bicep @@ -22,7 +22,9 @@ var splittedKeyUri = split(uri, '/') // if serverManaged, use serverManaged, if uri provided use concated uri value // MUST match the pattern '__' -var serverKeyName = empty(uri) ? 'ServiceManaged' : '${split(splittedKeyUri[2], '.')[0]}_${splittedKeyUri[4]}_${splittedKeyUri[5]}' +var serverKeyName = empty(uri) + ? 'ServiceManaged' + : '${split(splittedKeyUri[2], '.')[0]}_${splittedKeyUri[4]}_${splittedKeyUri[5]}' resource managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' existing = { name: managedInstanceName diff --git a/avm/res/sql/managed-instance/main.bicep b/avm/res/sql/managed-instance/main.bicep index c180440c4d..52aa3ea5f3 100644 --- a/avm/res/sql/managed-instance/main.bicep +++ b/avm/res/sql/managed-instance/main.bicep @@ -143,47 +143,81 @@ param minimalTlsVersion string = '1.2' ]) param requestedBackupStorageRedundancy string = 'Geo' -var formattedUserAssignedIdentities = reduce(map((managedIdentities.?userAssignedResourceIds ?? []), (id) => { '${id}': {} }), {}, (cur, next) => union(cur, next)) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} } - -var identity = !empty(managedIdentities) ? { - type: (managedIdentities.?systemAssigned ?? false) ? (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') : (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : null) - userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null -} : null - +var formattedUserAssignedIdentities = reduce( + map((managedIdentities.?userAssignedResourceIds ?? []), (id) => { '${id}': {} }), + {}, + (cur, next) => union(cur, next) +) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} } + +var identity = !empty(managedIdentities) + ? { + type: (managedIdentities.?systemAssigned ?? false) + ? (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') + : (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : null) + userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null + } + : null var builtInRoleNames = { Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') - 'Reservation Purchaser': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f7b75c60-3036-4b75-91c3-6b41c27c1689') - 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') - 'SQL DB Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9b7fa17d-e63e-47b0-bb0a-15c516ac86ec') - 'SQL Managed Instance Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4939a1f6-9ae0-4e48-a1e0-f2cbe897382d') - 'SQL Security Manager': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '056cd41c-7e88-42e1-933e-88ba6a50c9c3') - 'SQL Server Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6d8ee4ec-f05a-4a1d-8b00-a9b17e38b437') - 'SqlDb Migration Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '189207d4-bb67-4208-a635-b06afe8b2c57') - 'SqlMI Migration Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1d335eef-eee1-47fe-a9e0-53214eba8872') - 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') + 'Reservation Purchaser': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f7b75c60-3036-4b75-91c3-6b41c27c1689' + ) + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' + ) + 'SQL DB Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '9b7fa17d-e63e-47b0-bb0a-15c516ac86ec' + ) + 'SQL Managed Instance Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '4939a1f6-9ae0-4e48-a1e0-f2cbe897382d' + ) + 'SQL Security Manager': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '056cd41c-7e88-42e1-933e-88ba6a50c9c3' + ) + 'SQL Server Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '6d8ee4ec-f05a-4a1d-8b00-a9b17e38b437' + ) + 'SqlDb Migration Role': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '189207d4-bb67-4208-a635-b06afe8b2c57' + ) + 'SqlMI Migration Role': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '1d335eef-eee1-47fe-a9e0-53214eba8872' + ) + 'User Access Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' + ) } - -resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { - name: '46d3xbcp.res.sql-managedinstance.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' - properties: { - mode: 'Incremental' - template: { - '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' - contentVersion: '1.0.0.0' - resources: [] - outputs: { - telemetry: { - type: 'String' - value: 'For more information, see https://aka.ms/avm/TelemetryInfo' +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = + if (enableTelemetry) { + name: '46d3xbcp.res.sql-managedinstance.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + outputs: { + telemetry: { + type: 'String' + value: 'For more information, see https://aka.ms/avm/TelemetryInfo' + } } } } } -} resource managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' = { name: name @@ -222,14 +256,17 @@ resource managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' = { } } -resource managedInstance_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { - name: lock.?name ?? 'lock-${name}' - properties: { - level: lock.?kind ?? '' - notes: lock.?kind == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot delete or modify the resource or child resources.' +resource managedInstance_lock 'Microsoft.Authorization/locks@2020-05-01' = + if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' + ? 'Cannot delete resource or child resources.' + : 'Cannot delete or modify the resource or child resources.' + } + scope: managedInstance } - scope: managedInstance -} resource managedInstance_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [ for (diagnosticSetting, index) in (diagnosticSettings ?? []): { @@ -273,89 +310,126 @@ resource managedInstance_roleAssignments 'Microsoft.Authorization/roleAssignment } ] -module managedInstance_databases 'database/main.bicep' = [for (database, index) in databases: { - name: '${uniqueString(deployment().name, location)}-SqlMi-DB-${index}' - params: { - name: database.name - managedInstanceName: managedInstance.name - catalogCollation: contains(database, 'catalogCollation') ? database.catalogCollation : 'SQL_Latin1_General_CP1_CI_AS' - collation: contains(database, 'collation') ? database.collation : 'SQL_Latin1_General_CP1_CI_AS' - createMode: contains(database, 'createMode') ? database.createMode : 'Default' - diagnosticSettings: database.?diagnosticSettings - location: contains(database, 'location') ? database.location : managedInstance.location - lock: database.?lock ?? lock - longTermRetentionBackupResourceId: contains(database, 'longTermRetentionBackupResourceId') ? database.longTermRetentionBackupResourceId : '' - recoverableDatabaseId: contains(database, 'recoverableDatabaseId') ? database.recoverableDatabaseId : '' - restorableDroppedDatabaseId: contains(database, 'restorableDroppedDatabaseId') ? database.restorableDroppedDatabaseId : '' - restorePointInTime: contains(database, 'restorePointInTime') ? database.restorePointInTime : '' - sourceDatabaseId: contains(database, 'sourceDatabaseId') ? database.sourceDatabaseId : '' - storageContainerSasToken: contains(database, 'storageContainerSasToken') ? database.storageContainerSasToken : '' - storageContainerUri: contains(database, 'storageContainerUri') ? database.storageContainerUri : '' - tags: database.?tags ?? tags - backupShortTermRetentionPoliciesObj: contains(database, 'backupShortTermRetentionPolicies') ? database.backupShortTermRetentionPolicies : {} - backupLongTermRetentionPoliciesObj: contains(database, 'backupLongTermRetentionPolicies') ? database.backupLongTermRetentionPolicies : {} +module managedInstance_databases 'database/main.bicep' = [ + for (database, index) in databases: { + name: '${uniqueString(deployment().name, location)}-SqlMi-DB-${index}' + params: { + name: database.name + managedInstanceName: managedInstance.name + catalogCollation: contains(database, 'catalogCollation') + ? database.catalogCollation + : 'SQL_Latin1_General_CP1_CI_AS' + collation: contains(database, 'collation') ? database.collation : 'SQL_Latin1_General_CP1_CI_AS' + createMode: contains(database, 'createMode') ? database.createMode : 'Default' + diagnosticSettings: database.?diagnosticSettings + location: contains(database, 'location') ? database.location : managedInstance.location + lock: database.?lock ?? lock + longTermRetentionBackupResourceId: contains(database, 'longTermRetentionBackupResourceId') + ? database.longTermRetentionBackupResourceId + : '' + recoverableDatabaseId: contains(database, 'recoverableDatabaseId') ? database.recoverableDatabaseId : '' + restorableDroppedDatabaseId: contains(database, 'restorableDroppedDatabaseId') + ? database.restorableDroppedDatabaseId + : '' + restorePointInTime: contains(database, 'restorePointInTime') ? database.restorePointInTime : '' + sourceDatabaseId: contains(database, 'sourceDatabaseId') ? database.sourceDatabaseId : '' + storageContainerSasToken: contains(database, 'storageContainerSasToken') ? database.storageContainerSasToken : '' + storageContainerUri: contains(database, 'storageContainerUri') ? database.storageContainerUri : '' + tags: database.?tags ?? tags + backupShortTermRetentionPoliciesObj: contains(database, 'backupShortTermRetentionPolicies') + ? database.backupShortTermRetentionPolicies + : {} + backupLongTermRetentionPoliciesObj: contains(database, 'backupLongTermRetentionPolicies') + ? database.backupLongTermRetentionPolicies + : {} + } } -}] - -module managedInstance_securityAlertPolicy 'security-alert-policy/main.bicep' = if (!empty(securityAlertPoliciesObj)) { - name: '${uniqueString(deployment().name, location)}-SqlMi-SecAlertPol' - params: { - managedInstanceName: managedInstance.name - name: securityAlertPoliciesObj.name - emailAccountAdmins: contains(securityAlertPoliciesObj, 'emailAccountAdmins') ? securityAlertPoliciesObj.emailAccountAdmins : false - state: contains(securityAlertPoliciesObj, 'state') ? securityAlertPoliciesObj.state : 'Disabled' +] + +module managedInstance_securityAlertPolicy 'security-alert-policy/main.bicep' = + if (!empty(securityAlertPoliciesObj)) { + name: '${uniqueString(deployment().name, location)}-SqlMi-SecAlertPol' + params: { + managedInstanceName: managedInstance.name + name: securityAlertPoliciesObj.name + emailAccountAdmins: contains(securityAlertPoliciesObj, 'emailAccountAdmins') + ? securityAlertPoliciesObj.emailAccountAdmins + : false + state: contains(securityAlertPoliciesObj, 'state') ? securityAlertPoliciesObj.state : 'Disabled' + } } -} -module managedInstance_vulnerabilityAssessment 'vulnerability-assessment/main.bicep' = if (!empty(vulnerabilityAssessmentsObj) && (managedIdentities.?systemAssigned ?? false)) { - name: '${uniqueString(deployment().name, location)}-SqlMi-VulnAssessm' - params: { - managedInstanceName: managedInstance.name - name: vulnerabilityAssessmentsObj.name - recurringScansEmails: contains(vulnerabilityAssessmentsObj, 'recurringScansEmails') ? vulnerabilityAssessmentsObj.recurringScansEmails : [] - recurringScansEmailSubscriptionAdmins: contains(vulnerabilityAssessmentsObj, 'recurringScansEmailSubscriptionAdmins') ? vulnerabilityAssessmentsObj.recurringScansEmailSubscriptionAdmins : false - recurringScansIsEnabled: contains(vulnerabilityAssessmentsObj, 'recurringScansIsEnabled') ? vulnerabilityAssessmentsObj.recurringScansIsEnabled : false - storageAccountResourceId: vulnerabilityAssessmentsObj.storageAccountResourceId - useStorageAccountAccessKey: contains(vulnerabilityAssessmentsObj, 'useStorageAccountAccessKey') ? vulnerabilityAssessmentsObj.useStorageAccountAccessKey : false - createStorageRoleAssignment: contains(vulnerabilityAssessmentsObj, 'createStorageRoleAssignment') ? vulnerabilityAssessmentsObj.createStorageRoleAssignment : true +module managedInstance_vulnerabilityAssessment 'vulnerability-assessment/main.bicep' = + if (!empty(vulnerabilityAssessmentsObj) && (managedIdentities.?systemAssigned ?? false)) { + name: '${uniqueString(deployment().name, location)}-SqlMi-VulnAssessm' + params: { + managedInstanceName: managedInstance.name + name: vulnerabilityAssessmentsObj.name + recurringScansEmails: contains(vulnerabilityAssessmentsObj, 'recurringScansEmails') + ? vulnerabilityAssessmentsObj.recurringScansEmails + : [] + recurringScansEmailSubscriptionAdmins: contains( + vulnerabilityAssessmentsObj, + 'recurringScansEmailSubscriptionAdmins' + ) + ? vulnerabilityAssessmentsObj.recurringScansEmailSubscriptionAdmins + : false + recurringScansIsEnabled: contains(vulnerabilityAssessmentsObj, 'recurringScansIsEnabled') + ? vulnerabilityAssessmentsObj.recurringScansIsEnabled + : false + storageAccountResourceId: vulnerabilityAssessmentsObj.storageAccountResourceId + useStorageAccountAccessKey: contains(vulnerabilityAssessmentsObj, 'useStorageAccountAccessKey') + ? vulnerabilityAssessmentsObj.useStorageAccountAccessKey + : false + createStorageRoleAssignment: contains(vulnerabilityAssessmentsObj, 'createStorageRoleAssignment') + ? vulnerabilityAssessmentsObj.createStorageRoleAssignment + : true + } + dependsOn: [ + managedInstance_securityAlertPolicy + ] } - dependsOn: [ - managedInstance_securityAlertPolicy - ] -} -module managedInstance_keys 'key/main.bicep' = [for (key, index) in keys: { - name: '${uniqueString(deployment().name, location)}-SqlMi-Key-${index}' - params: { - name: key.name - managedInstanceName: managedInstance.name - serverKeyType: contains(key, 'serverKeyType') ? key.serverKeyType : 'ServiceManaged' - uri: contains(key, 'uri') ? key.uri : '' +module managedInstance_keys 'key/main.bicep' = [ + for (key, index) in keys: { + name: '${uniqueString(deployment().name, location)}-SqlMi-Key-${index}' + params: { + name: key.name + managedInstanceName: managedInstance.name + serverKeyType: contains(key, 'serverKeyType') ? key.serverKeyType : 'ServiceManaged' + uri: contains(key, 'uri') ? key.uri : '' + } } -}] - -module managedInstance_encryptionProtector 'encryption-protector/main.bicep' = if (!empty(encryptionProtectorObj)) { - name: '${uniqueString(deployment().name, location)}-SqlMi-EncryProtector' - params: { - managedInstanceName: managedInstance.name - serverKeyName: encryptionProtectorObj.serverKeyName - serverKeyType: contains(encryptionProtectorObj, 'serverKeyType') ? encryptionProtectorObj.serverKeyType : 'ServiceManaged' - autoRotationEnabled: contains(encryptionProtectorObj, 'autoRotationEnabled') ? encryptionProtectorObj.autoRotationEnabled : true +] + +module managedInstance_encryptionProtector 'encryption-protector/main.bicep' = + if (!empty(encryptionProtectorObj)) { + name: '${uniqueString(deployment().name, location)}-SqlMi-EncryProtector' + params: { + managedInstanceName: managedInstance.name + serverKeyName: encryptionProtectorObj.serverKeyName + serverKeyType: contains(encryptionProtectorObj, 'serverKeyType') + ? encryptionProtectorObj.serverKeyType + : 'ServiceManaged' + autoRotationEnabled: contains(encryptionProtectorObj, 'autoRotationEnabled') + ? encryptionProtectorObj.autoRotationEnabled + : true + } + dependsOn: [ + managedInstance_keys + ] } - dependsOn: [ - managedInstance_keys - ] -} -module managedInstance_administrator 'administrator/main.bicep' = if (!empty(administratorsObj)) { - name: '${uniqueString(deployment().name, location)}-SqlMi-Admin' - params: { - managedInstanceName: managedInstance.name - login: administratorsObj.name - sid: administratorsObj.sid - tenantId: contains(administratorsObj, 'tenantId') ? administratorsObj.tenantId : '' +module managedInstance_administrator 'administrator/main.bicep' = + if (!empty(administratorsObj)) { + name: '${uniqueString(deployment().name, location)}-SqlMi-Admin' + params: { + managedInstanceName: managedInstance.name + login: administratorsObj.name + sid: administratorsObj.sid + tenantId: contains(administratorsObj, 'tenantId') ? administratorsObj.tenantId : '' + } } -} @description('The name of the deployed managed instance.') output name string = managedInstance.name diff --git a/avm/res/sql/managed-instance/tests/e2e/defaults/main.test.bicep b/avm/res/sql/managed-instance/tests/e2e/defaults/main.test.bicep index e6eda07e9e..49b54f4a63 100644 --- a/avm/res/sql/managed-instance/tests/e2e/defaults/main.test.bicep +++ b/avm/res/sql/managed-instance/tests/e2e/defaults/main.test.bicep @@ -51,14 +51,16 @@ module nestedDependencies 'dependencies.bicep' = { // ============== // @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}' - location: resourceLocation - administratorLogin: 'adminUserName' - administratorLoginPassword: password - subnetResourceId: nestedDependencies.outputs.subnetResourceId +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}-${serviceShort}' + location: resourceLocation + administratorLogin: 'adminUserName' + administratorLoginPassword: password + subnetResourceId: nestedDependencies.outputs.subnetResourceId + } } -}] +] diff --git a/avm/res/sql/managed-instance/tests/e2e/max/dependencies.bicep b/avm/res/sql/managed-instance/tests/e2e/max/dependencies.bicep index 20398fd372..5d852c35aa 100644 --- a/avm/res/sql/managed-instance/tests/e2e/max/dependencies.bicep +++ b/avm/res/sql/managed-instance/tests/e2e/max/dependencies.bicep @@ -302,7 +302,10 @@ resource keyPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { scope: keyVault::key properties: { principalId: managedIdentity.properties.principalId - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'e147488a-f6f5-4113-8e2d-b22465e65bf6' + ) // Key Vault Crypto Service Encryption User principalType: 'ServicePrincipal' } } diff --git a/avm/res/sql/managed-instance/tests/e2e/max/main.test.bicep b/avm/res/sql/managed-instance/tests/e2e/max/main.test.bicep index b45ce3cbb7..6e1643f2b3 100644 --- a/avm/res/sql/managed-instance/tests/e2e/max/main.test.bicep +++ b/avm/res/sql/managed-instance/tests/e2e/max/main.test.bicep @@ -71,119 +71,124 @@ module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/t // ============== // @batchSize(1) -module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { - scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' - params: { - location: resourceLocation - name: '${namePrefix}-${serviceShort}' - administratorLogin: 'adminUserName' - administratorLoginPassword: password - subnetResourceId: nestedDependencies.outputs.subnetResourceId - collation: 'SQL_Latin1_General_CP1_CI_AS' - databases: [ - { - backupLongTermRetentionPolicies: { - name: 'default' - } - backupShortTermRetentionPolicies: { - name: 'default' - } - name: '${namePrefix}-${serviceShort}-db-001' - diagnosticSettings: [ - { - name: 'customSetting' - eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName - eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId - storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId - workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}-${serviceShort}' + administratorLogin: 'adminUserName' + administratorLoginPassword: password + subnetResourceId: nestedDependencies.outputs.subnetResourceId + collation: 'SQL_Latin1_General_CP1_CI_AS' + databases: [ + { + backupLongTermRetentionPolicies: { + name: 'default' } - ] - } - ] - diagnosticSettings: [ - { - name: 'customSetting' - logCategoriesAndGroups:[ - { - categoryGroup: 'allLogs' + backupShortTermRetentionPolicies: { + name: 'default' } - ] - eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName - eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId - storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId - workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId - } - ] - dnsZonePartner: '' - encryptionProtectorObj: { - serverKeyName: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' - serverKeyType: 'AzureKeyVault' - } - hardwareFamily: 'Gen5' - keys: [ - { - name: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' + name: '${namePrefix}-${serviceShort}-db-001' + diagnosticSettings: [ + { + name: 'customSetting' + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + } + ] + diagnosticSettings: [ + { + name: 'customSetting' + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + dnsZonePartner: '' + encryptionProtectorObj: { + serverKeyName: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' serverKeyType: 'AzureKeyVault' - uri: nestedDependencies.outputs.keyVaultEncryptionKeyUrl } - ] - licenseType: 'LicenseIncluded' - lock: { - kind: 'CanNotDelete' - name: 'myCustomLockName' - } - primaryUserAssignedIdentityId: nestedDependencies.outputs.managedIdentityResourceId - proxyOverride: 'Proxy' - publicDataEndpointEnabled: false - roleAssignments: [ - { - roleDefinitionIdOrName: 'Owner' - principalId: nestedDependencies.outputs.managedIdentityPrincipalId - principalType: 'ServicePrincipal' + hardwareFamily: 'Gen5' + keys: [ + { + name: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' + serverKeyType: 'AzureKeyVault' + uri: nestedDependencies.outputs.keyVaultEncryptionKeyUrl + } + ] + licenseType: 'LicenseIncluded' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' } - { - roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' - principalId: nestedDependencies.outputs.managedIdentityPrincipalId - principalType: 'ServicePrincipal' + primaryUserAssignedIdentityId: nestedDependencies.outputs.managedIdentityResourceId + proxyOverride: 'Proxy' + publicDataEndpointEnabled: false + roleAssignments: [ + { + roleDefinitionIdOrName: 'Owner' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'acdd72a7-3385-48ef-bd42-f606fba81ae7' + ) + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + securityAlertPoliciesObj: { + emailAccountAdmins: true + name: 'default' + state: 'Enabled' } - { - roleDefinitionIdOrName: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') - principalId: nestedDependencies.outputs.managedIdentityPrincipalId - principalType: 'ServicePrincipal' + servicePrincipal: 'SystemAssigned' + skuName: 'GP_Gen5' + skuTier: 'GeneralPurpose' + storageSizeInGB: 32 + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] } - ] - securityAlertPoliciesObj: { - emailAccountAdmins: true - name: 'default' - state: 'Enabled' - } - servicePrincipal: 'SystemAssigned' - skuName: 'GP_Gen5' - skuTier: 'GeneralPurpose' - storageSizeInGB: 32 - managedIdentities: { - systemAssigned: true - userAssignedResourceIds: [ - nestedDependencies.outputs.managedIdentityResourceId - ] - } - timezoneId: 'UTC' - vCores: 4 - vulnerabilityAssessmentsObj: { - emailSubscriptionAdmins: true - name: 'default' - recurringScansEmails: [ - 'test1@contoso.com' - 'test2@contoso.com' - ] - recurringScansIsEnabled: true - storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId - tags: { - 'hidden-title': 'This is visible in the resource name' - Environment: 'Non-Prod' - Role: 'DeploymentValidation' + timezoneId: 'UTC' + vCores: 4 + vulnerabilityAssessmentsObj: { + emailSubscriptionAdmins: true + name: 'default' + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + recurringScansIsEnabled: true + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } } } } -}] +] diff --git a/avm/res/sql/managed-instance/tests/e2e/vulnAssm/main.test.bicep b/avm/res/sql/managed-instance/tests/e2e/vulnAssm/main.test.bicep index a51971ca80..9b39f9bbd3 100644 --- a/avm/res/sql/managed-instance/tests/e2e/vulnAssm/main.test.bicep +++ b/avm/res/sql/managed-instance/tests/e2e/vulnAssm/main.test.bicep @@ -52,39 +52,41 @@ module nestedDependencies 'dependencies.bicep' = { // ============== // @batchSize(1) -module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { - scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' - params: { - location: resourceLocation - name: '${namePrefix}-${serviceShort}' - administratorLogin: 'adminUserName' - administratorLoginPassword: password - subnetResourceId: nestedDependencies.outputs.subnetResourceId - managedIdentities: { - systemAssigned: true - } - securityAlertPoliciesObj: { - emailAccountAdmins: true - name: 'default' - state: 'Enabled' - } - vulnerabilityAssessmentsObj: { - emailSubscriptionAdmins: true - name: 'default' - recurringScansEmails: [ - 'test1@contoso.com' - 'test2@contoso.com' - ] - recurringScansIsEnabled: true - storageAccountResourceId: nestedDependencies.outputs.storageAccountResourceId - useStorageAccountAccessKey: false - createStorageRoleAssignment: true - tags: { - 'hidden-title': 'This is visible in the resource name' - Environment: 'Non-Prod' - Role: 'DeploymentValidation' +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}-${serviceShort}' + administratorLogin: 'adminUserName' + administratorLoginPassword: password + subnetResourceId: nestedDependencies.outputs.subnetResourceId + managedIdentities: { + systemAssigned: true + } + securityAlertPoliciesObj: { + emailAccountAdmins: true + name: 'default' + state: 'Enabled' + } + vulnerabilityAssessmentsObj: { + emailSubscriptionAdmins: true + name: 'default' + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + recurringScansIsEnabled: true + storageAccountResourceId: nestedDependencies.outputs.storageAccountResourceId + useStorageAccountAccessKey: false + createStorageRoleAssignment: true + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } } } } -}] +] diff --git a/avm/res/sql/managed-instance/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/sql/managed-instance/tests/e2e/waf-aligned/dependencies.bicep index 20398fd372..5d852c35aa 100644 --- a/avm/res/sql/managed-instance/tests/e2e/waf-aligned/dependencies.bicep +++ b/avm/res/sql/managed-instance/tests/e2e/waf-aligned/dependencies.bicep @@ -302,7 +302,10 @@ resource keyPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { scope: keyVault::key properties: { principalId: managedIdentity.properties.principalId - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'e147488a-f6f5-4113-8e2d-b22465e65bf6' + ) // Key Vault Crypto Service Encryption User principalType: 'ServicePrincipal' } } diff --git a/avm/res/sql/managed-instance/tests/e2e/waf-aligned/main.test.bicep b/avm/res/sql/managed-instance/tests/e2e/waf-aligned/main.test.bicep index 7cbd5f19b5..8c72d7b96e 100644 --- a/avm/res/sql/managed-instance/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/sql/managed-instance/tests/e2e/waf-aligned/main.test.bicep @@ -71,102 +71,104 @@ module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/t // ============== // @batchSize(1) -module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { - scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' - params: { - location: resourceLocation - name: '${namePrefix}-${serviceShort}' - administratorLogin: 'adminUserName' - administratorLoginPassword: password - subnetResourceId: nestedDependencies.outputs.subnetResourceId - collation: 'SQL_Latin1_General_CP1_CI_AS' - databases: [ - { - backupLongTermRetentionPolicies: { - name: 'default' +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}-${serviceShort}' + administratorLogin: 'adminUserName' + administratorLoginPassword: password + subnetResourceId: nestedDependencies.outputs.subnetResourceId + collation: 'SQL_Latin1_General_CP1_CI_AS' + databases: [ + { + backupLongTermRetentionPolicies: { + name: 'default' + } + backupShortTermRetentionPolicies: { + name: 'default' + } + name: '${namePrefix}-${serviceShort}-db-001' + diagnosticSettings: [ + { + name: 'customSetting' + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] } - backupShortTermRetentionPolicies: { - name: 'default' + ] + diagnosticSettings: [ + { + name: 'customSetting' + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId } - name: '${namePrefix}-${serviceShort}-db-001' - diagnosticSettings: [ - { - name: 'customSetting' - eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName - eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId - storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId - workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId - } + ] + dnsZonePartner: '' + encryptionProtectorObj: { + serverKeyName: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' + serverKeyType: 'AzureKeyVault' + } + hardwareFamily: 'Gen5' + keys: [ + { + name: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' + serverKeyType: 'AzureKeyVault' + uri: nestedDependencies.outputs.keyVaultEncryptionKeyUrl + } + ] + licenseType: 'LicenseIncluded' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + primaryUserAssignedIdentityId: nestedDependencies.outputs.managedIdentityResourceId + proxyOverride: 'Proxy' + publicDataEndpointEnabled: false + securityAlertPoliciesObj: { + emailAccountAdmins: true + name: 'default' + state: 'Enabled' + } + servicePrincipal: 'SystemAssigned' + skuName: 'GP_Gen5' + skuTier: 'GeneralPurpose' + storageSizeInGB: 32 + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId ] } - ] - diagnosticSettings: [ - { - name: 'customSetting' - logCategoriesAndGroups: [ - { - categoryGroup: 'allLogs' - } + timezoneId: 'UTC' + vCores: 4 + vulnerabilityAssessmentsObj: { + emailSubscriptionAdmins: true + name: 'default' + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' ] - eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName - eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + recurringScansIsEnabled: true storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId - workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId - } - ] - dnsZonePartner: '' - encryptionProtectorObj: { - serverKeyName: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' - serverKeyType: 'AzureKeyVault' - } - hardwareFamily: 'Gen5' - keys: [ - { - name: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' - serverKeyType: 'AzureKeyVault' - uri: nestedDependencies.outputs.keyVaultEncryptionKeyUrl - } - ] - licenseType: 'LicenseIncluded' - lock: { - kind: 'CanNotDelete' - name: 'myCustomLockName' - } - primaryUserAssignedIdentityId: nestedDependencies.outputs.managedIdentityResourceId - proxyOverride: 'Proxy' - publicDataEndpointEnabled: false - securityAlertPoliciesObj: { - emailAccountAdmins: true - name: 'default' - state: 'Enabled' - } - servicePrincipal: 'SystemAssigned' - skuName: 'GP_Gen5' - skuTier: 'GeneralPurpose' - storageSizeInGB: 32 - managedIdentities: { - systemAssigned: true - userAssignedResourceIds: [ - nestedDependencies.outputs.managedIdentityResourceId - ] - } - timezoneId: 'UTC' - vCores: 4 - vulnerabilityAssessmentsObj: { - emailSubscriptionAdmins: true - name: 'default' - recurringScansEmails: [ - 'test1@contoso.com' - 'test2@contoso.com' - ] - recurringScansIsEnabled: true - storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId - tags: { - 'hidden-title': 'This is visible in the resource name' - Environment: 'Non-Prod' - Role: 'DeploymentValidation' + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } } } } -}] +] diff --git a/avm/res/sql/managed-instance/vulnerability-assessment/main.bicep b/avm/res/sql/managed-instance/vulnerability-assessment/main.bicep index 4bac1770f6..85b9a48d75 100644 --- a/avm/res/sql/managed-instance/vulnerability-assessment/main.bicep +++ b/avm/res/sql/managed-instance/vulnerability-assessment/main.bicep @@ -31,21 +31,24 @@ resource managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' exi } // Assign SQL MI MSI access to storage account -module storageAccount_sbdc_rbac 'modules/nested_storageRoleAssignment.bicep' = if (!useStorageAccountAccessKey && createStorageRoleAssignment) { - name: '${managedInstance.name}-sbdc-rbac' - scope: resourceGroup(split(storageAccountResourceId, '/')[4]) - params: { - storageAccountName: last(split(storageAccountResourceId, '/')) - managedInstanceIdentityPrincipalId: managedInstance.identity.principalId +module storageAccount_sbdc_rbac 'modules/nested_storageRoleAssignment.bicep' = + if (!useStorageAccountAccessKey && createStorageRoleAssignment) { + name: '${managedInstance.name}-sbdc-rbac' + scope: resourceGroup(split(storageAccountResourceId, '/')[4]) + params: { + storageAccountName: last(split(storageAccountResourceId, '/')) + managedInstanceIdentityPrincipalId: managedInstance.identity.principalId + } } -} resource vulnerabilityAssessment 'Microsoft.Sql/managedInstances/vulnerabilityAssessments@2023-08-01-preview' = { name: name parent: managedInstance properties: { storageContainerPath: 'https://${last(split(storageAccountResourceId, '/'))}.blob.${environment().suffixes.storage}/vulnerability-assessment/' - storageAccountAccessKey: useStorageAccountAccessKey ? listKeys(storageAccountResourceId, '2019-06-01').keys[0].value : any(null) + storageAccountAccessKey: useStorageAccountAccessKey + ? listKeys(storageAccountResourceId, '2019-06-01').keys[0].value + : any(null) recurringScans: { isEnabled: recurringScansIsEnabled emailSubscriptionAdmins: recurringScansEmailSubscriptionAdmins diff --git a/avm/res/sql/managed-instance/vulnerability-assessment/modules/nested_storageRoleAssignment.bicep b/avm/res/sql/managed-instance/vulnerability-assessment/modules/nested_storageRoleAssignment.bicep index 7ef3f28a53..03367f8fb2 100644 --- a/avm/res/sql/managed-instance/vulnerability-assessment/modules/nested_storageRoleAssignment.bicep +++ b/avm/res/sql/managed-instance/vulnerability-assessment/modules/nested_storageRoleAssignment.bicep @@ -13,7 +13,10 @@ resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid('${storageAccount.id}-${managedInstanceIdentityPrincipalId}-Storage-Blob-Data-Contributor') scope: storageAccount properties: { - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' + ) principalId: managedInstanceIdentityPrincipalId principalType: 'ServicePrincipal' } From e75aa3a8a79c506c1c543d1a37551deec74ef8e7 Mon Sep 17 00:00:00 2001 From: ChrisSidebotham-MSFT <48600046+ChrisSidebotham@users.noreply.github.com> Date: Thu, 23 May 2024 09:30:32 +0100 Subject: [PATCH 13/18] fix: Key-Vault WAF Test Fix (#2013) ## Description Closes #2010 Key-Vault WAF Test Fix ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.key-vault.vault](https://github.com/ChrisSidebotham/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml/badge.svg?branch=kv-fix)](https://github.com/ChrisSidebotham/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/key-vault/vault/README.md | 28 +++++++++++++------ avm/res/key-vault/vault/key/main.bicep | 16 ----------- avm/res/key-vault/vault/key/main.json | 6 +--- avm/res/key-vault/vault/main.json | 15 ++-------- avm/res/key-vault/vault/secret/main.bicep | 20 ------------- avm/res/key-vault/vault/secret/main.json | 7 +---- .../tests/e2e/waf-aligned/main.test.bicep | 14 +++++++--- 7 files changed, 35 insertions(+), 71 deletions(-) diff --git a/avm/res/key-vault/vault/README.md b/avm/res/key-vault/vault/README.md index ac00599aa6..f8e4cfc1f1 100644 --- a/avm/res/key-vault/vault/README.md +++ b/avm/res/key-vault/vault/README.md @@ -867,8 +867,11 @@ module vault 'br/public:avm/res/key-vault/vault:' = { enableRbacAuthorization: true keys: [ { - attributesExp: 1725109032 - attributesNbf: 10000 + attributes: { + enabled: true + exp: 1702648632 + nbf: 10000 + } keySize: 4096 name: 'keyName' rotationPolicy: { @@ -916,8 +919,11 @@ module vault 'br/public:avm/res/key-vault/vault:' = { ] secrets: [ { - attributesExp: 1702648632 - attributesNbf: 10000 + attributes: { + enabled: true + exp: 1702648632 + nbf: 10000 + } contentType: 'Something' name: 'secretName' value: 'secretValue' @@ -967,8 +973,11 @@ module vault 'br/public:avm/res/key-vault/vault:' = { "keys": { "value": [ { - "attributesExp": 1725109032, - "attributesNbf": 10000, + "attributes": { + "enabled": true, + "exp": 1702648632, + "nbf": 10000 + }, "keySize": 4096, "name": "keyName", "rotationPolicy": { @@ -1026,8 +1035,11 @@ module vault 'br/public:avm/res/key-vault/vault:' = { "secrets": { "value": [ { - "attributesExp": 1702648632, - "attributesNbf": 10000, + "attributes": { + "enabled": true, + "exp": 1702648632, + "nbf": 10000 + }, "contentType": "Something", "name": "secretName", "value": "secretValue" diff --git a/avm/res/key-vault/vault/key/main.bicep b/avm/res/key-vault/vault/key/main.bicep index c6b5ee3819..300c781271 100644 --- a/avm/res/key-vault/vault/key/main.bicep +++ b/avm/res/key-vault/vault/key/main.bicep @@ -68,14 +68,6 @@ var builtInRoleNames = { 'Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483' ) - 'Key Vault Certificates Officer': subscriptionResourceId( - 'Microsoft.Authorization/roleDefinitions', - 'a4417e6f-fecd-4de8-b567-7b0420556985' - ) - 'Key Vault Certificate User': subscriptionResourceId( - 'Microsoft.Authorization/roleDefinitions', - 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba' - ) 'Key Vault Contributor': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395' @@ -96,14 +88,6 @@ var builtInRoleNames = { 'Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2' ) - 'Key Vault Secrets Officer': subscriptionResourceId( - 'Microsoft.Authorization/roleDefinitions', - 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7' - ) - 'Key Vault Secrets User': subscriptionResourceId( - 'Microsoft.Authorization/roleDefinitions', - '4633458b-17de-408a-b874-0445c86b69e6' - ) Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( diff --git a/avm/res/key-vault/vault/key/main.json b/avm/res/key-vault/vault/key/main.json index 91962de233..52b95e83b3 100644 --- a/avm/res/key-vault/vault/key/main.json +++ b/avm/res/key-vault/vault/key/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "7648379973162364092" + "templateHash": "2556304156587722941" }, "name": "Key Vault Keys", "description": "This module deploys a Key Vault Key.", @@ -195,15 +195,11 @@ "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", - "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", - "Key Vault Certificate User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba')]", "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", - "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", - "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", diff --git a/avm/res/key-vault/vault/main.json b/avm/res/key-vault/vault/main.json index 0a867b281c..33fcf07d57 100644 --- a/avm/res/key-vault/vault/main.json +++ b/avm/res/key-vault/vault/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "17896878469622609898" + "templateHash": "18302078907641172783" }, "name": "Key Vaults", "description": "This module deploys a Key Vault.", @@ -1459,7 +1459,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "1160382186999187070" + "templateHash": "5636875941217257115" }, "name": "Key Vault Secrets", "description": "This module deploys a Key Vault Secret.", @@ -1598,12 +1598,7 @@ "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", - "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", - "Key Vault Certificate User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba')]", "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", - "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", - "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", - "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", @@ -1748,7 +1743,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "7648379973162364092" + "templateHash": "2556304156587722941" }, "name": "Key Vault Keys", "description": "This module deploys a Key Vault Key.", @@ -1937,15 +1932,11 @@ "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", - "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", - "Key Vault Certificate User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba')]", "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", - "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", - "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", diff --git a/avm/res/key-vault/vault/secret/main.bicep b/avm/res/key-vault/vault/secret/main.bicep index 3dd83b47b6..0c4db8dfbe 100644 --- a/avm/res/key-vault/vault/secret/main.bicep +++ b/avm/res/key-vault/vault/secret/main.bicep @@ -37,30 +37,10 @@ var builtInRoleNames = { 'Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483' ) - 'Key Vault Certificates Officer': subscriptionResourceId( - 'Microsoft.Authorization/roleDefinitions', - 'a4417e6f-fecd-4de8-b567-7b0420556985' - ) - 'Key Vault Certificate User': subscriptionResourceId( - 'Microsoft.Authorization/roleDefinitions', - 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba' - ) 'Key Vault Contributor': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395' ) - 'Key Vault Crypto Officer': subscriptionResourceId( - 'Microsoft.Authorization/roleDefinitions', - '14b46e9e-c2b7-41b4-b07b-48a6ebf60603' - ) - 'Key Vault Crypto Service Encryption User': subscriptionResourceId( - 'Microsoft.Authorization/roleDefinitions', - 'e147488a-f6f5-4113-8e2d-b22465e65bf6' - ) - 'Key Vault Crypto User': subscriptionResourceId( - 'Microsoft.Authorization/roleDefinitions', - '12338af0-0e69-4776-bea7-57ae8d297424' - ) 'Key Vault Reader': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2' diff --git a/avm/res/key-vault/vault/secret/main.json b/avm/res/key-vault/vault/secret/main.json index b311bb75a0..d810cc8479 100644 --- a/avm/res/key-vault/vault/secret/main.json +++ b/avm/res/key-vault/vault/secret/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.27.1.19265", - "templateHash": "1160382186999187070" + "templateHash": "5636875941217257115" }, "name": "Key Vault Secrets", "description": "This module deploys a Key Vault Secret.", @@ -145,12 +145,7 @@ "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", - "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", - "Key Vault Certificate User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba')]", "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", - "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", - "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", - "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", diff --git a/avm/res/key-vault/vault/tests/e2e/waf-aligned/main.test.bicep b/avm/res/key-vault/vault/tests/e2e/waf-aligned/main.test.bicep index 1f8905b423..f75788795d 100644 --- a/avm/res/key-vault/vault/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/key-vault/vault/tests/e2e/waf-aligned/main.test.bicep @@ -80,8 +80,11 @@ module testDeployment '../../../main.bicep' = [ enableRbacAuthorization: true keys: [ { - attributesExp: 1725109032 - attributesNbf: 10000 + attributes: { + enabled: true + exp: 1702648632 + nbf: 10000 + } name: 'keyName' rotationPolicy: { attributes: { @@ -128,8 +131,11 @@ module testDeployment '../../../main.bicep' = [ ] secrets: [ { - attributesExp: 1702648632 - attributesNbf: 10000 + attributes: { + enabled: true + exp: 1702648632 + nbf: 10000 + } contentType: 'Something' name: 'secretName' value: 'secretValue' From fe5727c1b8738354533949681a64c2abf635f3fd Mon Sep 17 00:00:00 2001 From: Seif Bassem <38246040+sebassem@users.noreply.github.com> Date: Thu, 23 May 2024 17:36:46 +0300 Subject: [PATCH 14/18] feat: New Module `avm/ptn/finops-toolkit/finops-hub` (#1808) ## Description A pattern module for the [finops-hub](https://github.com/microsoft/finops-toolkit) ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.finops-toolkit.finops-hub](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.ptn.finops-toolkit.finops-hub.yml/badge.svg?branch=avm-finops-hub)](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.ptn.finops-toolkit.finops-hub.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/avm_module_issue.yml | 1 + .../avm.ptn.finops-toolkit.finops-hub.yml | 86 + avm/ptn/finops-toolkit/finops-hub/README.md | 242 + avm/ptn/finops-toolkit/finops-hub/main.bicep | 222 + avm/ptn/finops-toolkit/finops-hub/main.json | 11060 ++++++++++++++++ .../finops-hub/modules/dataFactory.bicep | 925 ++ .../finops-hub/modules/keyVault.bicep | 102 + .../modules/scripts/Copy-FileToAzureBlob.ps1 | 81 + .../modules/scripts/Remove-OldResources.ps1 | 18 + .../modules/scripts/Start-Triggers.ps1 | 41 + .../finops-hub/modules/storage.bicep | 164 + .../tests/e2e/defaults/main.test.bicep | 45 + .../finops-toolkit/finops-hub/version.json | 7 + ...ules.Rule.yaml => custom-rules.Rules.yaml} | 0 15 files changed, 12995 insertions(+) create mode 100644 .github/workflows/avm.ptn.finops-toolkit.finops-hub.yml create mode 100644 avm/ptn/finops-toolkit/finops-hub/README.md create mode 100644 avm/ptn/finops-toolkit/finops-hub/main.bicep create mode 100644 avm/ptn/finops-toolkit/finops-hub/main.json create mode 100644 avm/ptn/finops-toolkit/finops-hub/modules/dataFactory.bicep create mode 100644 avm/ptn/finops-toolkit/finops-hub/modules/keyVault.bicep create mode 100644 avm/ptn/finops-toolkit/finops-hub/modules/scripts/Copy-FileToAzureBlob.ps1 create mode 100644 avm/ptn/finops-toolkit/finops-hub/modules/scripts/Remove-OldResources.ps1 create mode 100644 avm/ptn/finops-toolkit/finops-hub/modules/scripts/Start-Triggers.ps1 create mode 100644 avm/ptn/finops-toolkit/finops-hub/modules/storage.bicep create mode 100644 avm/ptn/finops-toolkit/finops-hub/tests/e2e/defaults/main.test.bicep create mode 100644 avm/ptn/finops-toolkit/finops-hub/version.json rename avm/utilities/pipelines/staticValidation/psrule/.ps-rule/{custom-rules.Rule.yaml => custom-rules.Rules.yaml} (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3829d6ef26..7355262163 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -147,6 +147,7 @@ /avm/res/web/serverfarm/ @Azure/avm-res-web-serverfarm-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/web/site/ @Azure/avm-res-web-site-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/web/static-site/ @Azure/avm-res-web-staticsite-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/ptn/finops-toolkit/finops-hub/ @Azure/avm-ptn-finopstoolkit-finopshub-module-owners-bicep @Azure/avm-core-team-technical-bicep #/avm/ptn/avd-lza/insights/ @Azure/avm-ptn-avd-lza-insights-module-owners-bicep @Azure/avm-core-team-technical-bicep #/avm/ptn/avd-lza/management-plane/ @Azure/avm-ptn-avd-lza-managementplane-module-owners-bicep @Azure/avm-core-team-technical-bicep #/avm/ptn/avd-lza/networking/ @Azure/avm-ptn-avd-lza-networking-module-owners-bicep @Azure/avm-core-team-technical-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index ab3d0c7867..4817dc7792 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -45,6 +45,7 @@ body: # - "avm/ptn/avd-lza/management-plane" # - "avm/ptn/avd-lza/networking" # - "avm/ptn/avd-lza/session-hosts" + - "avm/ptn/finops-toolkit/finops-hub" - "avm/ptn/policy-insights/remediation" - "avm/ptn/security/security-center" - "avm/res/aad/domain-service" diff --git a/.github/workflows/avm.ptn.finops-toolkit.finops-hub.yml b/.github/workflows/avm.ptn.finops-toolkit.finops-hub.yml new file mode 100644 index 0000000000..54aa178c47 --- /dev/null +++ b/.github/workflows/avm.ptn.finops-toolkit.finops-hub.yml @@ -0,0 +1,86 @@ +name: "avm.ptn.finops-toolkit.finops-hub" + +on: + schedule: + - cron: "0 12 1/15 * *" # Bi-Weekly Test (on 1st & 15th of month) + workflow_dispatch: + inputs: + staticValidation: + type: boolean + description: "Execute static validation" + required: false + default: true + deploymentValidation: + type: boolean + description: "Execute deployment validation" + required: false + default: true + removeDeployment: + type: boolean + description: "Remove deployed module" + required: false + default: true + push: + branches: + - main + paths: + - ".github/actions/templates/avm-**" + - ".github/workflows/avm.template.module.yml" + - ".github/workflows/avm.ptn.finops-toolkit.finops-hub.yml" + - "avm/ptn/finops-toolkit/finops-hub/**" + - "avm/utilities/pipelines/**" + - "!avm/utilities/pipelines/platform/**" + - "!*/**/README.md" + +env: + modulePath: "avm/ptn/finops-toolkit/finops-hub" + workflowPath: ".github/workflows/avm.ptn.finops-toolkit.finops-hub.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-latest + name: "Initialize pipeline" + steps: + - name: "Checkout" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: "Set input parameters to output variables" + id: get-workflow-param + uses: ./.github/actions/templates/avm-getWorkflowInput + with: + workflowPath: "${{ env.workflowPath}}" + - name: "Get module test file paths" + id: get-module-test-file-paths + uses: ./.github/actions/templates/avm-getModuleTestFiles + with: + modulePath: "${{ env.modulePath }}" + outputs: + workflowInput: ${{ steps.get-workflow-param.outputs.workflowInput }} + moduleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.moduleTestFilePaths }} + psRuleModuleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.psRuleModuleTestFilePaths }} + modulePath: "${{ env.modulePath }}" + + ############################## + # Call reusable workflow # + ############################## + call-workflow-passing-data: + name: "Run" + permissions: + id-token: write # For OIDC + contents: write # For release tags + needs: + - job_initialize_pipeline + uses: ./.github/workflows/avm.template.module.yml + with: + workflowInput: "${{ needs.job_initialize_pipeline.outputs.workflowInput }}" + moduleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.moduleTestFilePaths }}" + psRuleModuleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.psRuleModuleTestFilePaths }}" + modulePath: "${{ needs.job_initialize_pipeline.outputs.modulePath}}" + secrets: inherit diff --git a/avm/ptn/finops-toolkit/finops-hub/README.md b/avm/ptn/finops-toolkit/finops-hub/README.md new file mode 100644 index 0000000000..1910a08edd --- /dev/null +++ b/avm/ptn/finops-toolkit/finops-hub/README.md @@ -0,0 +1,242 @@ +# Finops-hub `[Microsoft.finopstoolkit/finopshub]` + +This module deploys a Finops hub from the Finops toolkit. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `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.DataFactory/factories` | [2018-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.DataFactory/2018-06-01/factories) | +| `Microsoft.DataFactory/factories/datasets` | [2018-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.DataFactory/2018-06-01/factories/datasets) | +| `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/pipelines` | [2018-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.DataFactory/2018-06-01/factories/pipelines) | +| `Microsoft.DataFactory/factories/triggers` | [2018-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.DataFactory/2018-06-01/factories/triggers) | +| `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | +| `Microsoft.KeyVault/vaults` | [2022-07-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.KeyVault/2022-07-01/vaults) | +| `Microsoft.KeyVault/vaults/accessPolicies` | [2022-07-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.KeyVault/2022-07-01/vaults/accessPolicies) | +| `Microsoft.KeyVault/vaults/keys` | [2022-07-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.KeyVault/2022-07-01/vaults/keys) | +| `Microsoft.KeyVault/vaults/secrets` | [2022-07-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.KeyVault/2022-07-01/vaults/secrets) | +| `Microsoft.ManagedIdentity/userAssignedIdentities` | [2023-01-31](https://learn.microsoft.com/en-us/azure/templates/Microsoft.ManagedIdentity/2023-01-31/userAssignedIdentities) | +| `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) | +| `Microsoft.Resources/deploymentScripts` | [2023-08-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Resources/deploymentScripts) | +| `Microsoft.Storage/storageAccounts` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts) | +| `Microsoft.Storage/storageAccounts/blobServices` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices) | +| `Microsoft.Storage/storageAccounts/blobServices/containers` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices/containers) | +| `Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices/containers/immutabilityPolicies) | +| `Microsoft.Storage/storageAccounts/fileServices` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/fileServices) | +| `Microsoft.Storage/storageAccounts/fileServices/shares` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/fileServices/shares) | +| `Microsoft.Storage/storageAccounts/localUsers` | [2022-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-05-01/storageAccounts/localUsers) | +| `Microsoft.Storage/storageAccounts/managementPolicies` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/managementPolicies) | +| `Microsoft.Storage/storageAccounts/queueServices` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/queueServices) | +| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/queueServices/queues) | +| `Microsoft.Storage/storageAccounts/tableServices` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/tableServices) | +| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2021-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2021-09-01/storageAccounts/tableServices/tables) | + +## Usage examples + +The following section provides usage examples for the module, which were used to validate and deploy the module successfully. For a full reference, please review the module's test folder in its repository. + +>**Note**: Each example lists all the required parameters first, followed by the rest - each in alphabetical order. + +>**Note**: To reference the module, please use the following syntax `br/public:avm/ptn/finops-toolkit/finops-hub:`. + +- [Using only defaults](#example-1-using-only-defaults) + +### Example 1: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. + + +

+ +via Bicep module + +```bicep +module finopsHub 'br/public:avm/ptn/finops-toolkit/finops-hub:' = { + name: 'finopsHubDeployment' + params: { + // Required parameters + hubName: 'finops-hub-finmin' + // Non-required parameters + location: '' + } +} +``` + +
+

+ +

+ +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 + "hubName": { + "value": "finops-hub-finmin" + }, + // Non-required parameters + "location": { + "value": "" + } + } +} +``` + +
+

+ + +## Parameters + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`configContainer`](#parameter-configcontainer) | string | The name of the container used for configuration settings. | +| [`convertToParquet`](#parameter-converttoparquet) | bool | Indicates whether ingested data should be converted to Parquet. Default: true. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`exportContainer`](#parameter-exportcontainer) | string | The name of the container used for Cost Management exports. | +| [`exportScopes`](#parameter-exportscopes) | array | List of scope IDs to create exports for. | +| [`hubName`](#parameter-hubname) | string | Name of the hub. Used to ensure unique resource names. Default: "finops-hub". | +| [`ingestionContainer`](#parameter-ingestioncontainer) | string | The name of the container used for normalized data ingestion. | +| [`location`](#parameter-location) | string | Location for all Resources. | +| [`storageSku`](#parameter-storagesku) | string | Storage SKU to use. LRS = Lowest cost, ZRS = High availability. Note Standard SKUs are not available for Data Lake gen2 storage. Allowed: Premium_LRS, Premium_ZRS. Default: Premium_LRS. | +| [`tags`](#parameter-tags) | object | Tags to apply to all resources. We will also add the cm-resource-parent tag for improved cost roll-ups in Cost Management. | +| [`tagsByResource`](#parameter-tagsbyresource) | object | Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources. | + +### Parameter: `configContainer` + +The name of the container used for configuration settings. + +- Required: No +- Type: string +- Default: `'config'` + +### Parameter: `convertToParquet` + +Indicates whether ingested data should be converted to Parquet. Default: true. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `exportContainer` + +The name of the container used for Cost Management exports. + +- Required: No +- Type: string +- Default: `'exports'` + +### Parameter: `exportScopes` + +List of scope IDs to create exports for. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `hubName` + +Name of the hub. Used to ensure unique resource names. Default: "finops-hub". + +- Required: Yes +- Type: string + +### Parameter: `ingestionContainer` + +The name of the container used for normalized data ingestion. + +- Required: No +- Type: string +- Default: `'ingestion'` + +### Parameter: `location` + +Location for all Resources. + +- Required: No +- Type: string +- Default: `[resourceGroup().location]` + +### Parameter: `storageSku` + +Storage SKU to use. LRS = Lowest cost, ZRS = High availability. Note Standard SKUs are not available for Data Lake gen2 storage. Allowed: Premium_LRS, Premium_ZRS. Default: Premium_LRS. + +- Required: No +- Type: string +- Default: `'Premium_LRS'` +- Allowed: + ```Bicep + [ + 'Premium_LRS' + 'Premium_ZRS' + ] + ``` + +### Parameter: `tags` + +Tags to apply to all resources. We will also add the cm-resource-parent tag for improved cost roll-ups in Cost Management. + +- Required: No +- Type: object + +### Parameter: `tagsByResource` + +Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources. + +- Required: No +- Type: object +- Default: `{}` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `dataFactoryName` | string | Name of the Data Factory. | +| `location` | string | The location the resources wer deployed to. | +| `name` | string | The name of the resource group. | +| `resourceGroupName` | string | The resource group the finops hub was deployed into. | +| `storageAccountId` | string | The resource ID of the deployed storage account. | +| `storageAccountName` | string | Name of the storage account created for the hub instance. This must be used when connecting FinOps toolkit Power BI reports to your data. | +| `storageUrlForPowerBi` | string | URL to use when connecting custom Power BI reports to your data. | + +## Cross-referenced modules + +This section gives you an overview of all local-referenced module files (i.e., other modules that are referenced in this module) and all remote-referenced files (i.e., Bicep modules that are referenced from a Bicep Registry or Template Specs). + +| Reference | Type | +| :-- | :-- | +| `br/public:avm/res/key-vault/vault:0.5.1` | Remote reference | +| `br/public:avm/res/resources/deployment-script:0.2.0` | Remote reference | +| `br/public:avm/res/storage/storage-account:0.8.3` | Remote reference | + +## 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/ptn/finops-toolkit/finops-hub/main.bicep b/avm/ptn/finops-toolkit/finops-hub/main.bicep new file mode 100644 index 0000000000..2f2fb128ca --- /dev/null +++ b/avm/ptn/finops-toolkit/finops-hub/main.bicep @@ -0,0 +1,222 @@ +metadata name = 'Finops-hub' +metadata description = 'This module deploys a Finops hub from the Finops toolkit.' +metadata owner = 'Azure/module-maintainers' + +@description('Optional. Name of the hub. Used to ensure unique resource names. Default: "finops-hub".') +param hubName string + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@allowed([ + 'Premium_LRS' + 'Premium_ZRS' +]) +@description('Optional. Storage SKU to use. LRS = Lowest cost, ZRS = High availability. Note Standard SKUs are not available for Data Lake gen2 storage. Allowed: Premium_LRS, Premium_ZRS. Default: Premium_LRS.') +param storageSku string = 'Premium_LRS' + +@description('Optional. Tags to apply to all resources. We will also add the cm-resource-parent tag for improved cost roll-ups in Cost Management.') +param tags object? + +@description('Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources.') +param tagsByResource object = {} + +@description('Optional. List of scope IDs to create exports for.') +param exportScopes array = [] + +@description('Optional. The name of the container used for configuration settings.') +param configContainer string = 'config' + +@description('Optional. The name of the container used for Cost Management exports.') +param exportContainer string = 'exports' + +@description('Optional. The name of the container used for normalized data ingestion.') +param ingestionContainer string = 'ingestion' + +@description('Optional. Indicates whether ingested data should be converted to Parquet. Default: true.') +param convertToParquet bool = true + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +//------------------------------------------------------------------------------ +// Variables +//------------------------------------------------------------------------------ + +// Generate globally unique storage account name: 3-24 chars; lowercase letters/numbers only +var safeHubName = replace(replace(toLower(hubName), '-', ''), '_', '') +var storageAccountSuffix = uniqueSuffix +var storageAccountName = '${take(safeHubName, 24 - length(storageAccountSuffix))}${storageAccountSuffix}' +var ftkVersion = '0.3' + +// Add cm-resource-parent to group resources in Cost Management +var resourceTags = union( + tags ?? {}, + { + 'cm-resource-parent': '${resourceGroup().id}/providers/Microsoft.Cloud/hubs/${hubName}' + 'ftk-version': ftkVersion + 'ftk-tool': 'FinOps hubs' + } +) + +// Generate globally unique Data Factory name: 3-63 chars; letters, numbers, non-repeating dashes +var uniqueSuffix = uniqueString(hubName, resourceGroup().id) +var dataFactoryPrefix = '${replace(hubName, '_', '-')}-engine' +var dataFactorySuffix = '-${uniqueSuffix}' +var dataFactoryName = replace( + '${take(dataFactoryPrefix, 63 - length(dataFactorySuffix))}${dataFactorySuffix}', + '--', + '-' +) + +// The last segment of the telemetryId is used to identify this module +var telemetryId = '00f120b5-2007-6120-0000-40b000000000' + +//============================================================================== +// Resources +//============================================================================== + +//------------------------------------------------------------------------------ +// ADLSv2 storage account for staging and archive +//------------------------------------------------------------------------------ + +module storage 'modules/storage.bicep' = { + name: '${uniqueString(deployment().name, location)}-storage' + params: { + storageAccountName: storageAccountName + sku: storageSku + location: location + tags: resourceTags + tagsByResource: tagsByResource + exportScopes: exportScopes + configContainer: configContainer + exportContainer: exportContainer + ingestionContainer: ingestionContainer + ftkVersion: ftkVersion + } +} + +//------------------------------------------------------------------------------ +// Data Factory and pipelines +//------------------------------------------------------------------------------ +resource dataFactory 'Microsoft.DataFactory/factories@2018-06-01' = { + name: dataFactoryName + location: location + tags: union( + resourceTags, + contains(tagsByResource, 'Microsoft.DataFactory/factories') ? tagsByResource['Microsoft.DataFactory/factories'] : {} + ) + identity: { type: 'SystemAssigned' } + properties: any( + // Using any() to hide the error that gets surfaced because globalConfigurations is not in the ADF schema yet. + { + globalConfigurations: { + PipelineBillingEnabled: 'true' + } + } + ) +} +module dataFactoryResources 'modules/dataFactory.bicep' = { + name: '${uniqueString(deployment().name, location)}-dataFactoryResources' + params: { + dataFactoryName: dataFactoryName + convertToParquet: convertToParquet + keyVaultName: keyVault.outputs.name + storageAccountName: storage.outputs.name + exportContainerName: exportContainer + ingestionContainerName: ingestionContainer + location: location + tags: resourceTags + tagsByResource: tagsByResource + } +} + +//------------------------------------------------------------------------------ +// Key Vault for storing secrets +//------------------------------------------------------------------------------ + +module keyVault 'modules/keyVault.bicep' = { + name: '${uniqueString(deployment().name, location)}-keyVault' + params: { + hubName: hubName + uniqueSuffix: uniqueSuffix + location: location + tags: resourceTags + tagsByResource: tagsByResource + storageAccountName: storage.outputs.name + accessPolicies: [ + { + objectId: dataFactory.identity.principalId + tenantId: subscription().tenantId + permissions: { + secrets: [ + 'get' + ] + } + } + ] + } +} + +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = + if (enableTelemetry) { + name: '46d3xbcp.ptn.finopstoolkit-finopshub.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + outputs: { + telemetry: { + type: 'String' + value: 'For more information, see https://aka.ms/avm/TelemetryInfo' + } + } + } + } + } + +resource defaultTelemetry 'Microsoft.Resources/deployments@2023-07-01' = + if (enableTelemetry) { + name: 'pid-${telemetryId}-${uniqueString(deployment().name, location)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + metadata: { + _generator: { + name: 'FinOps toolkit' + version: '0.3' + } + } + resources: [] + } + } + } + +//============================================================================== +// Outputs +//============================================================================== + +@description('The name of the resource group.') +output name string = hubName + +@description('The location the resources wer deployed to.') +output location string = location + +@description('Name of the Data Factory.') +output dataFactoryName string = dataFactoryName + +@description('The resource group the finops hub was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The resource ID of the deployed storage account.') +output storageAccountId string = storage.outputs.resourceId + +@description('Name of the storage account created for the hub instance. This must be used when connecting FinOps toolkit Power BI reports to your data.') +output storageAccountName string = storage.outputs.name + +@description('URL to use when connecting custom Power BI reports to your data.') +output storageUrlForPowerBi string = 'https://${storage.outputs.name}.dfs.${environment().suffixes.storage}/${ingestionContainer}' diff --git a/avm/ptn/finops-toolkit/finops-hub/main.json b/avm/ptn/finops-toolkit/finops-hub/main.json new file mode 100644 index 0000000000..f0171cd213 --- /dev/null +++ b/avm/ptn/finops-toolkit/finops-hub/main.json @@ -0,0 +1,11060 @@ +{ + "$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.26.170.59819", + "templateHash": "13888331902588628493" + }, + "name": "Finops-hub", + "description": "This module deploys a Finops hub from the Finops toolkit.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "hubName": { + "type": "string", + "metadata": { + "description": "Optional. Name of the hub. Used to ensure unique resource names. Default: \"finops-hub\"." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "storageSku": { + "type": "string", + "defaultValue": "Premium_LRS", + "allowedValues": [ + "Premium_LRS", + "Premium_ZRS" + ], + "metadata": { + "description": "Optional. Storage SKU to use. LRS = Lowest cost, ZRS = High availability. Note Standard SKUs are not available for Data Lake gen2 storage. Allowed: Premium_LRS, Premium_ZRS. Default: Premium_LRS." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to apply to all resources. We will also add the cm-resource-parent tag for improved cost roll-ups in Cost Management." + } + }, + "tagsByResource": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources." + } + }, + "exportScopes": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. List of scope IDs to create exports for." + } + }, + "configContainer": { + "type": "string", + "defaultValue": "config", + "metadata": { + "description": "Optional. The name of the container used for configuration settings." + } + }, + "exportContainer": { + "type": "string", + "defaultValue": "exports", + "metadata": { + "description": "Optional. The name of the container used for Cost Management exports." + } + }, + "ingestionContainer": { + "type": "string", + "defaultValue": "ingestion", + "metadata": { + "description": "Optional. The name of the container used for normalized data ingestion." + } + }, + "convertToParquet": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether ingested data should be converted to Parquet. Default: true." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "safeHubName": "[replace(replace(toLower(parameters('hubName')), '-', ''), '_', '')]", + "storageAccountSuffix": "[variables('uniqueSuffix')]", + "storageAccountName": "[format('{0}{1}', take(variables('safeHubName'), sub(24, length(variables('storageAccountSuffix')))), variables('storageAccountSuffix'))]", + "ftkVersion": "0.3", + "resourceTags": "[union(coalesce(parameters('tags'), createObject()), createObject('cm-resource-parent', format('{0}/providers/Microsoft.Cloud/hubs/{1}', resourceGroup().id, parameters('hubName')), 'ftk-version', variables('ftkVersion'), 'ftk-tool', 'FinOps hubs'))]", + "uniqueSuffix": "[uniqueString(parameters('hubName'), resourceGroup().id)]", + "dataFactoryPrefix": "[format('{0}-engine', replace(parameters('hubName'), '_', '-'))]", + "dataFactorySuffix": "[format('-{0}', variables('uniqueSuffix'))]", + "dataFactoryName": "[replace(format('{0}{1}', take(variables('dataFactoryPrefix'), sub(63, length(variables('dataFactorySuffix')))), variables('dataFactorySuffix')), '--', '-')]", + "telemetryId": "00f120b5-2007-6120-0000-40b000000000" + }, + "resources": { + "dataFactory": { + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[variables('dataFactoryName')]", + "location": "[parameters('location')]", + "tags": "[union(variables('resourceTags'), if(contains(parameters('tagsByResource'), 'Microsoft.DataFactory/factories'), parameters('tagsByResource')['Microsoft.DataFactory/factories'], createObject()))]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" + } + } + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.ptn.finopstoolkit-finopshub.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "defaultTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('pid-{0}-{1}', variables('telemetryId'), uniqueString(deployment().name, parameters('location')))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "FinOps toolkit", + "version": "0.3" + } + }, + "resources": [] + } + } + }, + "storage": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-storage', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[variables('storageAccountName')]" + }, + "sku": { + "value": "[parameters('storageSku')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('resourceTags')]" + }, + "tagsByResource": { + "value": "[parameters('tagsByResource')]" + }, + "exportScopes": { + "value": "[parameters('exportScopes')]" + }, + "configContainer": { + "value": "[parameters('configContainer')]" + }, + "exportContainer": { + "value": "[parameters('exportContainer')]" + }, + "ingestionContainer": { + "value": "[parameters('ingestionContainer')]" + }, + "ftkVersion": { + "value": "[variables('ftkVersion')]" + } + }, + "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.26.170.59819", + "templateHash": "4383879433834970993" + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Azure location where all resources should be created. See https://aka.ms/azureregions. Default: (resource group location)." + } + }, + "sku": { + "type": "string", + "defaultValue": "Premium_LRS", + "allowedValues": [ + "Premium_LRS", + "Premium_ZRS" + ], + "metadata": { + "description": "Optional. Storage SKU to use. LRS = Lowest cost, ZRS = High availability. Note Standard SKUs are not available for Data Lake gen2 storage. Allowed: Premium_LRS, Premium_ZRS. Default: Premium_LRS." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to apply to all resources. We will also add the cm-resource-parent tag for improved cost roll-ups in Cost Management." + } + }, + "tagsByResource": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources." + } + }, + "exportScopes": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. List of scope IDs to create exports for." + } + }, + "configContainer": { + "type": "string", + "defaultValue": "config", + "metadata": { + "description": "The name of the container used for configuration settings." + } + }, + "exportContainer": { + "type": "string", + "defaultValue": "exports", + "metadata": { + "description": "The name of the container used for Cost Management exports." + } + }, + "ingestionContainer": { + "type": "string", + "defaultValue": "ingestion", + "metadata": { + "description": "The name of the container used for normalized data ingestion." + } + }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the storage account." + } + }, + "ftkVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The version of the FTK to use." + } + } + }, + "variables": { + "$fxv#0": "Write-Output 'Updating settings.json file...'\nWrite-Output \" Storage account: $env:storageAccountName\"\nWrite-Output \" Container: $env:containerName\"\n\n$validateScopes = { $_.Length -gt 45 }\n\n# Initialize variables\n$fileName = 'settings.json'\n$filePath = Join-Path -Path . -ChildPath $fileName\n$newScopes = $env:exportScopes.Split('|') | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Download existing settings, if they exist\n$blob = Get-AzStorageBlobContent @storageContext -Blob $fileName -Destination $filePath -Force\nif ($blob) {\n Write-Output 'Existing settings.json file found. Updating...'\n $text = Get-Content $filePath -Raw\n Write-Output '---------'\n Write-Output $text\n Write-Output '---------'\n $json = $text | ConvertFrom-Json\n\n # Rename exportScopes to scopes + convert to object array\n if ($json.exportScopes) {\n Write-Output ' Updating exportScopes...'\n if ($json.exportScopes[0] -is [string]) {\n Write-Output ' Converting string array to object array...'\n $json.exportScopes = $json.exportScopes | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\n if (-not ($json.exportScopes -is [array])) {\n Write-Output ' Converting single object to object array...'\n $json.exportScopes = @($json.exportScopes)\n }\n }\n\n Write-Output \" Renaming to 'scopes'...\"\n $json | Add-Member -MemberType NoteProperty -Name scopes -Value $json.exportScopes\n $json.PSObject.Properties.Remove('exportScopes')\n }\n}\n\n# Set default if not found\nif (!$json) {\n Write-Output 'No existing settings.json file found. Creating new file...'\n $json = [ordered]@{\n '$schema' = 'https://aka.ms/finops/hubs/settings-schema'\n type = 'HubInstance'\n version = ''\n learnMore = 'https://aka.ms/finops/hubs'\n scopes = @()\n }\n}\n\n# Updating settings\nWrite-Output \"Updating version to $env:ftkVersion...\"\n$json.version = $env:ftkVersion\nif ($newScopes) {\n Write-Output \"Merging $($newScopes.Count) scopes...\"\n $json.scopes = Compare-Object -ReferenceObject $json.scopes -DifferenceObject $newScopes -Property scope -PassThru -IncludeEqual\n\n # Remove the SideIndicator property from the Compare-Object output\n $json.scopes | ForEach-Object { $_.PSObject.Properties.Remove('SideIndicator') } | ConvertTo-Json\n\n if (-not ($json.scopes -is [array])) {\n $json.scopes = @($json.scopes)\n }\n Write-Output \"$($json.scopes.Count) scopes found.\"\n}\n$text = $json | ConvertTo-Json\nWrite-Output '---------'\nWrite-Output $text\nWrite-Output '---------'\n$text | Out-File $filePath\n\n# Upload new/updated settings\nWrite-Output 'Uploading settings.json file...'\nSet-AzStorageBlobContent @storageContext -File $filePath -Force\n" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_blobManager', parameters('storageAccountName'))]", + "tags": "[union(coalesce(parameters('tags'), createObject()), if(contains(parameters('tagsByResource'), 'Microsoft.ManagedIdentity/userAssignedIdentities'), parameters('tagsByResource')['Microsoft.ManagedIdentity/userAssignedIdentities'], createObject()))]", + "location": "[parameters('location')]" + }, + "storageAccount": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-storage', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('storageAccountName')]" + }, + "skuName": { + "value": "[parameters('sku')]" + }, + "kind": { + "value": "BlockBlobStorage" + }, + "tags": { + "value": "[union(coalesce(parameters('tags'), createObject()), if(contains(parameters('tagsByResource'), 'Microsoft.Storage/storageAccounts'), parameters('tagsByResource')['Microsoft.Storage/storageAccounts'], createObject()))]" + }, + "supportsHttpsTrafficOnly": { + "value": true + }, + "minimumTlsVersion": { + "value": "TLS1_2" + }, + "allowBlobPublicAccess": { + "value": false + }, + "publicNetworkAccess": { + "value": "Enabled" + }, + "enableHierarchicalNamespace": { + "value": true + }, + "blobServices": { + "value": { + "containers": [ + { + "name": "[parameters('configContainer')]", + "publicAccess": "None", + "metadata": {} + }, + { + "name": "[parameters('exportContainer')]", + "publicAccess": "None", + "metadata": {} + }, + { + "name": "[parameters('ingestionContainer')]", + "publicAccess": "None", + "metadata": {} + } + ] + } + }, + "roleAssignments": { + "value": [ + { + "roleDefinitionIdOrName": "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + { + "roleDefinitionIdOrName": "e40ec5ca-96e0-45a2-b4ff-59039f2c2b59", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + } + ] + }, + "networkAcls": { + "value": { + "bypass": "AzureServices", + "defaultAction": "Allow" + } + } + }, + "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.26.170.59819", + "templateHash": "16887137065839172279" + }, + "name": "Storage Accounts", + "description": "This module deploys a Storage Account.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "managedIdentitiesType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + }, + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource." + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "networkAclsType": { + "type": "object", + "properties": { + "resourceAccessRules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "metadata": { + "description": "Required. The ID of the tenant in which the resource resides in." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the target service. Can also contain a wildcard, if multiple services e.g. in a resource group should be included." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Sets the resource access rules. Array entries must consist of \"tenantId\" and \"resourceId\" fields only." + } + }, + "bypass": { + "type": "string", + "allowedValues": [ + "AzureServices", + "AzureServices, Logging", + "AzureServices, Logging, Metrics", + "AzureServices, Metrics", + "Logging", + "Logging, Metrics", + "Metrics", + "None" + ], + "metadata": { + "description": "Required. Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Possible values are any combination of Logging,Metrics,AzureServices (For example, \"Logging, Metrics\"), or None to bypass none of those traffics." + } + }, + "virtualNetworkRules": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Sets the virtual network rules." + } + }, + "ipRules": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Sets the IP ACL rules." + } + }, + "defaultAction": { + "type": "string", + "allowedValues": [ + "Allow", + "Deny" + ], + "metadata": { + "description": "Required. Specifies the default action of allow or deny when no other rules match." + } + } + } + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "manualConnectionRequestMessage": { + "type": "string", + "nullable": true, + "maxLength": 140, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with the manual connection request." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint ip address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private ip addresses of the private endpoint." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private ip address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + }, + "customerManagedKeyType": { + "type": "object", + "properties": { + "keyVaultResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of a key vault to reference a customer managed key for encryption from." + } + }, + "keyName": { + "type": "string", + "metadata": { + "description": "Required. The name of the customer managed key to use for encryption." + } + }, + "keyVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The version of the customer managed key to reference for encryption. If not provided, using 'latest'." + } + }, + "userAssignedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User assigned identity to use when fetching the customer managed key. If used must also be specified in `managedIdentities.userAssignedResourceIds`. Required if no system assigned identity is available for use." + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Name of the Storage Account. Must be lower-case." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "kind": { + "type": "string", + "defaultValue": "StorageV2", + "allowedValues": [ + "Storage", + "StorageV2", + "BlobStorage", + "FileStorage", + "BlockBlobStorage" + ], + "metadata": { + "description": "Optional. Type of Storage Account to create." + } + }, + "skuName": { + "type": "string", + "defaultValue": "Standard_GRS", + "allowedValues": [ + "Standard_LRS", + "Standard_GRS", + "Standard_RAGRS", + "Standard_ZRS", + "Premium_LRS", + "Premium_ZRS", + "Standard_GZRS", + "Standard_RAGZRS" + ], + "metadata": { + "description": "Optional. Storage Account Sku Name." + } + }, + "accessTier": { + "type": "string", + "defaultValue": "Hot", + "allowedValues": [ + "Premium", + "Hot", + "Cool" + ], + "metadata": { + "description": "Conditional. Required if the Storage Account kind is set to BlobStorage. The access tier is used for billing. The \"Premium\" access tier is the default value for premium block blobs storage account type and it cannot be changed for the premium block blobs storage account type." + } + }, + "largeFileSharesState": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "metadata": { + "description": "Optional. Allow large file shares if sets to 'Enabled'. It cannot be disabled once it is enabled. Only supported on locally redundant and zone redundant file shares. It cannot be set on FileStorage storage accounts (storage accounts for premium file shares)." + } + }, + "azureFilesIdentityBasedAuthentication": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Provides the identity based authentication settings for Azure Files." + } + }, + "defaultToOAuthAuthentication": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. A boolean flag which indicates whether the default authentication is OAuth or not." + } + }, + "allowSharedKeyAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether the storage account permits requests to be authorized with the account access key via Shared Key. If false, then all requests, including shared access signatures, must be authorized with Azure Active Directory (Azure AD). The default value is null, which is equivalent to true." + } + }, + "privateEndpoints": { + "$ref": "#/definitions/privateEndpointType", + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "managementPolicyRules": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The Storage Account ManagementPolicies Rules." + } + }, + "networkAcls": { + "$ref": "#/definitions/networkAclsType", + "defaultValue": { + "bypass": "AzureServices", + "defaultAction": "Deny" + }, + "metadata": { + "description": "Required. Networks ACLs, this value contains IPs to whitelist and/or Subnet information. If in use, bypass needs to be supplied. For security reasons, it is recommended to set the DefaultAction Deny." + } + }, + "requireInfrastructureEncryption": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. A Boolean indicating whether or not the service applies a secondary layer of encryption with platform managed keys for data at rest. For security reasons, it is recommended to set it to true." + } + }, + "allowCrossTenantReplication": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Allow or disallow cross AAD tenant object replication." + } + }, + "customDomainName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Sets the custom domain name assigned to the storage account. Name is the CNAME source." + } + }, + "customDomainUseSubDomainName": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether indirect CName validation is enabled. This should only be set on updates." + } + }, + "dnsEndpointType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "AzureDnsZone", + "Standard" + ], + "metadata": { + "description": "Optional. Allows you to specify the type of endpoint. Set this to AzureDNSZone to create a large number of accounts in a single subscription, which creates accounts in an Azure DNS Zone and the endpoint URL will have an alphanumeric DNS Zone identifier." + } + }, + "blobServices": { + "type": "object", + "defaultValue": "[if(not(equals(parameters('kind'), 'FileStorage')), createObject('containerDeleteRetentionPolicyEnabled', true(), 'containerDeleteRetentionPolicyDays', 7, 'deleteRetentionPolicyEnabled', true(), 'deleteRetentionPolicyDays', 6), createObject())]", + "metadata": { + "description": "Optional. Blob service and containers to deploy." + } + }, + "fileServices": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. File service and shares to deploy." + } + }, + "queueServices": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Queue service and queues to create." + } + }, + "tableServices": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Table service and tables to create." + } + }, + "allowBlobPublicAccess": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether public access is enabled for all blobs or containers in the storage account. For security reasons, it is recommended to set it to false." + } + }, + "minimumTlsVersion": { + "type": "string", + "defaultValue": "TLS1_2", + "allowedValues": [ + "TLS1_0", + "TLS1_1", + "TLS1_2" + ], + "metadata": { + "description": "Optional. Set the minimum TLS version on request to storage." + } + }, + "enableHierarchicalNamespace": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Conditional. If true, enables Hierarchical Namespace for the storage account. Required if enableSftp or enableNfsV3 is set to true." + } + }, + "enableSftp": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, enables Secure File Transfer Protocol for the storage account. Requires enableHierarchicalNamespace to be true." + } + }, + "localUsers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Local users to deploy for SFTP authentication." + } + }, + "isLocalUserEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enables local users feature, if set to true." + } + }, + "enableNfsV3": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, enables NFS 3.0 support for the storage account. Requires enableHierarchicalNamespace to be true." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "allowedCopyScope": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "AAD", + "PrivateLink" + ], + "metadata": { + "description": "Optional. Restrict copy to and from Storage Accounts within an AAD tenant or with Private Links to the same VNet." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "", + "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 and networkAcls are not set." + } + }, + "supportsHttpsTrafficOnly": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Allows HTTPS traffic only to storage service if sets to true." + } + }, + "customerManagedKey": { + "$ref": "#/definitions/customerManagedKeyType", + "metadata": { + "description": "Optional. The customer managed key definition." + } + }, + "sasExpirationPeriod": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The SAS expiration period. DD.HH:MM:SS." + } + } + }, + "variables": { + "supportsBlobService": "[or(or(or(equals(parameters('kind'), 'BlockBlobStorage'), equals(parameters('kind'), 'BlobStorage')), equals(parameters('kind'), 'StorageV2')), equals(parameters('kind'), 'Storage'))]", + "supportsFileService": "[or(or(equals(parameters('kind'), 'FileStorage'), equals(parameters('kind'), 'StorageV2')), equals(parameters('kind'), 'Storage'))]", + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Blob Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "Storage Blob Data Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]", + "Storage Blob Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')]", + "Storage Blob Delegator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db58b8e5-c6ad-4a2a-8342-4190687cbf4a')]", + "Storage File Data SMB Share Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb')]", + "Storage File Data SMB Share Elevated Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a7264617-510b-434b-a828-9731dc254ea7')]", + "Storage File Data SMB Share Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'aba4ae5f-2193-4029-9191-0cb91df5e314')]", + "Storage Queue Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88')]", + "Storage Queue Data Message Processor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8a0f0c08-91a1-4084-bc3d-661d67233fed')]", + "Storage Queue Data Message Sender": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a')]", + "Storage Queue Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '19e7f393-937e-4f77-808e-94535e297925')]", + "Storage Table Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3')]", + "Storage Table Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76199698-9eea-4c19-bc75-cec21354c6b6')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "cMKKeyVault::cMKKey": { + "condition": "[and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName')))))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2023-02-01", + "subscriptionId": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '////'), '/')[4]]", + "name": "[format('{0}/{1}', last(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), 'dummyVault'), '/')), coalesce(tryGet(parameters('customerManagedKey'), 'keyName'), 'dummyKey'))]", + "dependsOn": [ + "cMKKeyVault" + ] + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.storage-storageaccount.{0}.{1}', replace('0.8.3', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "cMKKeyVault": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId')))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "subscriptionId": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '////'), '/')[4]]", + "name": "[last(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), 'dummyVault'), '/'))]" + }, + "cMKUserAssignedIdentity": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId')))]", + "existing": true, + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "subscriptionId": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '////'), '/')[4]]", + "name": "[last(split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), 'dummyMsi'), '/'))]" + }, + "storageAccount": { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "kind": "[parameters('kind')]", + "sku": { + "name": "[parameters('skuName')]" + }, + "identity": "[variables('identity')]", + "tags": "[parameters('tags')]", + "properties": { + "allowSharedKeyAccess": "[parameters('allowSharedKeyAccess')]", + "defaultToOAuthAuthentication": "[parameters('defaultToOAuthAuthentication')]", + "allowCrossTenantReplication": "[parameters('allowCrossTenantReplication')]", + "allowedCopyScope": "[if(not(empty(parameters('allowedCopyScope'))), parameters('allowedCopyScope'), null())]", + "customDomain": { + "name": "[parameters('customDomainName')]", + "useSubDomainName": "[parameters('customDomainUseSubDomainName')]" + }, + "dnsEndpointType": "[if(not(empty(parameters('dnsEndpointType'))), parameters('dnsEndpointType'), null())]", + "isLocalUserEnabled": "[parameters('isLocalUserEnabled')]", + "encryption": "[union(createObject('keySource', if(not(empty(parameters('customerManagedKey'))), 'Microsoft.Keyvault', 'Microsoft.Storage'), 'services', createObject('blob', if(variables('supportsBlobService'), createObject('enabled', true()), null()), 'file', if(variables('supportsFileService'), createObject('enabled', true()), null()), 'table', createObject('enabled', true()), 'queue', createObject('enabled', true())), 'keyvaultproperties', if(not(empty(parameters('customerManagedKey'))), createObject('keyname', parameters('customerManagedKey').keyName, 'keyvaulturi', reference('cMKKeyVault').vaultUri, 'keyversion', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'keyVersion'), ''))), parameters('customerManagedKey').keyVersion, last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/')))), null()), 'identity', createObject('userAssignedIdentity', if(not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'))), 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()))), if(parameters('requireInfrastructureEncryption'), createObject('requireInfrastructureEncryption', if(not(equals(parameters('kind'), 'Storage')), parameters('requireInfrastructureEncryption'), null())), createObject()))]", + "accessTier": "[if(and(not(equals(parameters('kind'), 'Storage')), not(equals(parameters('kind'), 'BlockBlobStorage'))), parameters('accessTier'), null())]", + "sasPolicy": "[if(not(empty(parameters('sasExpirationPeriod'))), createObject('expirationAction', 'Log', 'sasExpirationPeriod', parameters('sasExpirationPeriod')), null())]", + "supportsHttpsTrafficOnly": "[parameters('supportsHttpsTrafficOnly')]", + "isHnsEnabled": "[if(parameters('enableHierarchicalNamespace'), parameters('enableHierarchicalNamespace'), null())]", + "isSftpEnabled": "[parameters('enableSftp')]", + "isNfsV3Enabled": "[if(parameters('enableNfsV3'), parameters('enableNfsV3'), '')]", + "largeFileSharesState": "[if(or(equals(parameters('skuName'), 'Standard_LRS'), equals(parameters('skuName'), 'Standard_ZRS')), parameters('largeFileSharesState'), null())]", + "minimumTlsVersion": "[parameters('minimumTlsVersion')]", + "networkAcls": "[if(not(empty(parameters('networkAcls'))), createObject('resourceAccessRules', tryGet(parameters('networkAcls'), 'resourceAccessRules'), 'bypass', tryGet(parameters('networkAcls'), 'bypass'), 'defaultAction', tryGet(parameters('networkAcls'), 'defaultAction'), 'virtualNetworkRules', tryGet(parameters('networkAcls'), 'virtualNetworkRules'), 'ipRules', tryGet(parameters('networkAcls'), 'ipRules')), null())]", + "allowBlobPublicAccess": "[parameters('allowBlobPublicAccess')]", + "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(not(empty(parameters('privateEndpoints'))), empty(parameters('networkAcls'))), 'Disabled', null()))]", + "azureFilesIdentityBasedAuthentication": "[if(not(empty(parameters('azureFilesIdentityBasedAuthentication'))), parameters('azureFilesIdentityBasedAuthentication'), null())]" + }, + "dependsOn": [ + "cMKKeyVault", + "cMKUserAssignedIdentity" + ] + }, + "storageAccount_diagnosticSettings": { + "copy": { + "name": "storageAccount_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[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')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_roleAssignments": { + "copy": { + "name": "storageAccount_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_privateEndpoints": { + "copy": { + "name": "storageAccount_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-StorageAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Storage/storageAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Storage/storageAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]", + "subnetResourceId": { + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableTelemetry": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableTelemetry'), parameters('enableTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" + }, + "lock": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } + }, + "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.24.24.22086", + "templateHash": "2592884001616184297" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "ipConfigurationsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true + }, + "manualPrivateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "privateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "customDnsConfigType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the private endpoint resource to create." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "ipConfigurations": { + "$ref": "#/definitions/ipConfigurationsType", + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if `privateDnsZoneResourceIds` were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "customDnsConfigs": { + "$ref": "#/definitions/customDnsConfigType", + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "$ref": "#/definitions/manualPrivateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource." + } + }, + "privateLinkServiceConnections": { + "$ref": "#/definitions/privateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]", + "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]", + "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]", + "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.4.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "applicationSecurityGroups", + "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]" + } + } + ], + "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]", + "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]", + "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]", + "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]", + "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]", + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + } + }, + "privateEndpoint_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_roleAssignments": { + "copy": { + "name": "privateEndpoint_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneResourceIds')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('privateDnsZoneGroupName'), 'default')]" + }, + "privateDNSResourceIds": { + "value": "[coalesce(parameters('privateDnsZoneResourceIds'), createArray())]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.24.24.22086", + "templateHash": "9321937464667207030" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDNSResourceIds": { + "type": "array", + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone resource IDs. A DNS zone group can support up to 5 DNS zones." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the private DNS zone group." + } + } + }, + "variables": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDNSResourceIds'))]", + "input": { + "name": "[last(split(parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "resources": [ + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigs')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint DNS zone group." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint DNS zone group." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint DNS zone group was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateEndpoint" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('privateEndpoint', '2023-04-01', 'full').location]" + }, + "groupId": { + "type": "string", + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[if(not(empty(reference('privateEndpoint').manualPrivateLinkServiceConnections)), reference('privateEndpoint').manualPrivateLinkServiceConnections[0].properties.groupIds[0], reference('privateEndpoint').privateLinkServiceConnections[0].properties.groupIds[0])]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_managementPolicies": { + "condition": "[not(empty(coalesce(parameters('managementPolicyRules'), createArray())))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-ManagementPolicies', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "rules": { + "value": "[coalesce(parameters('managementPolicyRules'), createArray())]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "4153955640795225346" + }, + "name": "Storage Account Management Policies", + "description": "This module deploys a Storage Account Management Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "rules": { + "type": "array", + "metadata": { + "description": "Required. The Storage Account ManagementPolicies Rules." + } + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts/managementPolicies", + "apiVersion": "2023-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "properties": { + "policy": { + "rules": "[parameters('rules')]" + } + } + } + ], + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed management policy." + }, + "value": "default" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed management policy." + }, + "value": "default" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed management policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount", + "storageAccount_blobServices" + ] + }, + "storageAccount_localUsers": { + "copy": { + "name": "storageAccount_localUsers", + "count": "[length(parameters('localUsers'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-LocalUsers-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[parameters('localUsers')[copyIndex()].name]" + }, + "hasSshKey": { + "value": "[parameters('localUsers')[copyIndex()].hasSshKey]" + }, + "hasSshPassword": { + "value": "[parameters('localUsers')[copyIndex()].hasSshPassword]" + }, + "permissionScopes": { + "value": "[parameters('localUsers')[copyIndex()].permissionScopes]" + }, + "hasSharedKey": { + "value": "[tryGet(parameters('localUsers')[copyIndex()], 'hasSharedKey')]" + }, + "homeDirectory": { + "value": "[tryGet(parameters('localUsers')[copyIndex()], 'homeDirectory')]" + }, + "sshAuthorizedKeys": { + "value": "[tryGet(parameters('localUsers')[copyIndex()], 'sshAuthorizedKeys')]" + } + }, + "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.26.170.59819", + "templateHash": "14593329616022153178" + }, + "name": "Storage Account Local Users", + "description": "This module deploys a Storage Account Local User, which is used for SFTP authentication.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "sshAuthorizedKeysType": { + "type": "secureObject", + "properties": { + "secureList": { + "type": "array", + "items": { + "type": "object", + "properties": { + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Description used to store the function/usage of the key." + } + }, + "key": { + "type": "string", + "metadata": { + "description": "Required. SSH public key base64 encoded. The format should be: '{keyType} {keyData}', e.g. ssh-rsa AAAABBBB." + } + } + } + }, + "metadata": { + "description": "Optional. The list of SSH authorized keys." + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the local user used for SFTP Authentication." + } + }, + "hasSharedKey": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether shared key exists. Set it to false to remove existing shared key." + } + }, + "hasSshKey": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether SSH key exists. Set it to false to remove existing SSH key." + } + }, + "hasSshPassword": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether SSH password exists. Set it to false to remove existing SSH password." + } + }, + "homeDirectory": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The local user home directory." + } + }, + "permissionScopes": { + "type": "array", + "metadata": { + "description": "Required. The permission scopes of the local user." + } + }, + "sshAuthorizedKeys": { + "$ref": "#/definitions/sshAuthorizedKeysType", + "metadata": { + "description": "Optional. The local user SSH authorized keys for SFTP." + } + } + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-09-01", + "name": "[parameters('storageAccountName')]" + }, + "localUsers": { + "type": "Microsoft.Storage/storageAccounts/localUsers", + "apiVersion": "2022-05-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", + "properties": { + "hasSharedKey": "[parameters('hasSharedKey')]", + "hasSshKey": "[parameters('hasSshKey')]", + "hasSshPassword": "[parameters('hasSshPassword')]", + "homeDirectory": "[parameters('homeDirectory')]", + "permissionScopes": "[parameters('permissionScopes')]", + "sshAuthorizedKeys": "[tryGet(parameters('sshAuthorizedKeys'), 'secureList')]" + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed local user." + }, + "value": "[parameters('name')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed local user." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed local user." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/localUsers', parameters('storageAccountName'), parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_blobServices": { + "condition": "[not(empty(parameters('blobServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-BlobServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "containers": { + "value": "[tryGet(parameters('blobServices'), 'containers')]" + }, + "automaticSnapshotPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'automaticSnapshotPolicyEnabled')]" + }, + "changeFeedEnabled": { + "value": "[tryGet(parameters('blobServices'), 'changeFeedEnabled')]" + }, + "changeFeedRetentionInDays": { + "value": "[tryGet(parameters('blobServices'), 'changeFeedRetentionInDays')]" + }, + "containerDeleteRetentionPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyEnabled')]" + }, + "containerDeleteRetentionPolicyDays": { + "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyDays')]" + }, + "containerDeleteRetentionPolicyAllowPermanentDelete": { + "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyAllowPermanentDelete')]" + }, + "corsRules": { + "value": "[tryGet(parameters('blobServices'), 'corsRules')]" + }, + "defaultServiceVersion": { + "value": "[tryGet(parameters('blobServices'), 'defaultServiceVersion')]" + }, + "deleteRetentionPolicyAllowPermanentDelete": { + "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyAllowPermanentDelete')]" + }, + "deleteRetentionPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyEnabled')]" + }, + "deleteRetentionPolicyDays": { + "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyDays')]" + }, + "isVersioningEnabled": { + "value": "[tryGet(parameters('blobServices'), 'isVersioningEnabled')]" + }, + "lastAccessTimeTrackingPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'lastAccessTimeTrackingPolicyEnabled')]" + }, + "restorePolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'restorePolicyEnabled')]" + }, + "restorePolicyDays": { + "value": "[tryGet(parameters('blobServices'), 'restorePolicyDays')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('blobServices'), 'diagnosticSettings')]" + } + }, + "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.26.170.59819", + "templateHash": "7278814029590745003" + }, + "name": "Storage Account blob Services", + "description": "This module deploys a Storage Account Blob Service.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "automaticSnapshotPolicyEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Automatic Snapshot is enabled if set to true." + } + }, + "changeFeedEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The blob service properties for change feed events. Indicates whether change feed event logging is enabled for the Blob service." + } + }, + "changeFeedRetentionInDays": { + "type": "int", + "nullable": true, + "minValue": 0, + "maxValue": 146000, + "metadata": { + "description": "Optional. Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. A \"0\" value indicates an infinite retention of the change feed." + } + }, + "containerDeleteRetentionPolicyEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. The blob service properties for container soft delete. Indicates whether DeleteRetentionPolicy is enabled." + } + }, + "containerDeleteRetentionPolicyDays": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 365, + "metadata": { + "description": "Optional. Indicates the number of days that the deleted item should be retained." + } + }, + "containerDeleteRetentionPolicyAllowPermanentDelete": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share." + } + }, + "corsRules": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Specifies CORS rules for the Blob service. You can include up to five CorsRule elements in the request. If no CorsRule elements are included in the request body, all CORS rules will be deleted, and CORS will be disabled for the Blob service." + } + }, + "defaultServiceVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Indicates the default version to use for requests to the Blob service if an incoming request's version is not specified. Possible values include version 2008-10-27 and all more recent versions." + } + }, + "deleteRetentionPolicyEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. The blob service properties for blob soft delete." + } + }, + "deleteRetentionPolicyDays": { + "type": "int", + "defaultValue": 7, + "minValue": 1, + "maxValue": 365, + "metadata": { + "description": "Optional. Indicates the number of days that the deleted blob should be retained." + } + }, + "deleteRetentionPolicyAllowPermanentDelete": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share." + } + }, + "isVersioningEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Use versioning to automatically maintain previous versions of your blobs." + } + }, + "lastAccessTimeTrackingPolicyEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The blob service property to configure last access time based tracking policy. When set to true last access time based tracking is enabled." + } + }, + "restorePolicyEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The blob service properties for blob restore policy. If point-in-time restore is enabled, then versioning, change feed, and blob soft delete must also be enabled." + } + }, + "restorePolicyDays": { + "type": "int", + "defaultValue": 6, + "minValue": 1, + "metadata": { + "description": "Optional. How long this blob can be restored. It should be less than DeleteRetentionPolicy days." + } + }, + "containers": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Blob containers to create." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('storageAccountName')]" + }, + "blobServices": { + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": { + "automaticSnapshotPolicyEnabled": "[parameters('automaticSnapshotPolicyEnabled')]", + "changeFeed": "[if(parameters('changeFeedEnabled'), createObject('enabled', true(), 'retentionInDays', parameters('changeFeedRetentionInDays')), null())]", + "containerDeleteRetentionPolicy": { + "enabled": "[parameters('containerDeleteRetentionPolicyEnabled')]", + "days": "[parameters('containerDeleteRetentionPolicyDays')]", + "allowPermanentDelete": "[if(equals(parameters('containerDeleteRetentionPolicyEnabled'), true()), parameters('containerDeleteRetentionPolicyAllowPermanentDelete'), null())]" + }, + "cors": { + "corsRules": "[parameters('corsRules')]" + }, + "defaultServiceVersion": "[if(not(empty(parameters('defaultServiceVersion'))), parameters('defaultServiceVersion'), null())]", + "deleteRetentionPolicy": { + "enabled": "[parameters('deleteRetentionPolicyEnabled')]", + "days": "[parameters('deleteRetentionPolicyDays')]", + "allowPermanentDelete": "[if(and(parameters('deleteRetentionPolicyEnabled'), parameters('deleteRetentionPolicyAllowPermanentDelete')), true(), null())]" + }, + "isVersioningEnabled": "[parameters('isVersioningEnabled')]", + "lastAccessTimeTrackingPolicy": "[if(not(equals(reference('storageAccount', '2022-09-01', 'full').kind, 'Storage')), createObject('enable', parameters('lastAccessTimeTrackingPolicyEnabled'), 'name', if(equals(parameters('lastAccessTimeTrackingPolicyEnabled'), true()), 'AccessTimeTracking', null()), 'trackingGranularityInDays', if(equals(parameters('lastAccessTimeTrackingPolicyEnabled'), true()), 1, null())), null())]", + "restorePolicy": "[if(parameters('restorePolicyEnabled'), createObject('enabled', true(), 'days', parameters('restorePolicyDays')), null())]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "blobServices_diagnosticSettings": { + "copy": { + "name": "blobServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}', parameters('storageAccountName'), variables('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[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')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "blobServices" + ] + }, + "blobServices_container": { + "copy": { + "name": "blobServices_container", + "count": "[length(coalesce(parameters('containers'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Container-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "name": { + "value": "[coalesce(parameters('containers'), createArray())[copyIndex()].name]" + }, + "defaultEncryptionScope": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'defaultEncryptionScope')]" + }, + "denyEncryptionScopeOverride": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'denyEncryptionScopeOverride')]" + }, + "enableNfsV3AllSquash": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'enableNfsV3AllSquash')]" + }, + "enableNfsV3RootSquash": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'enableNfsV3RootSquash')]" + }, + "immutableStorageWithVersioningEnabled": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'immutableStorageWithVersioningEnabled')]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'metadata')]" + }, + "publicAccess": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'publicAccess')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "immutabilityPolicyProperties": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'immutabilityPolicyProperties')]" + } + }, + "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.26.170.59819", + "templateHash": "3805384021483033369" + }, + "name": "Storage Account Blob Containers", + "description": "This module deploys a Storage Account Blob Container.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the storage container to deploy." + } + }, + "defaultEncryptionScope": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Default the container to use specified encryption scope for all writes." + } + }, + "denyEncryptionScopeOverride": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Block override of encryption scope from the container default." + } + }, + "enableNfsV3AllSquash": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable NFSv3 all squash on blob container." + } + }, + "enableNfsV3RootSquash": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable NFSv3 root squash on blob container." + } + }, + "immutableStorageWithVersioningEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This is an immutable property, when set to true it enables object level immutability at the container level. The property is immutable and can only be set to true at the container creation time. Existing containers must undergo a migration process." + } + }, + "immutabilityPolicyName": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. Name of the immutable policy." + } + }, + "immutabilityPolicyProperties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Configure immutability policy." + } + }, + "metadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. A name-value pair to associate with the container as metadata." + } + }, + "publicAccess": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "Container", + "Blob", + "None" + ], + "metadata": { + "description": "Optional. Specifies whether data in the container may be accessed publicly and the level of access." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Blob Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "Storage Blob Data Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]", + "Storage Blob Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')]", + "Storage Blob Delegator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db58b8e5-c6ad-4a2a-8342-4190687cbf4a')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "storageAccount::blobServices": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('storageAccountName')]" + }, + "container": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "properties": { + "defaultEncryptionScope": "[if(not(empty(parameters('defaultEncryptionScope'))), parameters('defaultEncryptionScope'), null())]", + "denyEncryptionScopeOverride": "[if(equals(parameters('denyEncryptionScopeOverride'), true()), parameters('denyEncryptionScopeOverride'), null())]", + "enableNfsV3AllSquash": "[if(equals(parameters('enableNfsV3AllSquash'), true()), parameters('enableNfsV3AllSquash'), null())]", + "enableNfsV3RootSquash": "[if(equals(parameters('enableNfsV3RootSquash'), true()), parameters('enableNfsV3RootSquash'), null())]", + "immutableStorageWithVersioning": "[if(equals(parameters('immutableStorageWithVersioningEnabled'), true()), createObject('enabled', parameters('immutableStorageWithVersioningEnabled')), null())]", + "metadata": "[parameters('metadata')]", + "publicAccess": "[parameters('publicAccess')]" + }, + "dependsOn": [ + "storageAccount::blobServices" + ] + }, + "container_roleAssignments": { + "copy": { + "name": "container_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}/containers/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), 'default', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "container" + ] + }, + "immutabilityPolicy": { + "condition": "[not(empty(coalesce(parameters('immutabilityPolicyProperties'), createObject())))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[parameters('immutabilityPolicyName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "containerName": { + "value": "[parameters('name')]" + }, + "immutabilityPeriodSinceCreationInDays": { + "value": "[tryGet(parameters('immutabilityPolicyProperties'), 'immutabilityPeriodSinceCreationInDays')]" + }, + "allowProtectedAppendWrites": { + "value": "[tryGet(parameters('immutabilityPolicyProperties'), 'allowProtectedAppendWrites')]" + }, + "allowProtectedAppendWritesAll": { + "value": "[tryGet(parameters('immutabilityPolicyProperties'), 'allowProtectedAppendWritesAll')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "12849754295459852309" + }, + "name": "Storage Account Blob Container Immutability Policies", + "description": "This module deploys a Storage Account Blob Container Immutability Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "containerName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent container to apply the policy to. Required if the template is used in a standalone deployment." + } + }, + "immutabilityPeriodSinceCreationInDays": { + "type": "int", + "defaultValue": 365, + "metadata": { + "description": "Optional. The immutability period for the blobs in the container since the policy creation, in days." + } + }, + "allowProtectedAppendWrites": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to an append blob while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API." + } + }, + "allowProtectedAppendWritesAll": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to both \"Append and Block Blobs\" while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. The \"allowProtectedAppendWrites\" and \"allowProtectedAppendWritesAll\" properties are mutually exclusive." + } + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}/{3}', parameters('storageAccountName'), 'default', parameters('containerName'), 'default')]", + "properties": { + "immutabilityPeriodSinceCreationInDays": "[parameters('immutabilityPeriodSinceCreationInDays')]", + "allowProtectedAppendWrites": "[parameters('allowProtectedAppendWrites')]", + "allowProtectedAppendWritesAll": "[parameters('allowProtectedAppendWritesAll')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed immutability policy." + }, + "value": "default" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed immutability policy." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies', parameters('storageAccountName'), 'default', parameters('containerName'), 'default')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed immutability policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "container", + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed container." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed container." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), 'default', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed container." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed blob service." + }, + "value": "[variables('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed blob service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccountName'), variables('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the deployed blob service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_fileServices": { + "condition": "[not(empty(parameters('fileServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-FileServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('fileServices'), 'diagnosticSettings')]" + }, + "protocolSettings": { + "value": "[tryGet(parameters('fileServices'), 'protocolSettings')]" + }, + "shareDeleteRetentionPolicy": { + "value": "[tryGet(parameters('fileServices'), 'shareDeleteRetentionPolicy')]" + }, + "shares": { + "value": "[tryGet(parameters('fileServices'), 'shares')]" + } + }, + "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.26.170.59819", + "templateHash": "4306536926065797375" + }, + "name": "Storage Account File Share Services", + "description": "This module deploys a Storage Account File Share Service.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the file service." + } + }, + "protocolSettings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Protocol settings for file service." + } + }, + "shareDeleteRetentionPolicy": { + "type": "object", + "defaultValue": { + "enabled": true, + "days": 7 + }, + "metadata": { + "description": "Optional. The service properties for soft delete." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "shares": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. File shares to create." + } + } + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-09-01", + "name": "[parameters('storageAccountName')]" + }, + "fileServices": { + "type": "Microsoft.Storage/storageAccounts/fileServices", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", + "properties": { + "protocolSettings": "[parameters('protocolSettings')]", + "shareDeleteRetentionPolicy": "[parameters('shareDeleteRetentionPolicy')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "fileServices_diagnosticSettings": { + "copy": { + "name": "fileServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/fileServices/{1}', parameters('storageAccountName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[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')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "fileServices" + ] + }, + "fileServices_shares": { + "copy": { + "name": "fileServices_shares", + "count": "[length(coalesce(parameters('shares'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-shares-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "fileServicesName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('shares'), createArray())[copyIndex()].name]" + }, + "accessTier": { + "value": "[coalesce(tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'accessTier'), if(equals(reference('storageAccount', '2021-09-01', 'full').kind, 'FileStorage'), 'Premium', 'TransactionOptimized'))]" + }, + "enabledProtocols": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'enabledProtocols')]" + }, + "rootSquash": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'rootSquash')]" + }, + "shareQuota": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'shareQuota')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'roleAssignments')]" + } + }, + "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.26.170.59819", + "templateHash": "13618261904162439978" + }, + "name": "Storage Account File Shares", + "description": "This module deploys a Storage Account File Share.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "fileServicesName": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Conditional. The name of the parent file service. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the file share to create." + } + }, + "accessTier": { + "type": "string", + "defaultValue": "TransactionOptimized", + "allowedValues": [ + "Premium", + "Hot", + "Cool", + "TransactionOptimized" + ], + "metadata": { + "description": "Conditional. Access tier for specific share. Required if the Storage Account kind is set to FileStorage (should be set to \"Premium\"). GpV2 account can choose between TransactionOptimized (default), Hot, and Cool." + } + }, + "shareQuota": { + "type": "int", + "defaultValue": 5120, + "metadata": { + "description": "Optional. The maximum size of the share, in gigabytes. Must be greater than 0, and less than or equal to 5120 (5TB). For Large File Shares, the maximum size is 102400 (100TB)." + } + }, + "enabledProtocols": { + "type": "string", + "defaultValue": "SMB", + "allowedValues": [ + "NFS", + "SMB" + ], + "metadata": { + "description": "Optional. The authentication protocol that is used for the file share. Can only be specified when creating a share." + } + }, + "rootSquash": { + "type": "string", + "defaultValue": "NoRootSquash", + "allowedValues": [ + "AllSquash", + "NoRootSquash", + "RootSquash" + ], + "metadata": { + "description": "Optional. Permissions for NFS file shares are enforced by the client OS rather than the Azure Files service. Toggling the root squash behavior reduces the rights of the root user for NFS shares." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "resources": { + "storageAccount::fileService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/fileServices", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('fileServicesName'))]", + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-09-01", + "name": "[parameters('storageAccountName')]" + }, + "fileShare": { + "type": "Microsoft.Storage/storageAccounts/fileServices/shares", + "apiVersion": "2023-01-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name'))]", + "properties": { + "accessTier": "[parameters('accessTier')]", + "shareQuota": "[parameters('shareQuota')]", + "rootSquash": "[if(equals(parameters('enabledProtocols'), 'NFS'), parameters('rootSquash'), null())]", + "enabledProtocols": "[parameters('enabledProtocols')]" + }, + "dependsOn": [ + "storageAccount::fileService" + ] + }, + "fileShare_roleAssignments": { + "condition": "[not(empty(parameters('roleAssignments')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Share-Rbac', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "fileShareResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name'))]" + }, + "roleAssignments": { + "value": "[parameters('roleAssignments')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "6057169747302051267" + } + }, + "parameters": { + "roleAssignments": { + "type": "array", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "fileShareResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the file share to assign the roles to." + } + } + }, + "variables": { + "$fxv#0": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "scope": { + "type": "string", + "metadata": { + "description": "Required. The scope to deploy the role assignment to." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the role assignment." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. The role definition Id to assign." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User", + "" + ], + "defaultValue": "", + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "defaultValue": "", + "metadata": { + "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\"" + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "defaultValue": "2.0", + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[[parameters('scope')]", + "name": "[[parameters('name')]", + "properties": { + "roleDefinitionId": "[[parameters('roleDefinitionId')]", + "principalId": "[[parameters('principalId')]", + "description": "[[parameters('description')]", + "principalType": "[[if(not(empty(parameters('principalType'))), parameters('principalType'), null())]", + "condition": "[[if(not(empty(parameters('condition'))), parameters('condition'), null())]", + "conditionVersion": "[[if(and(not(empty(parameters('conditionVersion'))), not(empty(parameters('condition')))), parameters('conditionVersion'), null())]", + "delegatedManagedIdentityResourceId": "[[if(not(empty(parameters('delegatedManagedIdentityResourceId'))), parameters('delegatedManagedIdentityResourceId'), null())]" + } + } + ] + }, + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage File Data SMB Share Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb')]", + "Storage File Data SMB Share Elevated Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a7264617-510b-434b-a828-9731dc254ea7')]", + "Storage File Data SMB Share Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'aba4ae5f-2193-4029-9191-0cb91df5e314')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": [ + { + "copy": { + "name": "fileShare_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2021-04-01", + "name": "[format('{0}-Share-Rbac-{1}', uniqueString(deployment().name), copyIndex())]", + "properties": { + "mode": "Incremental", + "expressionEvaluationOptions": { + "scope": "Outer" + }, + "template": "[variables('$fxv#0')]", + "parameters": { + "scope": { + "value": "[replace(parameters('fileShareResourceId'), '/shares/', '/fileShares/')]" + }, + "name": { + "value": "[guid(parameters('fileShareResourceId'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, 'tyfa')]" + }, + "roleDefinitionId": { + "value": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]" + }, + "principalId": { + "value": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]" + }, + "principalType": { + "value": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]" + }, + "description": { + "value": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]" + }, + "condition": { + "value": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]" + }, + "conditionVersion": { + "value": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]" + }, + "delegatedManagedIdentityResourceId": { + "value": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + } + } + } + } + ] + } + }, + "dependsOn": [ + "fileShare" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "fileServices", + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share service." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/fileServices', parameters('storageAccountName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_queueServices": { + "condition": "[not(empty(parameters('queueServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-QueueServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('queueServices'), 'diagnosticSettings')]" + }, + "queues": { + "value": "[tryGet(parameters('queueServices'), 'queues')]" + } + }, + "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.26.170.59819", + "templateHash": "4494965320132257914" + }, + "name": "Storage Account Queue Services", + "description": "This module deploys a Storage Account Queue Service.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "queues": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Queues to create." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-09-01", + "name": "[parameters('storageAccountName')]" + }, + "queueServices": { + "type": "Microsoft.Storage/storageAccounts/queueServices", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": {}, + "dependsOn": [ + "storageAccount" + ] + }, + "queueServices_diagnosticSettings": { + "copy": { + "name": "queueServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/queueServices/{1}', parameters('storageAccountName'), variables('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[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')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "queueServices" + ] + }, + "queueServices_queues": { + "copy": { + "name": "queueServices_queues", + "count": "[length(coalesce(parameters('queues'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Queue-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "name": { + "value": "[coalesce(parameters('queues'), createArray())[copyIndex()].name]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('queues'), createArray())[copyIndex()], 'metadata')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('queues'), createArray())[copyIndex()], 'roleAssignments')]" + } + }, + "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.26.170.59819", + "templateHash": "2870814699283520878" + }, + "name": "Storage Account Queues", + "description": "This module deploys a Storage Account Queue.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the storage queue to deploy." + } + }, + "metadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Required. A name-value pair that represents queue metadata." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Queue Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88')]", + "Storage Queue Data Message Processor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8a0f0c08-91a1-4084-bc3d-661d67233fed')]", + "Storage Queue Data Message Sender": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a')]", + "Storage Queue Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '19e7f393-937e-4f77-808e-94535e297925')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "storageAccount::queueServices": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/queueServices", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-09-01", + "name": "[parameters('storageAccountName')]" + }, + "queue": { + "type": "Microsoft.Storage/storageAccounts/queueServices/queues", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "properties": { + "metadata": "[parameters('metadata')]" + }, + "dependsOn": [ + "storageAccount::queueServices" + ] + }, + "queue_roleAssignments": { + "copy": { + "name": "queue_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/queueServices/{1}/queues/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts/queueServices/queues', parameters('storageAccountName'), 'default', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "queue" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed queue." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed queue." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/queueServices/queues', parameters('storageAccountName'), 'default', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed queue." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share service." + }, + "value": "[variables('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/queueServices', parameters('storageAccountName'), variables('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_tableServices": { + "condition": "[not(empty(parameters('tableServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-TableServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('tableServices'), 'diagnosticSettings')]" + }, + "tables": { + "value": "[tryGet(parameters('tableServices'), 'tables')]" + } + }, + "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.26.170.59819", + "templateHash": "6806949296172999226" + }, + "name": "Storage Account Table Services", + "description": "This module deploys a Storage Account Table Service.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "tables": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. tables to create." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-09-01", + "name": "[parameters('storageAccountName')]" + }, + "tableServices": { + "type": "Microsoft.Storage/storageAccounts/tableServices", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": {}, + "dependsOn": [ + "storageAccount" + ] + }, + "tableServices_diagnosticSettings": { + "copy": { + "name": "tableServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/tableServices/{1}', parameters('storageAccountName'), variables('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[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')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "tableServices" + ] + }, + "tableServices_tables": { + "copy": { + "name": "tableServices_tables", + "count": "[length(parameters('tables'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Table-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('tables')[copyIndex()].name]" + }, + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "roleAssignments": { + "value": "[tryGet(parameters('tables')[copyIndex()], 'roleAssignments')]" + } + }, + "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.26.170.59819", + "templateHash": "5583637725561111612" + }, + "name": "Storage Account Table", + "description": "This module deploys a Storage Account Table.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the table." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Table Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3')]", + "Storage Table Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76199698-9eea-4c19-bc75-cec21354c6b6')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "storageAccount::tableServices": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/tableServices", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-09-01", + "name": "[parameters('storageAccountName')]" + }, + "table": { + "type": "Microsoft.Storage/storageAccounts/tableServices/tables", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "dependsOn": [ + "storageAccount::tableServices" + ] + }, + "table_roleAssignments": { + "copy": { + "name": "table_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/tableServices/{1}/tables/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts/tableServices/tables', parameters('storageAccountName'), 'default', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "table" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share service." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/tableServices/tables', parameters('storageAccountName'), 'default', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed table service." + }, + "value": "[variables('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed table service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/tableServices', parameters('storageAccountName'), variables('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed table service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed storage account." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed storage account." + }, + "value": "[parameters('name')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed storage account." + }, + "value": "[resourceGroup().name]" + }, + "primaryBlobEndpoint": { + "type": "string", + "metadata": { + "description": "The primary blob endpoint reference if blob services are deployed." + }, + "value": "[if(and(not(empty(parameters('blobServices'))), contains(parameters('blobServices'), 'containers')), reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('name')), '2019-04-01').primaryEndpoints.blob, '')]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[coalesce(tryGet(tryGet(reference('storageAccount', '2022-09-01', 'full'), 'identity'), 'principalId'), '')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('storageAccount', '2022-09-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "identity" + ] + }, + "uploadSettings": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-uploadSettings', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "uploadSettings" + }, + "kind": { + "value": "AzurePowerShell" + }, + "location": "[if(startsWith(parameters('location'), 'china'), createObject('value', 'chinaeast2'), createObject('value', parameters('location')))]", + "tags": { + "value": "[union(coalesce(parameters('tags'), createObject()), if(contains(parameters('tagsByResource'), 'Microsoft.Resources/deploymentScripts'), parameters('tagsByResource')['Microsoft.Resources/deploymentScripts'], createObject()))]" + }, + "managedIdentities": { + "value": { + "userAssignedResourcesIds": [ + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_blobManager', parameters('storageAccountName')))]" + ] + } + }, + "azPowerShellVersion": { + "value": "9.7" + }, + "retentionInterval": { + "value": "PT1H" + }, + "environmentVariables": { + "value": { + "secureList": [ + { + "name": "ftkVersion", + "value": "[parameters('ftkVersion')]" + }, + { + "name": "exportScopes", + "value": "[join(parameters('exportScopes'), '|')]" + }, + { + "name": "storageAccountName", + "value": "[parameters('storageAccountName')]" + }, + { + "name": "containerName", + "value": "config" + } + ] + } + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + } + }, + "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.25.53.49325", + "templateHash": "18265939959786496245" + }, + "name": "Deployment Scripts", + "description": "This module deploys Deployment Scripts.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "managedIdentitiesType": { + "type": "object", + "properties": { + "userAssignedResourcesIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "environmentVariableType": { + "type": "secureObject", + "properties": { + "secureList": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "secureValue": { + "type": "string", + "nullable": true + }, + "value": { + "type": "string", + "nullable": true + } + } + }, + "metadata": { + "description": "Optional. The list of environment variables to pass over to the deployment script." + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Name of the Deployment Script." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "AzureCLI", + "AzurePowerShell" + ], + "metadata": { + "description": "Required. Specifies the Kind of the Deployment Script." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "azPowerShellVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure PowerShell module version to be used. See a list of supported Azure PowerShell versions: https://mcr.microsoft.com/v2/azuredeploymentscripts-powershell/tags/list." + } + }, + "azCliVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure CLI module version to be used. See a list of supported Azure CLI versions: https://mcr.microsoft.com/v2/azure-cli/tags/list." + } + }, + "scriptContent": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Script body. Max length: 32000 characters. To run an external script, use primaryScriptURI instead." + } + }, + "primaryScriptUri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Uri for the external script. This is the entry point for the external script. To run an internal script, use the scriptContent parameter instead." + } + }, + "environmentVariables": { + "$ref": "#/definitions/environmentVariableType", + "metadata": { + "description": "Optional. The environment variables to pass over to the script. The list is passed as an object with a key name \"secureList\" and the value is the list of environment variables (array). The list must have a 'name' and a 'value' or a 'secretValue' property for each object." + } + }, + "supportingScriptUris": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. List of supporting files for the external script (defined in primaryScriptUri). Does not work with internal scripts (code defined in scriptContent)." + } + }, + "subnetResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of subnet IDs to use for the container group. This is required if you want to run the deployment script in a private network. When using a private network, the `Storage File Data Privileged Contributor` role needs to be assigned to the user-assigned managed identity and the deployment principal needs to have permissions to list the storage account keys. Also, Shared-Keys must not be disabled on the used storage account [ref](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-vnet)." + } + }, + "arguments": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Command-line arguments to pass to the script. Arguments are separated by spaces." + } + }, + "retentionInterval": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires. Duration is based on ISO 8601 pattern (for example P7D means one week)." + } + }, + "baseTime": { + "type": "string", + "defaultValue": "[utcNow('yyyy-MM-dd-HH-mm-ss')]", + "metadata": { + "description": "Generated. Do not provide a value! This date value is used to make sure the script run every time the template is deployed." + } + }, + "runOnce": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. When set to false, script will run every time the template is deployed. When set to true, the script will only run once." + } + }, + "cleanupPreference": { + "type": "string", + "defaultValue": "Always", + "allowedValues": [ + "Always", + "OnSuccess", + "OnExpiration" + ], + "metadata": { + "description": "Optional. The clean up preference when the script execution gets in a terminal state. Specify the preference on when to delete the deployment script resources. The default value is Always, which means the deployment script resources are deleted despite the terminal state (Succeeded, Failed, canceled)." + } + }, + "containerGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Container group name, if not specified then the name will get auto-generated. Not specifying a 'containerGroupName' indicates the system to generate a unique name which might end up flagging an Azure Policy as non-compliant. Use 'containerGroupName' when you have an Azure Policy that expects a specific naming convention or when you want to fully control the name. 'containerGroupName' property must be between 1 and 63 characters long, must contain only lowercase letters, numbers, and dashes and it cannot start or end with a dash and consecutive dashes are not allowed." + } + }, + "storageAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the storage account to use for this deployment script. If none is provided, the deployment script uses a temporary, managed storage account." + } + }, + "timeout": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Maximum allowed script execution time specified in ISO 8601 format. Default value is PT1H - 1 hour; 'PT30M' - 30 minutes; 'P5D' - 5 days; 'P1Y' 1 year." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "subnetIds", + "count": "[length(coalesce(parameters('subnetResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('subnetResourceIds'), createArray())[copyIndex('subnetIds')]]" + } + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + }, + "containerSettings": { + "containerGroupName": "[parameters('containerGroupName')]", + "subnetIds": "[if(not(empty(coalesce(variables('subnetIds'), createArray()))), variables('subnetIds'), null())]" + }, + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createObject()))), 'UserAssigned', null()), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]" + }, + "resources": { + "storageAccount": { + "condition": "[not(empty(parameters('storageAccountResourceId')))]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-04-01", + "subscriptionId": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]]", + "name": "[last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))]" + }, + "deploymentScript_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "deploymentScript_roleAssignments": { + "copy": { + "name": "deploymentScript_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Resources/deploymentScripts', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.resources-deploymentscript.{0}.{1}', replace('0.2.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "deploymentScript": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "kind": "[parameters('kind')]", + "properties": { + "azPowerShellVersion": "[if(equals(parameters('kind'), 'AzurePowerShell'), parameters('azPowerShellVersion'), null())]", + "azCliVersion": "[if(equals(parameters('kind'), 'AzureCLI'), parameters('azCliVersion'), null())]", + "containerSettings": "[if(not(empty(variables('containerSettings'))), variables('containerSettings'), null())]", + "storageAccountSettings": "[if(not(empty(parameters('storageAccountResourceId'))), if(not(empty(parameters('storageAccountResourceId'))), createObject('storageAccountKey', if(empty(parameters('subnetResourceIds')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2], split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))), '2023-01-01').keys[0].value, null()), 'storageAccountName', last(split(parameters('storageAccountResourceId'), '/'))), null()), null())]", + "arguments": "[parameters('arguments')]", + "environmentVariables": "[if(not(equals(parameters('environmentVariables'), null())), parameters('environmentVariables').secureList, createArray())]", + "scriptContent": "[if(not(empty(parameters('scriptContent'))), parameters('scriptContent'), null())]", + "primaryScriptUri": "[if(not(empty(parameters('primaryScriptUri'))), parameters('primaryScriptUri'), null())]", + "supportingScriptUris": "[if(not(empty(parameters('supportingScriptUris'))), parameters('supportingScriptUris'), null())]", + "cleanupPreference": "[parameters('cleanupPreference')]", + "forceUpdateTag": "[if(parameters('runOnce'), resourceGroup().name, parameters('baseTime'))]", + "retentionInterval": "[parameters('retentionInterval')]", + "timeout": "[parameters('timeout')]" + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployment script." + }, + "value": "[resourceId('Microsoft.Resources/deploymentScripts', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the deployment script was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployment script." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('deploymentScript', '2023-08-01', 'full').location]" + }, + "outputs": { + "type": "object", + "metadata": { + "description": "The output of the deployment script." + }, + "value": "[if(contains(reference('deploymentScript'), 'outputs'), reference('deploymentScript').outputs, createObject())]" + } + } + } + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the storage account." + }, + "value": "[reference('storageAccount').outputs.resourceId.value]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the storage account." + }, + "value": "[reference('storageAccount').outputs.name.value]" + } + } + } + } + }, + "dataFactoryResources": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-dataFactoryResources', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[variables('dataFactoryName')]" + }, + "convertToParquet": { + "value": "[parameters('convertToParquet')]" + }, + "keyVaultName": { + "value": "[reference('keyVault').outputs.name.value]" + }, + "storageAccountName": { + "value": "[reference('storage').outputs.name.value]" + }, + "exportContainerName": { + "value": "[parameters('exportContainer')]" + }, + "ingestionContainerName": { + "value": "[parameters('ingestionContainer')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('resourceTags')]" + }, + "tagsByResource": { + "value": "[parameters('tagsByResource')]" + } + }, + "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.26.170.59819", + "templateHash": "13617090394106970682" + } + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the hub. Used to ensure unique resource names. Default: \"finops-hub\"." + } + }, + "keyVaultName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Required. The name of the Azure Key Vault instance." + } + }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Required. The name of the Azure storage account instance." + } + }, + "exportContainerName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Required. The name of the container where Cost Management data is exported." + } + }, + "ingestionContainerName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Required. The name of the container where normalized data is ingested." + } + }, + "convertToParquet": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether ingested data should be converted to Parquet. Default: true." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location)." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to apply to all resources. We will also add the cm-resource-parent tag for improved cost roll-ups in Cost Management." + } + }, + "tagsByResource": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources." + } + } + }, + "variables": { + "copy": [ + { + "name": "focusCostMappings", + "count": "[length(range(0, length(variables('focusCostColumns'))))]", + "input": { + "source": { + "name": "[variables('focusCostColumns')[range(0, length(variables('focusCostColumns')))[copyIndex('focusCostMappings')]].name]", + "type": "[variables('focusCostColumns')[range(0, length(variables('focusCostColumns')))[copyIndex('focusCostMappings')]].type]" + }, + "sink": { + "name": "[variables('focusCostColumns')[range(0, length(variables('focusCostColumns')))[copyIndex('focusCostMappings')]].name]" + } + } + } + ], + "$fxv#0": "# Init outputs\n$DeploymentScriptOutputs = @{}\n\n$adfParams = @{\n ResourceGroupName = $env:DataFactoryResourceGroup\n DataFactoryName = $env:DataFactoryName\n}\n\n# Delete old triggers\n$triggers = Get-AzDataFactoryV2Trigger @adfParams -ErrorAction SilentlyContinue `\n| Where-Object { $_.Name -match '^msexports?$' }\n$DeploymentScriptOutputs['stopTriggers'] = $triggers | Stop-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\n$DeploymentScriptOutputs['deleteTriggers'] = $triggers | Remove-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\n\n# Delete old pipelines\n$DeploymentScriptOutputs['pipelines'] = Get-AzDataFactoryV2Pipeline @adfParams -ErrorAction SilentlyContinue `\n| Where-Object { $_.Name -match '^msexports_(extract|transform)$' } `\n| Remove-AzDataFactoryV2Pipeline -Force -ErrorAction SilentlyContinue\n", + "$fxv#1": "Param(\n [switch] $Stop\n)\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop) {\n Start-Sleep -Seconds 10\n}\n\n# Loop through triggers\n$env:Triggers.Split('|') `\n| ForEach-Object {\n $trigger = $_\n if ($Stop) {\n Write-Host \"Stopping trigger $trigger...\" -NoNewline\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue\n } else {\n Write-Host \"Starting trigger $trigger...\" -NoNewline\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput) {\n Write-Host 'done'\n } else {\n Write-Host 'failed'\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n}\n\nif ($Stop) {\n Start-Sleep -Seconds 10\n}\n", + "$fxv#2": "Param(\n [switch] $Stop\n)\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop) {\n Start-Sleep -Seconds 10\n}\n\n# Loop through triggers\n$env:Triggers.Split('|') `\n| ForEach-Object {\n $trigger = $_\n if ($Stop) {\n Write-Host \"Stopping trigger $trigger...\" -NoNewline\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue\n } else {\n Write-Host \"Starting trigger $trigger...\" -NoNewline\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput) {\n Write-Host 'done'\n } else {\n Write-Host 'failed'\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n}\n\nif ($Stop) {\n Start-Sleep -Seconds 10\n}\n", + "datasetPropsDelimitedText": { + "columnDelimiter": ",", + "compressionLevel": "Optimal", + "escapeChar": "\"", + "firstRowAsHeader": true, + "quoteChar": "\"" + }, + "datasetPropsCommon": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().fileName}", + "type": "Expression" + }, + "folderPath": { + "value": "@{dataset().folderName}", + "type": "Expression" + } + } + }, + "safeExportContainerName": "[replace(format('{0}', parameters('exportContainerName')), '-', '_')]", + "safeIngestionContainerName": "[replace(format('{0}', parameters('ingestionContainerName')), '-', '_')]", + "extractExportTriggerName": "[parameters('exportContainerName')]", + "allHubTriggers": [ + "[variables('extractExportTriggerName')]" + ], + "autoStartRbacRoles": [ + "673868aa-7521-48a0-acc6-0f60742d39f5", + "e40ec5ca-96e0-45a2-b4ff-59039f2c2b59" + ], + "focusCostColumns": [ + { + "name": "AvailabilityZone", + "type": "String" + }, + { + "name": "BilledCost", + "type": "Decimal" + }, + { + "name": "BillingAccountId", + "type": "String" + }, + { + "name": "BillingAccountName", + "type": "String" + }, + { + "name": "BillingAccountType", + "type": "String" + }, + { + "name": "BillingCurrency", + "type": "String" + }, + { + "name": "BillingPeriodEnd", + "type": "DateTime" + }, + { + "name": "BillingPeriodStart", + "type": "DateTime" + }, + { + "name": "ChargeCategory", + "type": "String" + }, + { + "name": "ChargeDescription", + "type": "String" + }, + { + "name": "ChargeFrequency", + "type": "String" + }, + { + "name": "ChargePeriodEnd", + "type": "DateTime" + }, + { + "name": "ChargePeriodStart", + "type": "DateTime" + }, + { + "name": "ChargeSubcategory", + "type": "String" + }, + { + "name": "CommitmentDiscountCategory", + "type": "String" + }, + { + "name": "CommitmentDiscountId", + "type": "String" + }, + { + "name": "CommitmentDiscountName", + "type": "String" + }, + { + "name": "CommitmentDiscountType", + "type": "String" + }, + { + "name": "EffectiveCost", + "type": "Decimal" + }, + { + "name": "InvoiceIssuerName", + "type": "String" + }, + { + "name": "ListCost", + "type": "Decimal" + }, + { + "name": "ListUnitPrice", + "type": "Decimal" + }, + { + "name": "PricingCategory", + "type": "String" + }, + { + "name": "PricingQuantity", + "type": "Decimal" + }, + { + "name": "PricingUnit", + "type": "String" + }, + { + "name": "ProviderName", + "type": "String" + }, + { + "name": "PublisherName", + "type": "String" + }, + { + "name": "Region", + "type": "String" + }, + { + "name": "ResourceId", + "type": "String" + }, + { + "name": "ResourceName", + "type": "String" + }, + { + "name": "ResourceType", + "type": "String" + }, + { + "name": "ServiceCategory", + "type": "String" + }, + { + "name": "ServiceName", + "type": "String" + }, + { + "name": "SkuId", + "type": "String" + }, + { + "name": "SkuPriceId", + "type": "String" + }, + { + "name": "SubAccountId", + "type": "String" + }, + { + "name": "SubAccountName", + "type": "String" + }, + { + "name": "SubAccountType", + "type": "String" + }, + { + "name": "Tags", + "type": "String" + }, + { + "name": "UsageQuantity", + "type": "Decimal" + }, + { + "name": "UsageUnit", + "type": "String" + }, + { + "name": "x_AccountName", + "type": "String" + }, + { + "name": "x_AccountOwnerId", + "type": "String" + }, + { + "name": "x_BilledCostInUsd", + "type": "Decimal" + }, + { + "name": "x_BilledUnitPrice", + "type": "Decimal" + }, + { + "name": "x_BillingAccountId", + "type": "String" + }, + { + "name": "x_BillingAccountName", + "type": "String" + }, + { + "name": "x_BillingExchangeRate", + "type": "Decimal" + }, + { + "name": "x_BillingExchangeRateDate", + "type": "DateTime" + }, + { + "name": "x_BillingProfileId", + "type": "String" + }, + { + "name": "x_BillingProfileName", + "type": "String" + }, + { + "name": "x_ChargeId", + "type": "String" + }, + { + "name": "x_CostAllocationRuleName", + "type": "String" + }, + { + "name": "x_CostCenter", + "type": "String" + }, + { + "name": "x_CustomerId", + "type": "String" + }, + { + "name": "x_CustomerName", + "type": "String" + }, + { + "name": "x_EffectiveCostInUsd", + "type": "Decimal" + }, + { + "name": "x_EffectiveUnitPrice", + "type": "Decimal" + }, + { + "name": "x_InvoiceId", + "type": "String" + }, + { + "name": "x_InvoiceIssuerId", + "type": "String" + }, + { + "name": "x_InvoiceSectionId", + "type": "String" + }, + { + "name": "x_InvoiceSectionName", + "type": "String" + }, + { + "name": "x_OnDemandCost", + "type": "Decimal" + }, + { + "name": "x_OnDemandCostInUsd", + "type": "Decimal" + }, + { + "name": "x_OnDemandUnitPrice", + "type": "Decimal" + }, + { + "name": "x_PartnerCreditApplied", + "type": "Boolean" + }, + { + "name": "x_PartnerCreditRate", + "type": "Decimal" + }, + { + "name": "x_PricingBlockSize", + "type": "Decimal" + }, + { + "name": "x_PricingCurrency", + "type": "String" + }, + { + "name": "x_PricingSubcategory", + "type": "String" + }, + { + "name": "x_PricingUnitDescription", + "type": "String" + }, + { + "name": "x_PublisherCategory", + "type": "String" + }, + { + "name": "x_PublisherId", + "type": "String" + }, + { + "name": "x_ResellerId", + "type": "String" + }, + { + "name": "x_ResellerName", + "type": "String" + }, + { + "name": "x_ResourceGroupName", + "type": "String" + }, + { + "name": "x_ResourceType", + "type": "String" + }, + { + "name": "x_ServicePeriodEnd", + "type": "DateTime" + }, + { + "name": "x_ServicePeriodStart", + "type": "DateTime" + }, + { + "name": "x_SkuDescription", + "type": "String" + }, + { + "name": "x_SkuDetails", + "type": "String" + }, + { + "name": "x_SkuIsCreditEligible", + "type": "Boolean" + }, + { + "name": "x_SkuMeterCategory", + "type": "String" + }, + { + "name": "x_SkuMeterId", + "type": "String" + }, + { + "name": "x_SkuMeterName", + "type": "String" + }, + { + "name": "x_SkuMeterSubcategory", + "type": "String" + }, + { + "name": "x_SkuOfferId", + "type": "String" + }, + { + "name": "x_SkuOrderId", + "type": "String" + }, + { + "name": "x_SkuOrderName", + "type": "String" + }, + { + "name": "x_SkuPartNumber", + "type": "String" + }, + { + "name": "x_SkuRegion", + "type": "String" + }, + { + "name": "x_SkuServiceFamily", + "type": "String" + }, + { + "name": "x_SkuTerm", + "type": "String" + }, + { + "name": "x_SkuTier", + "type": "String" + } + ] + }, + "resources": { + "dataFactory": { + "existing": true, + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('dataFactoryName')]" + }, + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_triggerManager', parameters('dataFactoryName'))]", + "location": "[parameters('location')]", + "tags": "[union(coalesce(parameters('tags'), createObject()), if(contains(parameters('tagsByResource'), 'Microsoft.ManagedIdentity/userAssignedIdentities'), parameters('tagsByResource')['Microsoft.ManagedIdentity/userAssignedIdentities'], createObject()))]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('autoStartRbacRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('dataFactoryName'))]", + "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('dataFactoryName'))))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "identity" + ] + }, + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-11-01", + "name": "[parameters('keyVaultName')]" + }, + "linkedService_keyVault": { + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'keyVault')]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureKeyVault", + "typeProperties": { + "baseUrl": "[reference('keyVault').vaultUri]" + } + }, + "dependsOn": [ + "dataFactory", + "keyVault" + ] + }, + "linkedService_storageAccount": { + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'storage')]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[reference('storageAccount').primaryEndpoints.dfs]", + "accountKey": { + "type": "AzureKeyVaultSecret", + "store": { + "referenceName": "keyVault", + "type": "LinkedServiceReference" + }, + "secretName": "[parameters('storageAccountName')]" + } + } + }, + "dependsOn": [ + "dataFactory", + "linkedService_keyVault", + "storageAccount" + ] + }, + "dataset_msexports": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeExportContainerName'))]", + "properties": { + "annotations": [], + "parameters": { + "fileName": { + "type": "String" + }, + "folderName": { + "type": "String" + } + }, + "type": "DelimitedText", + "typeProperties": "[union(variables('datasetPropsCommon'), variables('datasetPropsDelimitedText'), createObject('compressionCodec', 'none'))]", + "linkedServiceName": { + "parameters": {}, + "referenceName": "storage", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "dataFactory", + "linkedService_keyVault", + "linkedService_storageAccount" + ] + }, + "dataset_ingestion": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeIngestionContainerName'))]", + "properties": { + "annotations": [], + "parameters": { + "fileName": { + "type": "String" + }, + "folderName": { + "type": "String" + } + }, + "type": "[if(parameters('convertToParquet'), 'Parquet', 'DelimitedText')]", + "typeProperties": "[union(variables('datasetPropsCommon'), if(parameters('convertToParquet'), createObject(), variables('datasetPropsDelimitedText')), createObject('compressionCodec', 'gzip'))]", + "linkedServiceName": { + "parameters": {}, + "referenceName": "storage", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "dataFactory", + "linkedService_keyVault", + "linkedService_storageAccount" + ] + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('storageAccountName')]" + }, + "trigger_msexports_FileAdded": { + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_FileAdded', variables('safeExportContainerName')))]", + "properties": { + "annotations": [], + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[format('{0}_ExecuteETL', parameters('exportContainerName'))]", + "type": "PipelineReference" + }, + "parameters": { + "folderName": "@triggerBody().folderPath", + "fileName": "@triggerBody().fileName" + } + } + ], + "type": "BlobEventsTrigger", + "typeProperties": { + "blobPathBeginsWith": "[format('/{0}/blobs/', parameters('exportContainerName'))]", + "blobPathEndsWith": ".csv", + "ignoreEmptyBlobs": true, + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "events": [ + "Microsoft.Storage.BlobCreated" + ] + } + }, + "dependsOn": [ + "dataFactory", + "pipeline_ExecuteETL", + "stopHubTriggers", + "storageAccount" + ] + }, + "pipeline_ExecuteETL": { + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ExecuteETL', variables('safeExportContainerName')))]", + "properties": { + "activities": [ + { + "name": "Execute", + "type": "ExecutePipeline", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "[format('{0}_ETL_{1}', variables('safeExportContainerName'), variables('safeIngestionContainerName'))]", + "type": "PipelineReference" + }, + "waitOnCompletion": false, + "parameters": { + "folderName": { + "value": "@pipeline().parameters.folderName", + "type": "Expression" + }, + "fileName": { + "value": "@pipeline().parameters.fileName", + "type": "Expression" + } + } + } + } + ], + "parameters": { + "folderName": { + "type": "string" + }, + "fileName": { + "type": "string" + } + }, + "annotations": [] + }, + "dependsOn": [ + "dataFactory", + "pipeline_msexports_ETL_ingestion" + ] + }, + "pipeline_msexports_ETL_ingestion": { + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ETL_{1}', variables('safeExportContainerName'), variables('safeIngestionContainerName')))]", + "properties": { + "activities": [ + { + "name": "Wait", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 60 + } + }, + { + "name": "Set FolderArray", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait", + "dependencyConditions": [ + "Completed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "folderArray", + "value": { + "value": "@split(pipeline().parameters.folderName, '/')", + "type": "Expression" + } + } + }, + { + "name": "Set Scope", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set FolderArray", + "dependencyConditions": [ + "Completed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "scope", + "value": { + "value": "[format('@replace(split(pipeline().parameters.folderName,variables(''folderArray'')[sub(length(variables(''folderArray'')), if(greater(length(variables(''folderArray'')[sub(length(variables(''folderArray'')), 2)]), 12), 3, 4))])[0],''{0}'',''{1}'')', parameters('exportContainerName'), parameters('ingestionContainerName'))]", + "type": "Expression" + } + } + }, + { + "name": "Set Metric", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Scope", + "dependencyConditions": [ + "Completed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "metric", + "value": { + "value": "focuscost", + "type": "Expression" + } + } + }, + { + "name": "Set Date", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Metric", + "dependencyConditions": [ + "Completed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "date", + "value": { + "value": "@substring(variables('folderArray')[sub(length(variables('folderArray')), if(greater(length(variables('folderArray')[sub(length(variables('folderArray')), 2)]), 12), 2, 3))], 0, 6)", + "type": "Expression" + } + } + }, + { + "name": "Set Destination File Name", + "description": "", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Date", + "dependencyConditions": [ + "Completed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "destinationFile", + "value": { + "value": "[format('@replace(pipeline().parameters.fileName, ''.csv'', ''{0}'')', if(parameters('convertToParquet'), '.parquet', '.csv.gz'))]", + "type": "Expression" + } + } + }, + { + "name": "Set Destination Folder Name", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Destination File Name", + "dependencyConditions": [ + "Completed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "destinationFolder", + "value": { + "value": "@replace(concat(variables('scope'),variables('date'),'/',variables('metric')),'//','/')", + "type": "Expression" + } + } + }, + { + "name": "Delete Target", + "type": "Delete", + "dependsOn": [ + { + "activity": "Set Destination Folder Name", + "dependencyConditions": [ + "Completed" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[variables('safeIngestionContainerName')]", + "type": "DatasetReference", + "parameters": { + "folderName": { + "value": "@variables('destinationFolder')", + "type": "Expression" + }, + "fileName": { + "value": "@variables('destinationFile')", + "type": "Expression" + } + } + }, + "enableLogging": false, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + } + } + }, + { + "name": "Convert CSV", + "type": "Copy", + "dependsOn": [ + { + "activity": "Delete Target", + "dependencyConditions": [ + "Completed" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "sink": { + "type": "DelimitedTextSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": "[if(parameters('convertToParquet'), createObject('type', 'ParquetWriteSettings', 'fileExtension', '.parquet'), createObject('type', 'DelimitedTextWriteSettings', 'quoteAllText', true(), 'fileExtension', '.csv.gz'))]" + }, + "enableStaging": false, + "parallelCopies": 1, + "validateDataConsistency": false, + "translator": { + "type": "TabularTranslator", + "mappings": "[variables('focusCostMappings')]" + } + }, + "inputs": [ + { + "referenceName": "[variables('safeExportContainerName')]", + "type": "DatasetReference", + "parameters": { + "folderName": { + "value": "@pipeline().parameters.folderName", + "type": "Expression" + }, + "fileName": { + "value": "@pipeline().parameters.fileName", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "[variables('safeIngestionContainerName')]", + "type": "DatasetReference", + "parameters": { + "folderName": { + "value": "@variables('destinationFolder')", + "type": "Expression" + }, + "fileName": { + "value": "@variables('destinationFile')", + "type": "Expression" + } + } + } + ] + }, + { + "name": "Delete CSV", + "type": "Delete", + "dependsOn": [ + { + "activity": "Convert CSV", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[variables('safeExportContainerName')]", + "type": "DatasetReference", + "parameters": { + "folderName": { + "value": "@pipeline().parameters.folderName", + "type": "Expression" + }, + "fileName": { + "value": "@pipeline().parameters.fileName", + "type": "Expression" + } + } + }, + "enableLogging": false, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + } + } + } + ], + "parameters": { + "fileName": { + "type": "string" + }, + "folderName": { + "type": "string" + } + }, + "variables": { + "destinationFile": { + "type": "String" + }, + "destinationFolder": { + "type": "String" + }, + "folderArray": { + "type": "Array" + }, + "scope": { + "type": "String" + }, + "date": { + "type": "String" + }, + "metric": { + "type": "String" + } + }, + "annotations": [] + }, + "dependsOn": [ + "dataFactory", + "dataset_ingestion", + "dataset_msexports" + ] + }, + "deleteOldResources": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-deleteOldResources', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "deleteOldResources" + }, + "location": { + "value": "[parameters('location')]" + }, + "kind": { + "value": "AzurePowerShell" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "azPowerShellVersion": { + "value": "9.7" + }, + "retentionInterval": { + "value": "PT1H" + }, + "cleanupPreference": { + "value": "OnSuccess" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "managedIdentities": { + "value": { + "userAssignedResourcesIds": [ + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('dataFactoryName')))]" + ] + } + }, + "environmentVariables": { + "value": { + "secureList": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('dataFactoryName')]" + } + ] + } + } + }, + "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.25.53.49325", + "templateHash": "18265939959786496245" + }, + "name": "Deployment Scripts", + "description": "This module deploys Deployment Scripts.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "managedIdentitiesType": { + "type": "object", + "properties": { + "userAssignedResourcesIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "environmentVariableType": { + "type": "secureObject", + "properties": { + "secureList": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "secureValue": { + "type": "string", + "nullable": true + }, + "value": { + "type": "string", + "nullable": true + } + } + }, + "metadata": { + "description": "Optional. The list of environment variables to pass over to the deployment script." + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Name of the Deployment Script." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "AzureCLI", + "AzurePowerShell" + ], + "metadata": { + "description": "Required. Specifies the Kind of the Deployment Script." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "azPowerShellVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure PowerShell module version to be used. See a list of supported Azure PowerShell versions: https://mcr.microsoft.com/v2/azuredeploymentscripts-powershell/tags/list." + } + }, + "azCliVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure CLI module version to be used. See a list of supported Azure CLI versions: https://mcr.microsoft.com/v2/azure-cli/tags/list." + } + }, + "scriptContent": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Script body. Max length: 32000 characters. To run an external script, use primaryScriptURI instead." + } + }, + "primaryScriptUri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Uri for the external script. This is the entry point for the external script. To run an internal script, use the scriptContent parameter instead." + } + }, + "environmentVariables": { + "$ref": "#/definitions/environmentVariableType", + "metadata": { + "description": "Optional. The environment variables to pass over to the script. The list is passed as an object with a key name \"secureList\" and the value is the list of environment variables (array). The list must have a 'name' and a 'value' or a 'secretValue' property for each object." + } + }, + "supportingScriptUris": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. List of supporting files for the external script (defined in primaryScriptUri). Does not work with internal scripts (code defined in scriptContent)." + } + }, + "subnetResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of subnet IDs to use for the container group. This is required if you want to run the deployment script in a private network. When using a private network, the `Storage File Data Privileged Contributor` role needs to be assigned to the user-assigned managed identity and the deployment principal needs to have permissions to list the storage account keys. Also, Shared-Keys must not be disabled on the used storage account [ref](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-vnet)." + } + }, + "arguments": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Command-line arguments to pass to the script. Arguments are separated by spaces." + } + }, + "retentionInterval": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires. Duration is based on ISO 8601 pattern (for example P7D means one week)." + } + }, + "baseTime": { + "type": "string", + "defaultValue": "[utcNow('yyyy-MM-dd-HH-mm-ss')]", + "metadata": { + "description": "Generated. Do not provide a value! This date value is used to make sure the script run every time the template is deployed." + } + }, + "runOnce": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. When set to false, script will run every time the template is deployed. When set to true, the script will only run once." + } + }, + "cleanupPreference": { + "type": "string", + "defaultValue": "Always", + "allowedValues": [ + "Always", + "OnSuccess", + "OnExpiration" + ], + "metadata": { + "description": "Optional. The clean up preference when the script execution gets in a terminal state. Specify the preference on when to delete the deployment script resources. The default value is Always, which means the deployment script resources are deleted despite the terminal state (Succeeded, Failed, canceled)." + } + }, + "containerGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Container group name, if not specified then the name will get auto-generated. Not specifying a 'containerGroupName' indicates the system to generate a unique name which might end up flagging an Azure Policy as non-compliant. Use 'containerGroupName' when you have an Azure Policy that expects a specific naming convention or when you want to fully control the name. 'containerGroupName' property must be between 1 and 63 characters long, must contain only lowercase letters, numbers, and dashes and it cannot start or end with a dash and consecutive dashes are not allowed." + } + }, + "storageAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the storage account to use for this deployment script. If none is provided, the deployment script uses a temporary, managed storage account." + } + }, + "timeout": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Maximum allowed script execution time specified in ISO 8601 format. Default value is PT1H - 1 hour; 'PT30M' - 30 minutes; 'P5D' - 5 days; 'P1Y' 1 year." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "subnetIds", + "count": "[length(coalesce(parameters('subnetResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('subnetResourceIds'), createArray())[copyIndex('subnetIds')]]" + } + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + }, + "containerSettings": { + "containerGroupName": "[parameters('containerGroupName')]", + "subnetIds": "[if(not(empty(coalesce(variables('subnetIds'), createArray()))), variables('subnetIds'), null())]" + }, + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createObject()))), 'UserAssigned', null()), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]" + }, + "resources": { + "storageAccount": { + "condition": "[not(empty(parameters('storageAccountResourceId')))]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-04-01", + "subscriptionId": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]]", + "name": "[last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))]" + }, + "deploymentScript_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "deploymentScript_roleAssignments": { + "copy": { + "name": "deploymentScript_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Resources/deploymentScripts', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.resources-deploymentscript.{0}.{1}', replace('0.2.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "deploymentScript": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "kind": "[parameters('kind')]", + "properties": { + "azPowerShellVersion": "[if(equals(parameters('kind'), 'AzurePowerShell'), parameters('azPowerShellVersion'), null())]", + "azCliVersion": "[if(equals(parameters('kind'), 'AzureCLI'), parameters('azCliVersion'), null())]", + "containerSettings": "[if(not(empty(variables('containerSettings'))), variables('containerSettings'), null())]", + "storageAccountSettings": "[if(not(empty(parameters('storageAccountResourceId'))), if(not(empty(parameters('storageAccountResourceId'))), createObject('storageAccountKey', if(empty(parameters('subnetResourceIds')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2], split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))), '2023-01-01').keys[0].value, null()), 'storageAccountName', last(split(parameters('storageAccountResourceId'), '/'))), null()), null())]", + "arguments": "[parameters('arguments')]", + "environmentVariables": "[if(not(equals(parameters('environmentVariables'), null())), parameters('environmentVariables').secureList, createArray())]", + "scriptContent": "[if(not(empty(parameters('scriptContent'))), parameters('scriptContent'), null())]", + "primaryScriptUri": "[if(not(empty(parameters('primaryScriptUri'))), parameters('primaryScriptUri'), null())]", + "supportingScriptUris": "[if(not(empty(parameters('supportingScriptUris'))), parameters('supportingScriptUris'), null())]", + "cleanupPreference": "[parameters('cleanupPreference')]", + "forceUpdateTag": "[if(parameters('runOnce'), resourceGroup().name, parameters('baseTime'))]", + "retentionInterval": "[parameters('retentionInterval')]", + "timeout": "[parameters('timeout')]" + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployment script." + }, + "value": "[resourceId('Microsoft.Resources/deploymentScripts', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the deployment script was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployment script." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('deploymentScript', '2023-08-01', 'full').location]" + }, + "outputs": { + "type": "object", + "metadata": { + "description": "The output of the deployment script." + }, + "value": "[if(contains(reference('deploymentScript'), 'outputs'), reference('deploymentScript').outputs, createObject())]" + } + } + } + }, + "dependsOn": [ + "dataFactory", + "identity", + "identityRoleAssignments" + ] + }, + "stopHubTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-stopHubTriggers', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "stopHubTriggers" + }, + "location": { + "value": "[parameters('location')]" + }, + "kind": { + "value": "AzurePowerShell" + }, + "tags": { + "value": "[union(coalesce(parameters('tags'), createObject()), if(contains(parameters('tagsByResource'), 'Microsoft.Resources/deploymentScripts'), parameters('tagsByResource')['Microsoft.Resources/deploymentScripts'], createObject()))]" + }, + "azPowerShellVersion": { + "value": "9.7" + }, + "retentionInterval": { + "value": "PT1H" + }, + "cleanupPreference": { + "value": "OnSuccess" + }, + "scriptContent": { + "value": "[variables('$fxv#1')]" + }, + "arguments": { + "value": "-Stop" + }, + "environmentVariables": { + "value": { + "secureList": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('dataFactoryName')]" + }, + { + "name": "Triggers", + "value": "[join(variables('allHubTriggers'), '|')]" + } + ] + } + }, + "managedIdentities": { + "value": { + "userAssignedResourcesIds": [ + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('dataFactoryName')))]" + ] + } + } + }, + "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.25.53.49325", + "templateHash": "18265939959786496245" + }, + "name": "Deployment Scripts", + "description": "This module deploys Deployment Scripts.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "managedIdentitiesType": { + "type": "object", + "properties": { + "userAssignedResourcesIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "environmentVariableType": { + "type": "secureObject", + "properties": { + "secureList": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "secureValue": { + "type": "string", + "nullable": true + }, + "value": { + "type": "string", + "nullable": true + } + } + }, + "metadata": { + "description": "Optional. The list of environment variables to pass over to the deployment script." + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Name of the Deployment Script." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "AzureCLI", + "AzurePowerShell" + ], + "metadata": { + "description": "Required. Specifies the Kind of the Deployment Script." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "azPowerShellVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure PowerShell module version to be used. See a list of supported Azure PowerShell versions: https://mcr.microsoft.com/v2/azuredeploymentscripts-powershell/tags/list." + } + }, + "azCliVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure CLI module version to be used. See a list of supported Azure CLI versions: https://mcr.microsoft.com/v2/azure-cli/tags/list." + } + }, + "scriptContent": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Script body. Max length: 32000 characters. To run an external script, use primaryScriptURI instead." + } + }, + "primaryScriptUri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Uri for the external script. This is the entry point for the external script. To run an internal script, use the scriptContent parameter instead." + } + }, + "environmentVariables": { + "$ref": "#/definitions/environmentVariableType", + "metadata": { + "description": "Optional. The environment variables to pass over to the script. The list is passed as an object with a key name \"secureList\" and the value is the list of environment variables (array). The list must have a 'name' and a 'value' or a 'secretValue' property for each object." + } + }, + "supportingScriptUris": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. List of supporting files for the external script (defined in primaryScriptUri). Does not work with internal scripts (code defined in scriptContent)." + } + }, + "subnetResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of subnet IDs to use for the container group. This is required if you want to run the deployment script in a private network. When using a private network, the `Storage File Data Privileged Contributor` role needs to be assigned to the user-assigned managed identity and the deployment principal needs to have permissions to list the storage account keys. Also, Shared-Keys must not be disabled on the used storage account [ref](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-vnet)." + } + }, + "arguments": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Command-line arguments to pass to the script. Arguments are separated by spaces." + } + }, + "retentionInterval": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires. Duration is based on ISO 8601 pattern (for example P7D means one week)." + } + }, + "baseTime": { + "type": "string", + "defaultValue": "[utcNow('yyyy-MM-dd-HH-mm-ss')]", + "metadata": { + "description": "Generated. Do not provide a value! This date value is used to make sure the script run every time the template is deployed." + } + }, + "runOnce": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. When set to false, script will run every time the template is deployed. When set to true, the script will only run once." + } + }, + "cleanupPreference": { + "type": "string", + "defaultValue": "Always", + "allowedValues": [ + "Always", + "OnSuccess", + "OnExpiration" + ], + "metadata": { + "description": "Optional. The clean up preference when the script execution gets in a terminal state. Specify the preference on when to delete the deployment script resources. The default value is Always, which means the deployment script resources are deleted despite the terminal state (Succeeded, Failed, canceled)." + } + }, + "containerGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Container group name, if not specified then the name will get auto-generated. Not specifying a 'containerGroupName' indicates the system to generate a unique name which might end up flagging an Azure Policy as non-compliant. Use 'containerGroupName' when you have an Azure Policy that expects a specific naming convention or when you want to fully control the name. 'containerGroupName' property must be between 1 and 63 characters long, must contain only lowercase letters, numbers, and dashes and it cannot start or end with a dash and consecutive dashes are not allowed." + } + }, + "storageAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the storage account to use for this deployment script. If none is provided, the deployment script uses a temporary, managed storage account." + } + }, + "timeout": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Maximum allowed script execution time specified in ISO 8601 format. Default value is PT1H - 1 hour; 'PT30M' - 30 minutes; 'P5D' - 5 days; 'P1Y' 1 year." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "subnetIds", + "count": "[length(coalesce(parameters('subnetResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('subnetResourceIds'), createArray())[copyIndex('subnetIds')]]" + } + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + }, + "containerSettings": { + "containerGroupName": "[parameters('containerGroupName')]", + "subnetIds": "[if(not(empty(coalesce(variables('subnetIds'), createArray()))), variables('subnetIds'), null())]" + }, + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createObject()))), 'UserAssigned', null()), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]" + }, + "resources": { + "storageAccount": { + "condition": "[not(empty(parameters('storageAccountResourceId')))]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-04-01", + "subscriptionId": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]]", + "name": "[last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))]" + }, + "deploymentScript_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "deploymentScript_roleAssignments": { + "copy": { + "name": "deploymentScript_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Resources/deploymentScripts', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.resources-deploymentscript.{0}.{1}', replace('0.2.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "deploymentScript": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "kind": "[parameters('kind')]", + "properties": { + "azPowerShellVersion": "[if(equals(parameters('kind'), 'AzurePowerShell'), parameters('azPowerShellVersion'), null())]", + "azCliVersion": "[if(equals(parameters('kind'), 'AzureCLI'), parameters('azCliVersion'), null())]", + "containerSettings": "[if(not(empty(variables('containerSettings'))), variables('containerSettings'), null())]", + "storageAccountSettings": "[if(not(empty(parameters('storageAccountResourceId'))), if(not(empty(parameters('storageAccountResourceId'))), createObject('storageAccountKey', if(empty(parameters('subnetResourceIds')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2], split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))), '2023-01-01').keys[0].value, null()), 'storageAccountName', last(split(parameters('storageAccountResourceId'), '/'))), null()), null())]", + "arguments": "[parameters('arguments')]", + "environmentVariables": "[if(not(equals(parameters('environmentVariables'), null())), parameters('environmentVariables').secureList, createArray())]", + "scriptContent": "[if(not(empty(parameters('scriptContent'))), parameters('scriptContent'), null())]", + "primaryScriptUri": "[if(not(empty(parameters('primaryScriptUri'))), parameters('primaryScriptUri'), null())]", + "supportingScriptUris": "[if(not(empty(parameters('supportingScriptUris'))), parameters('supportingScriptUris'), null())]", + "cleanupPreference": "[parameters('cleanupPreference')]", + "forceUpdateTag": "[if(parameters('runOnce'), resourceGroup().name, parameters('baseTime'))]", + "retentionInterval": "[parameters('retentionInterval')]", + "timeout": "[parameters('timeout')]" + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployment script." + }, + "value": "[resourceId('Microsoft.Resources/deploymentScripts', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the deployment script was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployment script." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('deploymentScript', '2023-08-01', 'full').location]" + }, + "outputs": { + "type": "object", + "metadata": { + "description": "The output of the deployment script." + }, + "value": "[if(contains(reference('deploymentScript'), 'outputs'), reference('deploymentScript').outputs, createObject())]" + } + } + } + }, + "dependsOn": [ + "dataFactory", + "identity", + "identityRoleAssignments" + ] + }, + "startHubTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-startHubTriggers', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "startHubTriggers" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[union(coalesce(parameters('tags'), createObject()), if(contains(parameters('tagsByResource'), 'Microsoft.Resources/deploymentScripts'), parameters('tagsByResource')['Microsoft.Resources/deploymentScripts'], createObject()))]" + }, + "kind": { + "value": "AzurePowerShell" + }, + "azPowerShellVersion": { + "value": "9.7" + }, + "retentionInterval": { + "value": "PT1H" + }, + "cleanupPreference": { + "value": "OnSuccess" + }, + "managedIdentities": { + "value": { + "userAssignedResourcesIds": [ + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('dataFactoryName')))]" + ] + } + }, + "scriptContent": { + "value": "[variables('$fxv#2')]" + }, + "environmentVariables": { + "value": { + "secureList": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('dataFactoryName')]" + }, + { + "name": "Triggers", + "value": "[join(variables('allHubTriggers'), '|')]" + } + ] + } + } + }, + "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.25.53.49325", + "templateHash": "18265939959786496245" + }, + "name": "Deployment Scripts", + "description": "This module deploys Deployment Scripts.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "managedIdentitiesType": { + "type": "object", + "properties": { + "userAssignedResourcesIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "environmentVariableType": { + "type": "secureObject", + "properties": { + "secureList": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "secureValue": { + "type": "string", + "nullable": true + }, + "value": { + "type": "string", + "nullable": true + } + } + }, + "metadata": { + "description": "Optional. The list of environment variables to pass over to the deployment script." + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Name of the Deployment Script." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "AzureCLI", + "AzurePowerShell" + ], + "metadata": { + "description": "Required. Specifies the Kind of the Deployment Script." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "azPowerShellVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure PowerShell module version to be used. See a list of supported Azure PowerShell versions: https://mcr.microsoft.com/v2/azuredeploymentscripts-powershell/tags/list." + } + }, + "azCliVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure CLI module version to be used. See a list of supported Azure CLI versions: https://mcr.microsoft.com/v2/azure-cli/tags/list." + } + }, + "scriptContent": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Script body. Max length: 32000 characters. To run an external script, use primaryScriptURI instead." + } + }, + "primaryScriptUri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Uri for the external script. This is the entry point for the external script. To run an internal script, use the scriptContent parameter instead." + } + }, + "environmentVariables": { + "$ref": "#/definitions/environmentVariableType", + "metadata": { + "description": "Optional. The environment variables to pass over to the script. The list is passed as an object with a key name \"secureList\" and the value is the list of environment variables (array). The list must have a 'name' and a 'value' or a 'secretValue' property for each object." + } + }, + "supportingScriptUris": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. List of supporting files for the external script (defined in primaryScriptUri). Does not work with internal scripts (code defined in scriptContent)." + } + }, + "subnetResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of subnet IDs to use for the container group. This is required if you want to run the deployment script in a private network. When using a private network, the `Storage File Data Privileged Contributor` role needs to be assigned to the user-assigned managed identity and the deployment principal needs to have permissions to list the storage account keys. Also, Shared-Keys must not be disabled on the used storage account [ref](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-vnet)." + } + }, + "arguments": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Command-line arguments to pass to the script. Arguments are separated by spaces." + } + }, + "retentionInterval": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires. Duration is based on ISO 8601 pattern (for example P7D means one week)." + } + }, + "baseTime": { + "type": "string", + "defaultValue": "[utcNow('yyyy-MM-dd-HH-mm-ss')]", + "metadata": { + "description": "Generated. Do not provide a value! This date value is used to make sure the script run every time the template is deployed." + } + }, + "runOnce": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. When set to false, script will run every time the template is deployed. When set to true, the script will only run once." + } + }, + "cleanupPreference": { + "type": "string", + "defaultValue": "Always", + "allowedValues": [ + "Always", + "OnSuccess", + "OnExpiration" + ], + "metadata": { + "description": "Optional. The clean up preference when the script execution gets in a terminal state. Specify the preference on when to delete the deployment script resources. The default value is Always, which means the deployment script resources are deleted despite the terminal state (Succeeded, Failed, canceled)." + } + }, + "containerGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Container group name, if not specified then the name will get auto-generated. Not specifying a 'containerGroupName' indicates the system to generate a unique name which might end up flagging an Azure Policy as non-compliant. Use 'containerGroupName' when you have an Azure Policy that expects a specific naming convention or when you want to fully control the name. 'containerGroupName' property must be between 1 and 63 characters long, must contain only lowercase letters, numbers, and dashes and it cannot start or end with a dash and consecutive dashes are not allowed." + } + }, + "storageAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the storage account to use for this deployment script. If none is provided, the deployment script uses a temporary, managed storage account." + } + }, + "timeout": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Maximum allowed script execution time specified in ISO 8601 format. Default value is PT1H - 1 hour; 'PT30M' - 30 minutes; 'P5D' - 5 days; 'P1Y' 1 year." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "subnetIds", + "count": "[length(coalesce(parameters('subnetResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('subnetResourceIds'), createArray())[copyIndex('subnetIds')]]" + } + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + }, + "containerSettings": { + "containerGroupName": "[parameters('containerGroupName')]", + "subnetIds": "[if(not(empty(coalesce(variables('subnetIds'), createArray()))), variables('subnetIds'), null())]" + }, + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createObject()))), 'UserAssigned', null()), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]" + }, + "resources": { + "storageAccount": { + "condition": "[not(empty(parameters('storageAccountResourceId')))]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-04-01", + "subscriptionId": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]]", + "name": "[last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))]" + }, + "deploymentScript_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "deploymentScript_roleAssignments": { + "copy": { + "name": "deploymentScript_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Resources/deploymentScripts', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.resources-deploymentscript.{0}.{1}', replace('0.2.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "deploymentScript": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "kind": "[parameters('kind')]", + "properties": { + "azPowerShellVersion": "[if(equals(parameters('kind'), 'AzurePowerShell'), parameters('azPowerShellVersion'), null())]", + "azCliVersion": "[if(equals(parameters('kind'), 'AzureCLI'), parameters('azCliVersion'), null())]", + "containerSettings": "[if(not(empty(variables('containerSettings'))), variables('containerSettings'), null())]", + "storageAccountSettings": "[if(not(empty(parameters('storageAccountResourceId'))), if(not(empty(parameters('storageAccountResourceId'))), createObject('storageAccountKey', if(empty(parameters('subnetResourceIds')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2], split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))), '2023-01-01').keys[0].value, null()), 'storageAccountName', last(split(parameters('storageAccountResourceId'), '/'))), null()), null())]", + "arguments": "[parameters('arguments')]", + "environmentVariables": "[if(not(equals(parameters('environmentVariables'), null())), parameters('environmentVariables').secureList, createArray())]", + "scriptContent": "[if(not(empty(parameters('scriptContent'))), parameters('scriptContent'), null())]", + "primaryScriptUri": "[if(not(empty(parameters('primaryScriptUri'))), parameters('primaryScriptUri'), null())]", + "supportingScriptUris": "[if(not(empty(parameters('supportingScriptUris'))), parameters('supportingScriptUris'), null())]", + "cleanupPreference": "[parameters('cleanupPreference')]", + "forceUpdateTag": "[if(parameters('runOnce'), resourceGroup().name, parameters('baseTime'))]", + "retentionInterval": "[parameters('retentionInterval')]", + "timeout": "[parameters('timeout')]" + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployment script." + }, + "value": "[resourceId('Microsoft.Resources/deploymentScripts', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the deployment script was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployment script." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('deploymentScript', '2023-08-01', 'full').location]" + }, + "outputs": { + "type": "object", + "metadata": { + "description": "The output of the deployment script." + }, + "value": "[if(contains(reference('deploymentScript'), 'outputs'), reference('deploymentScript').outputs, createObject())]" + } + } + } + }, + "dependsOn": [ + "dataFactory", + "identity", + "identityRoleAssignments", + "trigger_msexports_FileAdded" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The Resource ID of the Data factory." + }, + "value": "[resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The Name of the Azure Data Factory instance." + }, + "value": "[parameters('dataFactoryName')]" + } + } + } + }, + "dependsOn": [ + "keyVault", + "storage" + ] + }, + "keyVault": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-keyVault', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "hubName": { + "value": "[parameters('hubName')]" + }, + "uniqueSuffix": { + "value": "[variables('uniqueSuffix')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('resourceTags')]" + }, + "tagsByResource": { + "value": "[parameters('tagsByResource')]" + }, + "storageAccountName": { + "value": "[reference('storage').outputs.name.value]" + }, + "accessPolicies": { + "value": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ] + } + }, + "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.26.170.59819", + "templateHash": "18290965054356591240" + } + }, + "parameters": { + "hubName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Required. Name of the hub. Used to ensure unique resource names." + } + }, + "uniqueSuffix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Required. Suffix to add to the KeyVault instance name to ensure uniqueness." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "accessPolicies": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of access policies object." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account to store access keys for." + } + }, + "sku": { + "type": "string", + "defaultValue": "premium", + "allowedValues": [ + "premium", + "standard" + ], + "metadata": { + "description": "Optional. Specifies the SKU for the vault." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "tagsByResource": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedAccessPolicies", + "count": "[length(parameters('accessPolicies'))]", + "input": { + "applicationId": "[if(contains(parameters('accessPolicies')[copyIndex('formattedAccessPolicies')], 'applicationId'), parameters('accessPolicies')[copyIndex('formattedAccessPolicies')].applicationId, '')]", + "objectId": "[if(contains(parameters('accessPolicies')[copyIndex('formattedAccessPolicies')], 'objectId'), parameters('accessPolicies')[copyIndex('formattedAccessPolicies')].objectId, '')]", + "permissions": "[parameters('accessPolicies')[copyIndex('formattedAccessPolicies')].permissions]", + "tenantId": "[if(contains(parameters('accessPolicies')[copyIndex('formattedAccessPolicies')], 'tenantId'), parameters('accessPolicies')[copyIndex('formattedAccessPolicies')].tenantId, tenant().tenantId)]" + } + } + ], + "keyVaultPrefix": "[format('{0}-vault', replace(parameters('hubName'), '_', '-'))]", + "keyVaultSuffix": "[format('-{0}', parameters('uniqueSuffix'))]", + "keyVaultName": "[replace(format('{0}{1}', take(variables('keyVaultPrefix'), sub(24, length(variables('keyVaultSuffix')))), variables('keyVaultSuffix')), '--', '-')]" + }, + "resources": { + "storageRef": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-01-01", + "name": "[parameters('storageAccountName')]" + }, + "keyVault": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-keyvault', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('keyVaultName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[union(coalesce(parameters('tags'), createObject()), if(contains(parameters('tagsByResource'), 'Microsoft.KeyVault/vaults'), parameters('tagsByResource')['Microsoft.KeyVault/vaults'], createObject()))]" + }, + "enableVaultForDeployment": { + "value": true + }, + "enableVaultForTemplateDeployment": { + "value": true + }, + "enableVaultForDiskEncryption": { + "value": true + }, + "enablePurgeProtection": { + "value": false + }, + "enableSoftDelete": { + "value": true + }, + "softDeleteRetentionInDays": { + "value": 90 + }, + "enableRbacAuthorization": { + "value": false + }, + "createMode": { + "value": "default" + }, + "sku": "[if(startsWith(parameters('location'), 'china'), createObject('value', 'standard'), createObject('value', parameters('sku')))]", + "accessPolicies": { + "value": "[variables('formattedAccessPolicies')]" + }, + "secrets": { + "value": { + "secureList": [ + { + "name": "[parameters('storageAccountName')]", + "value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-01-01').keys[0].value]", + "attributesExp": 1702648632, + "attributesNbf": 10000 + } + ] + } + } + }, + "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.26.54.24096", + "templateHash": "12538315610403519820" + }, + "name": "Key Vaults", + "description": "This module deploys a Key Vault.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subresource to deploy the private endpoint for. For example \"vault\", \"mysqlServer\" or \"dataFactory\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if `privateDnsZoneResourceIds` were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "manualConnectionRequestMessage": { + "type": "string", + "nullable": true, + "maxLength": 140, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with the manual connection request." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "resourceGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify if you want to deploy the Private Endpoint into a different resource group than the main resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "accessPoliciesType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tenant ID that is used for authenticating requests to the key vault." + } + }, + "objectId": { + "type": "string", + "metadata": { + "description": "Required. The object ID of a user, service principal or security group in the tenant for the vault." + } + }, + "applicationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Application ID of the client making request on behalf of a principal." + } + }, + "permissions": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "decrypt", + "delete", + "encrypt", + "get", + "getrotationpolicy", + "import", + "list", + "purge", + "recover", + "release", + "restore", + "rotate", + "setrotationpolicy", + "sign", + "unwrapKey", + "update", + "verify", + "wrapKey" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to keys." + } + }, + "secrets": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "get", + "list", + "purge", + "recover", + "restore", + "set" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to secrets." + } + }, + "certificates": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "delete", + "deleteissuers", + "get", + "getissuers", + "import", + "list", + "listissuers", + "managecontacts", + "manageissuers", + "purge", + "recover", + "restore", + "setissuers", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to certificates." + } + }, + "storage": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "deletesas", + "get", + "getsas", + "list", + "listsas", + "purge", + "recover", + "regeneratekey", + "restore", + "set", + "setsas", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to storage accounts." + } + } + }, + "metadata": { + "description": "Required. Permissions the identity has for keys, secrets and certificates." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Name of the Key Vault. Must be globally unique." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "accessPolicies": { + "$ref": "#/definitions/accessPoliciesType", + "metadata": { + "description": "Optional. All access policies to create." + } + }, + "secrets": { + "type": "secureObject", + "nullable": true, + "metadata": { + "description": "Optional. All secrets to create." + } + }, + "keys": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. All keys to create." + } + }, + "enableVaultForDeployment": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the vault is enabled for deployment by script or compute." + } + }, + "enableVaultForTemplateDeployment": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the vault is enabled for a template deployment." + } + }, + "enableVaultForDiskEncryption": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the azure platform has access to the vault for enabling disk encryption scenarios." + } + }, + "enableSoftDelete": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Switch to enable/disable Key Vault's soft delete feature." + } + }, + "softDeleteRetentionInDays": { + "type": "int", + "defaultValue": 90, + "metadata": { + "description": "Optional. softDelete data retention days. It accepts >=7 and <=90." + } + }, + "enableRbacAuthorization": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Property that controls how data actions are authorized. When true, the key vault will use Role Based Access Control (RBAC) for authorization of data actions, and the access policies specified in vault properties will be ignored. When false, the key vault will use the access policies specified in vault properties, and any policy stored on Azure Resource Manager will be ignored. Note that management actions are always authorized with RBAC." + } + }, + "createMode": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The vault's create mode to indicate whether the vault need to be recovered or not. - recover or default." + } + }, + "enablePurgeProtection": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Provide 'true' to enable Key Vault's purge protection feature." + } + }, + "sku": { + "type": "string", + "defaultValue": "premium", + "allowedValues": [ + "premium", + "standard" + ], + "metadata": { + "description": "Optional. Specifies the SKU for the vault." + } + }, + "networkAcls": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Rules governing the accessibility of the resource from specific network locations." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "", + "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 and networkAcls are not set." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "privateEndpoints": { + "$ref": "#/definitions/privateEndpointType", + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedAccessPolicies", + "count": "[length(coalesce(parameters('accessPolicies'), createArray()))]", + "input": { + "applicationId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'applicationId'), '')]", + "objectId": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].objectId]", + "permissions": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].permissions]", + "tenantId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'tenantId'), tenant().tenantId)]" + } + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", + "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", + "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + }, + "secretList": "[coalesce(tryGet(parameters('secrets'), 'secureList'), createArray())]" + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.keyvault-vault.{0}.{1}', replace('0.5.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "keyVault": { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "enabledForDeployment": "[parameters('enableVaultForDeployment')]", + "enabledForTemplateDeployment": "[parameters('enableVaultForTemplateDeployment')]", + "enabledForDiskEncryption": "[parameters('enableVaultForDiskEncryption')]", + "enableSoftDelete": "[parameters('enableSoftDelete')]", + "softDeleteRetentionInDays": "[parameters('softDeleteRetentionInDays')]", + "enableRbacAuthorization": "[parameters('enableRbacAuthorization')]", + "createMode": "[parameters('createMode')]", + "enablePurgeProtection": "[if(parameters('enablePurgeProtection'), parameters('enablePurgeProtection'), null())]", + "tenantId": "[subscription().tenantId]", + "accessPolicies": "[variables('formattedAccessPolicies')]", + "sku": { + "name": "[parameters('sku')]", + "family": "A" + }, + "networkAcls": "[if(not(empty(coalesce(parameters('networkAcls'), createObject()))), createObject('bypass', tryGet(parameters('networkAcls'), 'bypass'), 'defaultAction', tryGet(parameters('networkAcls'), 'defaultAction'), 'virtualNetworkRules', coalesce(tryGet(parameters('networkAcls'), 'virtualNetworkRules'), createArray()), 'ipRules', coalesce(tryGet(parameters('networkAcls'), 'ipRules'), createArray())), null())]", + "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(not(empty(coalesce(parameters('privateEndpoints'), createArray()))), empty(coalesce(parameters('networkAcls'), createObject()))), 'Disabled', null()))]" + } + }, + "keyVault_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_diagnosticSettings": { + "copy": { + "name": "keyVault_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[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')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_roleAssignments": { + "copy": { + "name": "keyVault_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.KeyVault/vaults', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_accessPolicies": { + "condition": "[not(empty(parameters('accessPolicies')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-AccessPolicies', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('name')]" + }, + "accessPolicies": { + "value": "[parameters('accessPolicies')]" + } + }, + "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.26.54.24096", + "templateHash": "10878813547461142217" + }, + "name": "Key Vault Access Policies", + "description": "This module deploys a Key Vault Access Policy.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "accessPoliciesType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tenant ID that is used for authenticating requests to the key vault." + } + }, + "objectId": { + "type": "string", + "metadata": { + "description": "Required. The object ID of a user, service principal or security group in the tenant for the vault." + } + }, + "applicationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Application ID of the client making request on behalf of a principal." + } + }, + "permissions": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "decrypt", + "delete", + "encrypt", + "get", + "getrotationpolicy", + "import", + "list", + "purge", + "recover", + "release", + "restore", + "rotate", + "setrotationpolicy", + "sign", + "unwrapKey", + "update", + "verify", + "wrapKey" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to keys." + } + }, + "secrets": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "get", + "list", + "purge", + "recover", + "restore", + "set" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to secrets." + } + }, + "certificates": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "delete", + "deleteissuers", + "get", + "getissuers", + "import", + "list", + "listissuers", + "managecontacts", + "manageissuers", + "purge", + "recover", + "restore", + "setissuers", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to certificates." + } + }, + "storage": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "deletesas", + "get", + "getsas", + "list", + "listsas", + "purge", + "recover", + "regeneratekey", + "restore", + "set", + "setsas", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to storage accounts." + } + } + }, + "metadata": { + "description": "Required. Permissions the identity has for keys, secrets and certificates." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "accessPolicies": { + "$ref": "#/definitions/accessPoliciesType", + "metadata": { + "description": "Optional. An array of 0 to 16 identities that have access to the key vault. All identities in the array must use the same tenant ID as the key vault's tenant ID." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedAccessPolicies", + "count": "[length(coalesce(parameters('accessPolicies'), createArray()))]", + "input": { + "applicationId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'applicationId'), '')]", + "objectId": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].objectId]", + "permissions": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].permissions]", + "tenantId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'tenantId'), tenant().tenantId)]" + } + } + ] + }, + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "policies": { + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), 'add')]", + "properties": { + "accessPolicies": "[variables('formattedAccessPolicies')]" + }, + "dependsOn": [ + "keyVault" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the access policies assignment was created in." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the access policies assignment." + }, + "value": "add" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the access policies assignment." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/accessPolicies', parameters('keyVaultName'), 'add')]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_secrets": { + "copy": { + "name": "keyVault_secrets", + "count": "[length(variables('secretList'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-Secret-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('secretList')[copyIndex()].name]" + }, + "value": { + "value": "[variables('secretList')[copyIndex()].value]" + }, + "keyVaultName": { + "value": "[parameters('name')]" + }, + "attributesEnabled": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'attributesEnabled')]" + }, + "attributesExp": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'attributesExp')]" + }, + "attributesNbf": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'attributesNbf')]" + }, + "contentType": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'contentType')]" + }, + "tags": { + "value": "[coalesce(tryGet(variables('secretList')[copyIndex()], 'tags'), parameters('tags'))]" + }, + "roleAssignments": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'roleAssignments')]" + } + }, + "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.26.54.24096", + "templateHash": "1877278864243602204" + }, + "name": "Key Vault Secrets", + "description": "This module deploys a Key Vault Secret.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the secret." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributesEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Determines whether the object is enabled." + } + }, + "attributesExp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Expiry date in seconds since 1970-01-01T00:00:00Z. For security reasons, it is recommended to set an expiration date whenever possible." + } + }, + "attributesNbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Not before date in seconds since 1970-01-01T00:00:00Z." + } + }, + "contentType": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. The content type of the secret." + } + }, + "value": { + "type": "securestring", + "metadata": { + "description": "Required. The value of the secret. NOTE: \"value\" will never be returned from the service, as APIs using this model are is intended for internal use in ARM deployments. Users should use the data-plane REST service for interaction with vault secrets." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", + "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", + "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "secret": { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "contentType": "[parameters('contentType')]", + "attributes": { + "enabled": "[parameters('attributesEnabled')]", + "exp": "[parameters('attributesExp')]", + "nbf": "[parameters('attributesNbf')]" + }, + "value": "[parameters('value')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "secret_roleAssignments": { + "copy": { + "name": "secret_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}/secrets/{1}', parameters('keyVaultName'), parameters('name'))]", + "name": "[guid(resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "secret" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the secret." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the secret." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the secret was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_keys": { + "copy": { + "name": "keyVault_keys", + "count": "[length(coalesce(parameters('keys'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-Key-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('keys'), createArray())[copyIndex()].name]" + }, + "keyVaultName": { + "value": "[parameters('name')]" + }, + "attributesEnabled": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributesEnabled')]" + }, + "attributesExp": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributesExp')]" + }, + "attributesNbf": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributesNbf')]" + }, + "curveName": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'curveName'), 'P-256')]" + }, + "keyOps": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'keyOps')]" + }, + "keySize": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'keySize')]" + }, + "kty": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'EC')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "rotationPolicy": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'rotationPolicy')]" + } + }, + "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.26.54.24096", + "templateHash": "5903918450419813264" + }, + "name": "Key Vault Keys", + "description": "This module deploys a Key Vault Key.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the key." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributesEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Determines whether the object is enabled." + } + }, + "attributesExp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Expiry date in seconds since 1970-01-01T00:00:00Z. For security reasons, it is recommended to set an expiration date whenever possible." + } + }, + "attributesNbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Not before date in seconds since 1970-01-01T00:00:00Z." + } + }, + "curveName": { + "type": "string", + "defaultValue": "P-256", + "allowedValues": [ + "P-256", + "P-256K", + "P-384", + "P-521" + ], + "metadata": { + "description": "Optional. The elliptic curve name." + } + }, + "keyOps": { + "type": "array", + "nullable": true, + "allowedValues": [ + "decrypt", + "encrypt", + "import", + "sign", + "unwrapKey", + "verify", + "wrapKey" + ], + "metadata": { + "description": "Optional. Array of JsonWebKeyOperation." + } + }, + "keySize": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The key size in bits. For example: 2048, 3072, or 4096 for RSA." + } + }, + "kty": { + "type": "string", + "defaultValue": "EC", + "allowedValues": [ + "EC", + "EC-HSM", + "RSA", + "RSA-HSM" + ], + "metadata": { + "description": "Optional. The type of the key." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "rotationPolicy": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Key rotation policy properties object." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", + "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", + "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "key": { + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "attributes": { + "enabled": "[parameters('attributesEnabled')]", + "exp": "[parameters('attributesExp')]", + "nbf": "[parameters('attributesNbf')]" + }, + "curveName": "[parameters('curveName')]", + "keyOps": "[parameters('keyOps')]", + "keySize": "[parameters('keySize')]", + "kty": "[parameters('kty')]", + "rotationPolicy": "[parameters('rotationPolicy')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "key_roleAssignments": { + "copy": { + "name": "key_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}/keys/{1}', parameters('keyVaultName'), parameters('name'))]", + "name": "[guid(resourceId('Microsoft.KeyVault/vaults/keys', parameters('keyVaultName'), parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "key" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the key." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the key." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/keys', parameters('keyVaultName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the key was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_privateEndpoints": { + "copy": { + "name": "keyVault_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-keyVault-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "resourceGroup": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupName'), '')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.KeyVault/vaults', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault')))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.KeyVault/vaults', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault')), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]", + "subnetResourceId": { + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableTelemetry": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableTelemetry'), parameters('enableTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" + }, + "lock": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } + }, + "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.25.53.49325", + "templateHash": "4120048060064073955" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "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'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "ipConfigurationsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true + }, + "manualPrivateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "privateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "customDnsConfigType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the private endpoint resource to create." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "ipConfigurations": { + "$ref": "#/definitions/ipConfigurationsType", + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if `privateDnsZoneResourceIds` were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "customDnsConfigs": { + "$ref": "#/definitions/customDnsConfigType", + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "$ref": "#/definitions/manualPrivateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource." + } + }, + "privateLinkServiceConnections": { + "$ref": "#/definitions/privateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]", + "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]", + "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]", + "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.4.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "applicationSecurityGroups", + "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]" + } + } + ], + "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]", + "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]", + "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]", + "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]", + "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]", + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + } + }, + "privateEndpoint_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_roleAssignments": { + "copy": { + "name": "privateEndpoint_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneResourceIds')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('privateDnsZoneGroupName'), 'default')]" + }, + "privateDNSResourceIds": { + "value": "[coalesce(parameters('privateDnsZoneResourceIds'), createArray())]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "11244630631275470040" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDNSResourceIds": { + "type": "array", + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone resource IDs. A DNS zone group can support up to 5 DNS zones." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the private DNS zone group." + } + } + }, + "variables": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDNSResourceIds'))]", + "input": { + "name": "[last(split(parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "resources": [ + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigs')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint DNS zone group." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint DNS zone group." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint DNS zone group was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateEndpoint" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('privateEndpoint', '2023-04-01', 'full').location]" + }, + "groupId": { + "type": "string", + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[if(not(empty(reference('privateEndpoint').manualPrivateLinkServiceConnections)), reference('privateEndpoint').manualPrivateLinkServiceConnections[0].properties.groupIds[0], reference('privateEndpoint').privateLinkServiceConnections[0].properties.groupIds[0])]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the key vault." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the key vault was created in." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the key vault." + }, + "value": "[parameters('name')]" + }, + "uri": { + "type": "string", + "metadata": { + "description": "The URI of the key vault." + }, + "value": "[reference('keyVault').vaultUri]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('keyVault', '2022-07-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "storageRef" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the key vault." + }, + "value": "[reference('keyVault').outputs.resourceId.value]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the key vault." + }, + "value": "[reference('keyVault').outputs.name.value]" + }, + "uri": { + "type": "string", + "metadata": { + "description": "The URI of the key vault." + }, + "value": "[reference('keyVault').outputs.uri.value]" + } + } + } + }, + "dependsOn": [ + "dataFactory", + "storage" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the resource group." + }, + "value": "[parameters('hubName')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resources wer deployed to." + }, + "value": "[parameters('location')]" + }, + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Name of the Data Factory." + }, + "value": "[variables('dataFactoryName')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the finops hub was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "storageAccountId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed storage account." + }, + "value": "[reference('storage').outputs.resourceId.value]" + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Name of the storage account created for the hub instance. This must be used when connecting FinOps toolkit Power BI reports to your data." + }, + "value": "[reference('storage').outputs.name.value]" + }, + "storageUrlForPowerBi": { + "type": "string", + "metadata": { + "description": "URL to use when connecting custom Power BI reports to your data." + }, + "value": "[format('https://{0}.dfs.{1}/{2}', reference('storage').outputs.name.value, environment().suffixes.storage, parameters('ingestionContainer'))]" + } + } +} \ No newline at end of file diff --git a/avm/ptn/finops-toolkit/finops-hub/modules/dataFactory.bicep b/avm/ptn/finops-toolkit/finops-hub/modules/dataFactory.bicep new file mode 100644 index 0000000000..3e4a79fdbe --- /dev/null +++ b/avm/ptn/finops-toolkit/finops-hub/modules/dataFactory.bicep @@ -0,0 +1,925 @@ +//============================================================================== +// Parameters +//============================================================================== + +@description('Optional. Name of the hub. Used to ensure unique resource names. Default: "finops-hub".') +param dataFactoryName string = '' + +@description('Required. The name of the Azure Key Vault instance.') +param keyVaultName string = '' + +@description('Required. The name of the Azure storage account instance.') +param storageAccountName string = '' + +@description('Required. The name of the container where Cost Management data is exported.') +param exportContainerName string = '' + +@description('Required. The name of the container where normalized data is ingested.') +param ingestionContainerName string = '' + +@description('Optional. Indicates whether ingested data should be converted to Parquet. Default: true.') +param convertToParquet bool = true + +@description('Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location).') +param location string = resourceGroup().location + +@description('Optional. Tags to apply to all resources. We will also add the cm-resource-parent tag for improved cost roll-ups in Cost Management.') +param tags object? + +@description('Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources.') +param tagsByResource object = {} + +//------------------------------------------------------------------------------ +// Variables +//------------------------------------------------------------------------------ + +var datasetPropsDelimitedText = { + columnDelimiter: ',' + compressionLevel: 'Optimal' + escapeChar: '"' + firstRowAsHeader: true + quoteChar: '"' +} +var datasetPropsCommon = { + location: { + type: 'AzureBlobFSLocation' + fileName: { + value: '@{dataset().fileName}' + type: 'Expression' + } + folderPath: { + value: '@{dataset().folderName}' + type: 'Expression' + } + } +} + +var safeExportContainerName = replace('${exportContainerName}', '-', '_') +var safeIngestionContainerName = replace('${ingestionContainerName}', '-', '_') + +// All hub triggers (used to auto-start) +var extractExportTriggerName = exportContainerName +var allHubTriggers = [ + extractExportTriggerName +] + +// Roles needed to auto-start triggers +var autoStartRbacRoles = [ + '673868aa-7521-48a0-acc6-0f60742d39f5' // Data Factory contributor - https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#data-factory-contributor + 'e40ec5ca-96e0-45a2-b4ff-59039f2c2b59' // Managed Identity Contributor - https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#managed-identity-contributor +] + +// FocusCost 1.0-preview (v1) columns +var focusCostColumns = [ + { name: 'AvailabilityZone', type: 'String' } + { name: 'BilledCost', type: 'Decimal' } + { name: 'BillingAccountId', type: 'String' } + { name: 'BillingAccountName', type: 'String' } + { name: 'BillingAccountType', type: 'String' } + { name: 'BillingCurrency', type: 'String' } + { name: 'BillingPeriodEnd', type: 'DateTime' } + { name: 'BillingPeriodStart', type: 'DateTime' } + { name: 'ChargeCategory', type: 'String' } + { name: 'ChargeDescription', type: 'String' } + { name: 'ChargeFrequency', type: 'String' } + { name: 'ChargePeriodEnd', type: 'DateTime' } + { name: 'ChargePeriodStart', type: 'DateTime' } + { name: 'ChargeSubcategory', type: 'String' } + { name: 'CommitmentDiscountCategory', type: 'String' } + { name: 'CommitmentDiscountId', type: 'String' } + { name: 'CommitmentDiscountName', type: 'String' } + { name: 'CommitmentDiscountType', type: 'String' } + { name: 'EffectiveCost', type: 'Decimal' } + { name: 'InvoiceIssuerName', type: 'String' } + { name: 'ListCost', type: 'Decimal' } + { name: 'ListUnitPrice', type: 'Decimal' } + { name: 'PricingCategory', type: 'String' } + { name: 'PricingQuantity', type: 'Decimal' } + { name: 'PricingUnit', type: 'String' } + { name: 'ProviderName', type: 'String' } + { name: 'PublisherName', type: 'String' } + { name: 'Region', type: 'String' } + { name: 'ResourceId', type: 'String' } + { name: 'ResourceName', type: 'String' } + { name: 'ResourceType', type: 'String' } + { name: 'ServiceCategory', type: 'String' } + { name: 'ServiceName', type: 'String' } + { name: 'SkuId', type: 'String' } + { name: 'SkuPriceId', type: 'String' } + { name: 'SubAccountId', type: 'String' } + { name: 'SubAccountName', type: 'String' } + { name: 'SubAccountType', type: 'String' } + { name: 'Tags', type: 'String' } + { name: 'UsageQuantity', type: 'Decimal' } + { name: 'UsageUnit', type: 'String' } + { name: 'x_AccountName', type: 'String' } + { name: 'x_AccountOwnerId', type: 'String' } + { name: 'x_BilledCostInUsd', type: 'Decimal' } + { name: 'x_BilledUnitPrice', type: 'Decimal' } + { name: 'x_BillingAccountId', type: 'String' } + { name: 'x_BillingAccountName', type: 'String' } + { name: 'x_BillingExchangeRate', type: 'Decimal' } + { name: 'x_BillingExchangeRateDate', type: 'DateTime' } + { name: 'x_BillingProfileId', type: 'String' } + { name: 'x_BillingProfileName', type: 'String' } + { name: 'x_ChargeId', type: 'String' } + { name: 'x_CostAllocationRuleName', type: 'String' } + { name: 'x_CostCenter', type: 'String' } + { name: 'x_CustomerId', type: 'String' } + { name: 'x_CustomerName', type: 'String' } + { name: 'x_EffectiveCostInUsd', type: 'Decimal' } + { name: 'x_EffectiveUnitPrice', type: 'Decimal' } + { name: 'x_InvoiceId', type: 'String' } + { name: 'x_InvoiceIssuerId', type: 'String' } + { name: 'x_InvoiceSectionId', type: 'String' } + { name: 'x_InvoiceSectionName', type: 'String' } + { name: 'x_OnDemandCost', type: 'Decimal' } + { name: 'x_OnDemandCostInUsd', type: 'Decimal' } + { name: 'x_OnDemandUnitPrice', type: 'Decimal' } + { name: 'x_PartnerCreditApplied', type: 'Boolean' } + { name: 'x_PartnerCreditRate', type: 'Decimal' } + { name: 'x_PricingBlockSize', type: 'Decimal' } + { name: 'x_PricingCurrency', type: 'String' } + { name: 'x_PricingSubcategory', type: 'String' } + { name: 'x_PricingUnitDescription', type: 'String' } + { name: 'x_PublisherCategory', type: 'String' } + { name: 'x_PublisherId', type: 'String' } + { name: 'x_ResellerId', type: 'String' } + { name: 'x_ResellerName', type: 'String' } + { name: 'x_ResourceGroupName', type: 'String' } + { name: 'x_ResourceType', type: 'String' } + { name: 'x_ServicePeriodEnd', type: 'DateTime' } + { name: 'x_ServicePeriodStart', type: 'DateTime' } + { name: 'x_SkuDescription', type: 'String' } + { name: 'x_SkuDetails', type: 'String' } + { name: 'x_SkuIsCreditEligible', type: 'Boolean' } + { name: 'x_SkuMeterCategory', type: 'String' } + { name: 'x_SkuMeterId', type: 'String' } + { name: 'x_SkuMeterName', type: 'String' } + { name: 'x_SkuMeterSubcategory', type: 'String' } + { name: 'x_SkuOfferId', type: 'String' } + { name: 'x_SkuOrderId', type: 'String' } + { name: 'x_SkuOrderName', type: 'String' } + { name: 'x_SkuPartNumber', type: 'String' } + { name: 'x_SkuRegion', type: 'String' } + { name: 'x_SkuServiceFamily', type: 'String' } + { name: 'x_SkuTerm', type: 'String' } + { name: 'x_SkuTier', type: 'String' } +] +var focusCostMappings = [ + for i in range(0, length(focusCostColumns)): { + source: { name: focusCostColumns[i].name, type: focusCostColumns[i].type } + sink: { name: focusCostColumns[i].name } + } +] + +//============================================================================== +// Resources +//============================================================================== + +// Get data factory instance +resource dataFactory 'Microsoft.DataFactory/factories@2018-06-01' existing = { + name: dataFactoryName +} + +//------------------------------------------------------------------------------ +// Delete old triggers and pipelines +//------------------------------------------------------------------------------ + +module deleteOldResources 'br/public:avm/res/resources/deployment-script:0.2.0' = { + name: '${uniqueString(deployment().name, location)}-deleteOldResources' + dependsOn: [ + identityRoleAssignments + ] + params: { + name: 'deleteOldResources' + location: location + kind: 'AzurePowerShell' + tags: tags + azPowerShellVersion: '9.7' + retentionInterval: 'PT1H' + cleanupPreference: 'OnSuccess' + scriptContent: loadTextContent('./scripts/Remove-OldResources.ps1') + managedIdentities: { + userAssignedResourcesIds: [ + identity.id + ] + } + environmentVariables: { + secureList: [ + { + name: 'DataFactorySubscriptionId' + value: subscription().id + } + { + name: 'DataFactoryResourceGroup' + value: resourceGroup().name + } + { + name: 'DataFactoryName' + value: dataFactory.name + } + ] + } + } +} + +//------------------------------------------------------------------------------ +// Stop all triggers before deploying +//------------------------------------------------------------------------------ + +// Create managed identity to start/stop triggers +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: '${dataFactoryName}_triggerManager' + location: location + tags: union( + tags ?? {}, + contains(tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities') + ? tagsByResource['Microsoft.ManagedIdentity/userAssignedIdentities'] + : {} + ) +} + +// Assign access to the identity +resource identityRoleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for role in autoStartRbacRoles: { + name: guid(dataFactory.id, role, identity.id) + scope: dataFactory + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', role) + principalId: identity.properties.principalId + principalType: 'ServicePrincipal' + } + } +] + +module stopHubTriggers 'br/public:avm/res/resources/deployment-script:0.2.0' = { + name: '${uniqueString(deployment().name, location)}-stopHubTriggers' + dependsOn: [ + identityRoleAssignments + ] + params: { + name: 'stopHubTriggers' + location: location + kind: 'AzurePowerShell' + tags: union( + tags ?? {}, + contains(tagsByResource, 'Microsoft.Resources/deploymentScripts') + ? tagsByResource['Microsoft.Resources/deploymentScripts'] + : {} + ) + azPowerShellVersion: '9.7' + retentionInterval: 'PT1H' + cleanupPreference: 'OnSuccess' + scriptContent: loadTextContent('./scripts/Start-Triggers.ps1') + arguments: '-Stop' + environmentVariables: { + secureList: [ + { + name: 'DataFactorySubscriptionId' + value: subscription().id + } + { + name: 'DataFactoryResourceGroup' + value: resourceGroup().name + } + { + name: 'DataFactoryName' + value: dataFactory.name + } + { + name: 'Triggers' + value: join(allHubTriggers, '|') + } + ] + } + managedIdentities: { + userAssignedResourcesIds: [ + identity.id + ] + } + } +} + +//------------------------------------------------------------------------------ +// Linked services +//------------------------------------------------------------------------------ + +resource keyVault 'Microsoft.KeyVault/vaults@2022-11-01' existing = { + name: keyVaultName +} + +resource linkedService_keyVault 'Microsoft.DataFactory/factories/linkedservices@2018-06-01' = { + name: 'keyVault' + parent: dataFactory + properties: { + annotations: [] + parameters: {} + type: 'AzureKeyVault' + typeProperties: { + baseUrl: keyVault.properties.vaultUri + } + } +} + +resource linkedService_storageAccount 'Microsoft.DataFactory/factories/linkedservices@2018-06-01' = { + name: 'storage' + parent: dataFactory + properties: { + annotations: [] + parameters: {} + type: 'AzureBlobFS' + typeProperties: { + url: storageAccount.properties.primaryEndpoints.dfs + accountKey: { + type: 'AzureKeyVaultSecret' + store: { + referenceName: linkedService_keyVault.name + type: 'LinkedServiceReference' + } + secretName: storageAccountName + } + } + } +} + +//------------------------------------------------------------------------------ +// Datasets +//------------------------------------------------------------------------------ + +resource dataset_msexports 'Microsoft.DataFactory/factories/datasets@2018-06-01' = { + name: safeExportContainerName + parent: dataFactory + dependsOn: [ + linkedService_keyVault + ] + properties: { + annotations: [] + parameters: { + fileName: { + type: 'String' + } + folderName: { + type: 'String' + } + } + type: 'DelimitedText' + typeProperties: union(datasetPropsCommon, datasetPropsDelimitedText, { compressionCodec: 'none' }) + linkedServiceName: { + parameters: {} + referenceName: linkedService_storageAccount.name + type: 'LinkedServiceReference' + } + } +} + +resource dataset_ingestion 'Microsoft.DataFactory/factories/datasets@2018-06-01' = { + name: safeIngestionContainerName + parent: dataFactory + dependsOn: [ + linkedService_keyVault + ] + properties: { + annotations: [] + parameters: { + fileName: { + type: 'String' + } + folderName: { + type: 'String' + } + } + type: any(convertToParquet ? 'Parquet' : 'DelimitedText') + typeProperties: union( + datasetPropsCommon, + convertToParquet ? {} : datasetPropsDelimitedText, + { compressionCodec: 'gzip' } + ) + linkedServiceName: { + parameters: {} + referenceName: linkedService_storageAccount.name + type: 'LinkedServiceReference' + } + } +} + +//------------------------------------------------------------------------------ +// Export container extract pipeline + trigger +// Trigger: New CSV files in exportContainer +// +// Queues the transform pipeline. +// This pipeline must complete ASAP due to ADF's hard limit of 100 concurrent executions per pipeline. +// If multiple large, partitioned exports run concurrently and this pipeline doesn't finish quickly, the transform pipeline won't get triggered. +// Queuing up the transform pipeline and exiting immediately greatly reduces the likelihood of this happening. +//------------------------------------------------------------------------------ + +// Get storage account instance +resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' existing = { + name: storageAccountName +} + +// Create trigger +resource trigger_msexports_FileAdded 'Microsoft.DataFactory/factories/triggers@2018-06-01' = { + name: '${safeExportContainerName}_FileAdded' + parent: dataFactory + dependsOn: [ + stopHubTriggers + pipeline_ExecuteETL + ] + properties: { + annotations: [] + pipelines: [ + { + pipelineReference: { + referenceName: '${exportContainerName}_ExecuteETL' + type: 'PipelineReference' + } + parameters: { + folderName: '@triggerBody().folderPath' + fileName: '@triggerBody().fileName' + } + } + ] + type: 'BlobEventsTrigger' + typeProperties: { + blobPathBeginsWith: '/${exportContainerName}/blobs/' + blobPathEndsWith: '.csv' + ignoreEmptyBlobs: true + scope: storageAccount.id + events: [ + 'Microsoft.Storage.BlobCreated' + ] + } + } +} + +resource pipeline_ExecuteETL 'Microsoft.DataFactory/factories/pipelines@2018-06-01' = { + name: '${safeExportContainerName}_ExecuteETL' + parent: dataFactory + dependsOn: [ + pipeline_msexports_ETL_ingestion + ] + properties: { + activities: [ + { + name: 'Execute' + type: 'ExecutePipeline' + dependsOn: [] + userProperties: [] + typeProperties: { + pipeline: { + referenceName: '${safeExportContainerName}_ETL_${safeIngestionContainerName}' + type: 'PipelineReference' + } + waitOnCompletion: false + parameters: { + folderName: { + value: '@pipeline().parameters.folderName' + type: 'Expression' + } + fileName: { + value: '@pipeline().parameters.fileName' + type: 'Expression' + } + } + } + } + ] + parameters: { + folderName: { + type: 'string' + } + fileName: { + type: 'string' + } + } + annotations: [] + } +} + +//------------------------------------------------------------------------------ +// Export container transform pipeline +// Trigger: pipeline_ExecuteETL +// +// Converts CSV files to Parquet or .CSV.GZ files. +//------------------------------------------------------------------------------ + +resource pipeline_msexports_ETL_ingestion 'Microsoft.DataFactory/factories/pipelines@2018-06-01' = { + name: '${safeExportContainerName}_ETL_${safeIngestionContainerName}' + parent: dataFactory + dependsOn: [ + dataset_msexports + dataset_ingestion + ] + properties: { + activities: [ + // (start) -> Wait -> FolderArray -> Scope -> Metric -> Date -> File -> Folder -> Delete Target -> Convert CSV -> Delete CSV -> (end) + // Wait + { + name: 'Wait' + type: 'Wait' + dependsOn: [] + userProperties: [] + typeProperties: { + waitTimeInSeconds: 60 + } + } + // Set FolderArray + { + name: 'Set FolderArray' + type: 'SetVariable' + dependsOn: [ + { + activity: 'Wait' + dependencyConditions: [ + 'Completed' + ] + } + ] + userProperties: [] + typeProperties: { + variableName: 'folderArray' + value: { + value: '@split(pipeline().parameters.folderName, \'/\')' + type: 'Expression' + } + } + } + // Set Scope + { + name: 'Set Scope' + type: 'SetVariable' + dependsOn: [ + { + activity: 'Set FolderArray' + dependencyConditions: [ + 'Completed' + ] + } + ] + userProperties: [] + typeProperties: { + variableName: 'scope' + value: { + value: '@replace(split(pipeline().parameters.folderName,variables(\'folderArray\')[sub(length(variables(\'folderArray\')), if(greater(length(variables(\'folderArray\')[sub(length(variables(\'folderArray\')), 2)]), 12), 3, 4))])[0],\'${exportContainerName}\',\'${ingestionContainerName}\')' + type: 'Expression' + } + } + } + // Set Metric + { + name: 'Set Metric' + type: 'SetVariable' + dependsOn: [ + { + activity: 'Set Scope' + dependencyConditions: [ + 'Completed' + ] + } + ] + userProperties: [] + typeProperties: { + variableName: 'metric' + value: { + // TODO: Parse metric out of the manifest file @ msexports////[/]/manifest.json + value: 'focuscost' + type: 'Expression' + } + } + } + // Set Date + { + name: 'Set Date' + type: 'SetVariable' + dependsOn: [ + { + activity: 'Set Metric' + dependencyConditions: [ + 'Completed' + ] + } + ] + userProperties: [] + typeProperties: { + variableName: 'date' + value: { + value: '@substring(variables(\'folderArray\')[sub(length(variables(\'folderArray\')), if(greater(length(variables(\'folderArray\')[sub(length(variables(\'folderArray\')), 2)]), 12), 2, 3))], 0, 6)' + type: 'Expression' + } + } + } + // Set Destination File Name + { + name: 'Set Destination File Name' + description: '' + type: 'SetVariable' + dependsOn: [ + { + activity: 'Set Date' + dependencyConditions: [ + 'Completed' + ] + } + ] + userProperties: [] + typeProperties: { + variableName: 'destinationFile' + value: { + value: '@replace(pipeline().parameters.fileName, \'.csv\', \'${convertToParquet ? '.parquet' : '.csv.gz'}\')' + type: 'Expression' + } + } + } + // Set Destination Folder Name + { + name: 'Set Destination Folder Name' + type: 'SetVariable' + dependsOn: [ + { + activity: 'Set Destination File Name' + dependencyConditions: [ + 'Completed' + ] + } + ] + userProperties: [] + typeProperties: { + variableName: 'destinationFolder' + value: { + value: '@replace(concat(variables(\'scope\'),variables(\'date\'),\'/\',variables(\'metric\')),\'//\',\'/\')' + type: 'Expression' + } + } + } + // Delete Target + { + name: 'Delete Target' + type: 'Delete' + dependsOn: [ + { + activity: 'Set Destination Folder Name' + dependencyConditions: [ + 'Completed' + ] + } + ] + policy: { + timeout: '0.12:00:00' + retry: 0 + retryIntervalInSeconds: 30 + secureOutput: false + secureInput: false + } + userProperties: [] + typeProperties: { + dataset: { + referenceName: safeIngestionContainerName + type: 'DatasetReference' + parameters: { + folderName: { + value: '@variables(\'destinationFolder\')' + type: 'Expression' + } + fileName: { + value: '@variables(\'destinationFile\')' + type: 'Expression' + } + } + } + enableLogging: false + storeSettings: { + type: 'AzureBlobFSReadSettings' + recursive: true + enablePartitionDiscovery: false + } + } + } + // Convert CSV + { + name: 'Convert CSV' + type: 'Copy' + dependsOn: [ + { + activity: 'Delete Target' + dependencyConditions: [ + 'Completed' + ] + } + ] + policy: { + timeout: '0.12:00:00' + retry: 0 + retryIntervalInSeconds: 30 + secureOutput: false + secureInput: false + } + userProperties: [] + typeProperties: { + source: { + type: 'DelimitedTextSource' + storeSettings: { + type: 'AzureBlobFSReadSettings' + recursive: true + enablePartitionDiscovery: false + } + formatSettings: { + type: 'DelimitedTextReadSettings' + } + } + sink: { + type: 'DelimitedTextSink' + storeSettings: { + type: 'AzureBlobFSWriteSettings' + } + formatSettings: convertToParquet + ? { + type: 'ParquetWriteSettings' + fileExtension: '.parquet' + } + : { + type: 'DelimitedTextWriteSettings' + quoteAllText: true + fileExtension: '.csv.gz' + } + } + enableStaging: false + parallelCopies: 1 + validateDataConsistency: false + translator: { + type: 'TabularTranslator' + mappings: focusCostMappings + } + } + inputs: [ + { + referenceName: safeExportContainerName + type: 'DatasetReference' + parameters: { + folderName: { + value: '@pipeline().parameters.folderName' + type: 'Expression' + } + fileName: { + value: '@pipeline().parameters.fileName' + type: 'Expression' + } + } + } + ] + outputs: [ + { + referenceName: safeIngestionContainerName + type: 'DatasetReference' + parameters: { + folderName: { + value: '@variables(\'destinationFolder\')' + type: 'Expression' + } + fileName: { + value: '@variables(\'destinationFile\')' + type: 'Expression' + } + } + } + ] + } + // Delete CSV + { + name: 'Delete CSV' + type: 'Delete' + dependsOn: [ + { + activity: 'Convert CSV' + dependencyConditions: [ + 'Succeeded' + ] + } + ] + policy: { + timeout: '0.12:00:00' + retry: 0 + retryIntervalInSeconds: 30 + secureOutput: false + secureInput: false + } + userProperties: [] + typeProperties: { + dataset: { + referenceName: safeExportContainerName + type: 'DatasetReference' + parameters: { + folderName: { + value: '@pipeline().parameters.folderName' + type: 'Expression' + } + fileName: { + value: '@pipeline().parameters.fileName' + type: 'Expression' + } + } + } + enableLogging: false + storeSettings: { + type: 'AzureBlobFSReadSettings' + recursive: true + enablePartitionDiscovery: false + } + } + } + ] + parameters: { + fileName: { + type: 'string' + } + folderName: { + type: 'string' + } + } + variables: { + destinationFile: { + type: 'String' + } + destinationFolder: { + type: 'String' + } + folderArray: { + type: 'Array' + } + scope: { + type: 'String' + } + date: { + type: 'String' + } + metric: { + type: 'String' + } + } + annotations: [] + } +} + +//------------------------------------------------------------------------------ +// Start all triggers +//------------------------------------------------------------------------------ + +// Start hub triggers + +module startHubTriggers 'br/public:avm/res/resources/deployment-script:0.2.0' = { + name: '${uniqueString(deployment().name, location)}-startHubTriggers' + dependsOn: [ + identityRoleAssignments + trigger_msexports_FileAdded + ] + params: { + name: 'startHubTriggers' + location: location + tags: union( + tags ?? {}, + contains(tagsByResource, 'Microsoft.Resources/deploymentScripts') + ? tagsByResource['Microsoft.Resources/deploymentScripts'] + : {} + ) + kind: 'AzurePowerShell' + azPowerShellVersion: '9.7' + retentionInterval: 'PT1H' + cleanupPreference: 'OnSuccess' + managedIdentities: { + userAssignedResourcesIds: [ + identity.id + ] + } + scriptContent: loadTextContent('./scripts/Start-Triggers.ps1') + environmentVariables: { + secureList: [ + { + name: 'DataFactorySubscriptionId' + value: subscription().id + } + { + name: 'DataFactoryResourceGroup' + value: resourceGroup().name + } + { + name: 'DataFactoryName' + value: dataFactory.name + } + { + name: 'Triggers' + value: join(allHubTriggers, '|') + } + ] + } + } +} + +//============================================================================== +// Outputs +//============================================================================== + +@description('The Resource ID of the Data factory.') +output resourceId string = dataFactory.id + +@description('The Name of the Azure Data Factory instance.') +output name string = dataFactory.name diff --git a/avm/ptn/finops-toolkit/finops-hub/modules/keyVault.bicep b/avm/ptn/finops-toolkit/finops-hub/modules/keyVault.bicep new file mode 100644 index 0000000000..207d944432 --- /dev/null +++ b/avm/ptn/finops-toolkit/finops-hub/modules/keyVault.bicep @@ -0,0 +1,102 @@ +//============================================================================== +// Parameters +//============================================================================== + +@description('Required. Name of the hub. Used to ensure unique resource names.') +param hubName string = '' + +@description('Required. Suffix to add to the KeyVault instance name to ensure uniqueness.') +param uniqueSuffix string = '' + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Optional. Array of access policies object.') +param accessPolicies array = [] + +@description('Required. Name of the storage account to store access keys for.') +param storageAccountName string + +@description('Optional. Specifies the SKU for the vault.') +@allowed([ + 'premium' + 'standard' +]) +param sku string = 'premium' + +@description('Optional. Resource tags.') +param tags object? + +@description('Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources.') +param tagsByResource object = {} + +//------------------------------------------------------------------------------ +// Variables +//------------------------------------------------------------------------------ + +// Generate globally unique KeyVault name: 3-24 chars; letters, numbers, dashes +var keyVaultPrefix = '${replace(hubName, '_', '-')}-vault' +var keyVaultSuffix = '-${uniqueSuffix}' +var keyVaultName = replace('${take(keyVaultPrefix, 24 - length(keyVaultSuffix))}${keyVaultSuffix}', '--', '-') + +var formattedAccessPolicies = [ + for accessPolicy in accessPolicies: { + applicationId: contains(accessPolicy, 'applicationId') ? accessPolicy.applicationId : '' + objectId: contains(accessPolicy, 'objectId') ? accessPolicy.objectId : '' + permissions: accessPolicy.permissions + tenantId: contains(accessPolicy, 'tenantId') ? accessPolicy.tenantId : tenant().tenantId + } +] + +//============================================================================== +// Resources +//============================================================================== + +module keyVault 'br/public:avm/res/key-vault/vault:0.5.1' = { + name: '${uniqueString(deployment().name, location)}-keyvault' + params: { + name: keyVaultName + location: location + tags: union( + tags ?? {}, + contains(tagsByResource, 'Microsoft.KeyVault/vaults') ? tagsByResource['Microsoft.KeyVault/vaults'] : {} + ) + enableVaultForDeployment: true + enableVaultForTemplateDeployment: true + enableVaultForDiskEncryption: true + enablePurgeProtection: false + enableSoftDelete: true + softDeleteRetentionInDays: 90 + enableRbacAuthorization: false + createMode: 'default' + sku: startsWith(location, 'china') ? 'standard' : sku + accessPolicies: formattedAccessPolicies + secrets: { + secureList: [ + { + name: storageRef.name + value: storageRef.listKeys().keys[0].value + attributesExp: 1702648632 + attributesNbf: 10000 + } + ] + } + } +} + +resource storageRef 'Microsoft.Storage/storageAccounts@2023-01-01' existing = { + name: storageAccountName +} + +//============================================================================== +// Outputs +//============================================================================== + +@description('The resource ID of the key vault.') +output resourceId string = keyVault.outputs.resourceId + +@description('The name of the key vault.') +output name string = keyVault.outputs.name + +@description('The URI of the key vault.') +output uri string = keyVault.outputs.uri diff --git a/avm/ptn/finops-toolkit/finops-hub/modules/scripts/Copy-FileToAzureBlob.ps1 b/avm/ptn/finops-toolkit/finops-hub/modules/scripts/Copy-FileToAzureBlob.ps1 new file mode 100644 index 0000000000..2368d4effa --- /dev/null +++ b/avm/ptn/finops-toolkit/finops-hub/modules/scripts/Copy-FileToAzureBlob.ps1 @@ -0,0 +1,81 @@ +Write-Output 'Updating settings.json file...' +Write-Output " Storage account: $env:storageAccountName" +Write-Output " Container: $env:containerName" + +$validateScopes = { $_.Length -gt 45 } + +# Initialize variables +$fileName = 'settings.json' +$filePath = Join-Path -Path . -ChildPath $fileName +$newScopes = $env:exportScopes.Split('|') | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } } + +# Get storage context +$storageContext = @{ + Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount + Container = $env:containerName +} + +# Download existing settings, if they exist +$blob = Get-AzStorageBlobContent @storageContext -Blob $fileName -Destination $filePath -Force +if ($blob) { + Write-Output 'Existing settings.json file found. Updating...' + $text = Get-Content $filePath -Raw + Write-Output '---------' + Write-Output $text + Write-Output '---------' + $json = $text | ConvertFrom-Json + + # Rename exportScopes to scopes + convert to object array + if ($json.exportScopes) { + Write-Output ' Updating exportScopes...' + if ($json.exportScopes[0] -is [string]) { + Write-Output ' Converting string array to object array...' + $json.exportScopes = $json.exportScopes | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } } + if (-not ($json.exportScopes -is [array])) { + Write-Output ' Converting single object to object array...' + $json.exportScopes = @($json.exportScopes) + } + } + + Write-Output " Renaming to 'scopes'..." + $json | Add-Member -MemberType NoteProperty -Name scopes -Value $json.exportScopes + $json.PSObject.Properties.Remove('exportScopes') + } +} + +# Set default if not found +if (!$json) { + Write-Output 'No existing settings.json file found. Creating new file...' + $json = [ordered]@{ + '$schema' = 'https://aka.ms/finops/hubs/settings-schema' + type = 'HubInstance' + version = '' + learnMore = 'https://aka.ms/finops/hubs' + scopes = @() + } +} + +# Updating settings +Write-Output "Updating version to $env:ftkVersion..." +$json.version = $env:ftkVersion +if ($newScopes) { + Write-Output "Merging $($newScopes.Count) scopes..." + $json.scopes = Compare-Object -ReferenceObject $json.scopes -DifferenceObject $newScopes -Property scope -PassThru -IncludeEqual + + # Remove the SideIndicator property from the Compare-Object output + $json.scopes | ForEach-Object { $_.PSObject.Properties.Remove('SideIndicator') } | ConvertTo-Json + + if (-not ($json.scopes -is [array])) { + $json.scopes = @($json.scopes) + } + Write-Output "$($json.scopes.Count) scopes found." +} +$text = $json | ConvertTo-Json +Write-Output '---------' +Write-Output $text +Write-Output '---------' +$text | Out-File $filePath + +# Upload new/updated settings +Write-Output 'Uploading settings.json file...' +Set-AzStorageBlobContent @storageContext -File $filePath -Force diff --git a/avm/ptn/finops-toolkit/finops-hub/modules/scripts/Remove-OldResources.ps1 b/avm/ptn/finops-toolkit/finops-hub/modules/scripts/Remove-OldResources.ps1 new file mode 100644 index 0000000000..2c5f42f836 --- /dev/null +++ b/avm/ptn/finops-toolkit/finops-hub/modules/scripts/Remove-OldResources.ps1 @@ -0,0 +1,18 @@ +# Init outputs +$DeploymentScriptOutputs = @{} + +$adfParams = @{ + ResourceGroupName = $env:DataFactoryResourceGroup + DataFactoryName = $env:DataFactoryName +} + +# Delete old triggers +$triggers = Get-AzDataFactoryV2Trigger @adfParams -ErrorAction SilentlyContinue ` +| Where-Object { $_.Name -match '^msexports?$' } +$DeploymentScriptOutputs['stopTriggers'] = $triggers | Stop-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue +$DeploymentScriptOutputs['deleteTriggers'] = $triggers | Remove-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue + +# Delete old pipelines +$DeploymentScriptOutputs['pipelines'] = Get-AzDataFactoryV2Pipeline @adfParams -ErrorAction SilentlyContinue ` +| Where-Object { $_.Name -match '^msexports_(extract|transform)$' } ` +| Remove-AzDataFactoryV2Pipeline -Force -ErrorAction SilentlyContinue diff --git a/avm/ptn/finops-toolkit/finops-hub/modules/scripts/Start-Triggers.ps1 b/avm/ptn/finops-toolkit/finops-hub/modules/scripts/Start-Triggers.ps1 new file mode 100644 index 0000000000..bd1d796ce6 --- /dev/null +++ b/avm/ptn/finops-toolkit/finops-hub/modules/scripts/Start-Triggers.ps1 @@ -0,0 +1,41 @@ +Param( + [switch] $Stop +) +# Init outputs +$DeploymentScriptOutputs = @{} + +if (-not $Stop) { + Start-Sleep -Seconds 10 +} + +# Loop through triggers +$env:Triggers.Split('|') ` +| ForEach-Object { + $trigger = $_ + if ($Stop) { + Write-Host "Stopping trigger $trigger..." -NoNewline + $triggerOutput = Stop-AzDataFactoryV2Trigger ` + -ResourceGroupName $env:DataFactoryResourceGroup ` + -DataFactoryName $env:DataFactoryName ` + -Name $trigger ` + -Force ` + -ErrorAction SilentlyContinue + } else { + Write-Host "Starting trigger $trigger..." -NoNewline + $triggerOutput = Start-AzDataFactoryV2Trigger ` + -ResourceGroupName $env:DataFactoryResourceGroup ` + -DataFactoryName $env:DataFactoryName ` + -Name $trigger ` + -Force + } + if ($triggerOutput) { + Write-Host 'done' + } else { + Write-Host 'failed' + } + $DeploymentScriptOutputs[$trigger] = $triggerOutput +} + +if ($Stop) { + Start-Sleep -Seconds 10 +} diff --git a/avm/ptn/finops-toolkit/finops-hub/modules/storage.bicep b/avm/ptn/finops-toolkit/finops-hub/modules/storage.bicep new file mode 100644 index 0000000000..7189586b6e --- /dev/null +++ b/avm/ptn/finops-toolkit/finops-hub/modules/storage.bicep @@ -0,0 +1,164 @@ +//============================================================================== +// Parameters +//============================================================================== +@description('Optional. Azure location where all resources should be created. See https://aka.ms/azureregions. Default: (resource group location).') +param location string = resourceGroup().location + +@allowed([ + 'Premium_LRS' + 'Premium_ZRS' +]) +@description('Optional. Storage SKU to use. LRS = Lowest cost, ZRS = High availability. Note Standard SKUs are not available for Data Lake gen2 storage. Allowed: Premium_LRS, Premium_ZRS. Default: Premium_LRS.') +param sku string = 'Premium_LRS' + +@description('Optional. Tags to apply to all resources. We will also add the cm-resource-parent tag for improved cost roll-ups in Cost Management.') +param tags object? + +@description('Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources.') +param tagsByResource object = {} + +@description('Optional. List of scope IDs to create exports for.') +param exportScopes array = [] + +@description('The name of the container used for configuration settings.') +param configContainer string = 'config' + +@description('The name of the container used for Cost Management exports.') +param exportContainer string = 'exports' + +@description('The name of the container used for normalized data ingestion.') +param ingestionContainer string = 'ingestion' + +@description('Optional. Name of the storage account.') +param storageAccountName string = '' + +@description('Optional. The version of the FTK to use.') +param ftkVersion string = '' + +//============================================================================== +// Resources +//============================================================================== + +module storageAccount 'br/public:avm/res/storage/storage-account:0.8.3' = { + name: '${uniqueString(deployment().name, location)}-storage' + params: { + name: storageAccountName + skuName: sku + kind: 'BlockBlobStorage' + tags: union( + tags ?? {}, + contains(tagsByResource, 'Microsoft.Storage/storageAccounts') + ? tagsByResource['Microsoft.Storage/storageAccounts'] + : {} + ) + supportsHttpsTrafficOnly: true + minimumTlsVersion: 'TLS1_2' + allowBlobPublicAccess: false + publicNetworkAccess: 'Enabled' + enableHierarchicalNamespace: true + blobServices: { + containers: [ + { + name: configContainer + publicAccess: 'None' + metadata: {} + } + { + name: exportContainer + publicAccess: 'None' + metadata: {} + } + { + name: ingestionContainer + publicAccess: 'None' + metadata: {} + } + ] + } + roleAssignments: [ + { + roleDefinitionIdOrName: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' + principalId: identity.properties.principalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: 'e40ec5ca-96e0-45a2-b4ff-59039f2c2b59' + principalId: identity.properties.principalId + principalType: 'ServicePrincipal' + } + ] + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Allow' + } + } +} + +//------------------------------------------------------------------------------ +// Settings.json +//------------------------------------------------------------------------------ + +// Create managed identity to upload files +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: '${storageAccountName}_blobManager' + tags: union( + tags ?? {}, + contains(tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities') + ? tagsByResource['Microsoft.ManagedIdentity/userAssignedIdentities'] + : {} + ) + location: location +} + +module uploadSettings 'br/public:avm/res/resources/deployment-script:0.2.0' = { + name: '${uniqueString(deployment().name, location)}-uploadSettings' + params: { + name: 'uploadSettings' + kind: 'AzurePowerShell' + location: startsWith(location, 'china') ? 'chinaeast2' : location + tags: union( + tags ?? {}, + contains(tagsByResource, 'Microsoft.Resources/deploymentScripts') + ? tagsByResource['Microsoft.Resources/deploymentScripts'] + : {} + ) + managedIdentities: { + userAssignedResourcesIds: [ + identity.id + ] + } + azPowerShellVersion: '9.7' + retentionInterval: 'PT1H' + environmentVariables: { + secureList: [ + { + name: 'ftkVersion' + value: ftkVersion + } + { + name: 'exportScopes' + value: join(exportScopes, '|') + } + { + name: 'storageAccountName' + value: storageAccountName + } + { + name: 'containerName' + value: 'config' + } + ] + } + scriptContent: loadTextContent('./scripts/Copy-FileToAzureBlob.ps1') + } +} + +//============================================================================== +// Outputs +//============================================================================== + +@description('The resource ID of the storage account.') +output resourceId string = storageAccount.outputs.resourceId + +@description('The name of the storage account.') +output name string = storageAccount.outputs.name diff --git a/avm/ptn/finops-toolkit/finops-hub/tests/e2e/defaults/main.test.bicep b/avm/ptn/finops-toolkit/finops-hub/tests/e2e/defaults/main.test.bicep new file mode 100644 index 0000000000..d6570aab85 --- /dev/null +++ b/avm/ptn/finops-toolkit/finops-hub/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,45 @@ +targetScope = 'subscription' + +metadata name = 'Using only defaults' +metadata description = 'This instance deploys the module with the minimum set of required parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-finops-hub-${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 = 'finmin' + +@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 +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}' + params: { + hubName: 'finops-hub-${namePrefix}-${serviceShort}' + location: resourceLocation + } +} diff --git a/avm/ptn/finops-toolkit/finops-hub/version.json b/avm/ptn/finops-toolkit/finops-hub/version.json new file mode 100644 index 0000000000..0200aa0775 --- /dev/null +++ b/avm/ptn/finops-toolkit/finops-hub/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} diff --git a/avm/utilities/pipelines/staticValidation/psrule/.ps-rule/custom-rules.Rule.yaml b/avm/utilities/pipelines/staticValidation/psrule/.ps-rule/custom-rules.Rules.yaml similarity index 100% rename from avm/utilities/pipelines/staticValidation/psrule/.ps-rule/custom-rules.Rule.yaml rename to avm/utilities/pipelines/staticValidation/psrule/.ps-rule/custom-rules.Rules.yaml From d3a31263297f4bf29aac37f2bb498f823ce15e3c Mon Sep 17 00:00:00 2001 From: Seif Bassem <38246040+sebassem@users.noreply.github.com> Date: Fri, 24 May 2024 01:32:58 +0300 Subject: [PATCH 15/18] fix: Avm finops hub - Update readme for static tests (#2018) ## Description ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.finops-toolkit.finops-hub](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.ptn.finops-toolkit.finops-hub.yml/badge.svg?branch=avm-finops-hub)](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.ptn.finops-toolkit.finops-hub.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [ ] I'm sure there are no other open Pull Requests for the same update/change - [ ] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/ptn/finops-toolkit/finops-hub/README.md | 2 +- avm/ptn/finops-toolkit/finops-hub/main.json | 18 +++++++++--------- .../modules/scripts/Copy-FileToAzureBlob.ps1 | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/avm/ptn/finops-toolkit/finops-hub/README.md b/avm/ptn/finops-toolkit/finops-hub/README.md index 1910a08edd..aac5e8bc8c 100644 --- a/avm/ptn/finops-toolkit/finops-hub/README.md +++ b/avm/ptn/finops-toolkit/finops-hub/README.md @@ -30,7 +30,7 @@ This module deploys a Finops hub from the Finops toolkit. | `Microsoft.ManagedIdentity/userAssignedIdentities` | [2023-01-31](https://learn.microsoft.com/en-us/azure/templates/Microsoft.ManagedIdentity/2023-01-31/userAssignedIdentities) | | `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) | -| `Microsoft.Resources/deploymentScripts` | [2023-08-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Resources/deploymentScripts) | +| `Microsoft.Resources/deploymentScripts` | [2023-08-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Resources/2023-08-01/deploymentScripts) | | `Microsoft.Storage/storageAccounts` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts) | | `Microsoft.Storage/storageAccounts/blobServices` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices) | | `Microsoft.Storage/storageAccounts/blobServices/containers` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices/containers) | diff --git a/avm/ptn/finops-toolkit/finops-hub/main.json b/avm/ptn/finops-toolkit/finops-hub/main.json index f0171cd213..170a9b7282 100644 --- a/avm/ptn/finops-toolkit/finops-hub/main.json +++ b/avm/ptn/finops-toolkit/finops-hub/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "13888331902588628493" + "version": "0.27.1.19265", + "templateHash": "8363675209548092624" }, "name": "Finops-hub", "description": "This module deploys a Finops hub from the Finops toolkit.", @@ -210,8 +210,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "4383879433834970993" + "version": "0.27.1.19265", + "templateHash": "4122497544653764025" } }, "parameters": { @@ -291,7 +291,7 @@ } }, "variables": { - "$fxv#0": "Write-Output 'Updating settings.json file...'\nWrite-Output \" Storage account: $env:storageAccountName\"\nWrite-Output \" Container: $env:containerName\"\n\n$validateScopes = { $_.Length -gt 45 }\n\n# Initialize variables\n$fileName = 'settings.json'\n$filePath = Join-Path -Path . -ChildPath $fileName\n$newScopes = $env:exportScopes.Split('|') | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Download existing settings, if they exist\n$blob = Get-AzStorageBlobContent @storageContext -Blob $fileName -Destination $filePath -Force\nif ($blob) {\n Write-Output 'Existing settings.json file found. Updating...'\n $text = Get-Content $filePath -Raw\n Write-Output '---------'\n Write-Output $text\n Write-Output '---------'\n $json = $text | ConvertFrom-Json\n\n # Rename exportScopes to scopes + convert to object array\n if ($json.exportScopes) {\n Write-Output ' Updating exportScopes...'\n if ($json.exportScopes[0] -is [string]) {\n Write-Output ' Converting string array to object array...'\n $json.exportScopes = $json.exportScopes | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\n if (-not ($json.exportScopes -is [array])) {\n Write-Output ' Converting single object to object array...'\n $json.exportScopes = @($json.exportScopes)\n }\n }\n\n Write-Output \" Renaming to 'scopes'...\"\n $json | Add-Member -MemberType NoteProperty -Name scopes -Value $json.exportScopes\n $json.PSObject.Properties.Remove('exportScopes')\n }\n}\n\n# Set default if not found\nif (!$json) {\n Write-Output 'No existing settings.json file found. Creating new file...'\n $json = [ordered]@{\n '$schema' = 'https://aka.ms/finops/hubs/settings-schema'\n type = 'HubInstance'\n version = ''\n learnMore = 'https://aka.ms/finops/hubs'\n scopes = @()\n }\n}\n\n# Updating settings\nWrite-Output \"Updating version to $env:ftkVersion...\"\n$json.version = $env:ftkVersion\nif ($newScopes) {\n Write-Output \"Merging $($newScopes.Count) scopes...\"\n $json.scopes = Compare-Object -ReferenceObject $json.scopes -DifferenceObject $newScopes -Property scope -PassThru -IncludeEqual\n\n # Remove the SideIndicator property from the Compare-Object output\n $json.scopes | ForEach-Object { $_.PSObject.Properties.Remove('SideIndicator') } | ConvertTo-Json\n\n if (-not ($json.scopes -is [array])) {\n $json.scopes = @($json.scopes)\n }\n Write-Output \"$($json.scopes.Count) scopes found.\"\n}\n$text = $json | ConvertTo-Json\nWrite-Output '---------'\nWrite-Output $text\nWrite-Output '---------'\n$text | Out-File $filePath\n\n# Upload new/updated settings\nWrite-Output 'Uploading settings.json file...'\nSet-AzStorageBlobContent @storageContext -File $filePath -Force\n" + "$fxv#0": "Write-Output 'Updating settings.json file...'\nWrite-Output \"Storage account: $env:storageAccountName\"\nWrite-Output \"Container: $env:containerName\"\n\n$validateScopes = { $_.Length -gt 45 }\n\n# Initialize variables\n$fileName = 'settings.json'\n$filePath = Join-Path -Path . -ChildPath $fileName\n$newScopes = $env:exportScopes.Split('|') | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Download existing settings, if they exist\n$blob = Get-AzStorageBlobContent @storageContext -Blob $fileName -Destination $filePath -Force\nif ($blob) {\n Write-Output 'Existing settings.json file found. Updating...'\n $text = Get-Content $filePath -Raw\n Write-Output '---------'\n Write-Output $text\n Write-Output '---------'\n $json = $text | ConvertFrom-Json\n\n # Rename exportScopes to scopes + convert to object array\n if ($json.exportScopes) {\n Write-Output ' Updating exportScopes...'\n if ($json.exportScopes[0] -is [string]) {\n Write-Output ' Converting string array to object array...'\n $json.exportScopes = $json.exportScopes | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\n if (-not ($json.exportScopes -is [array])) {\n Write-Output ' Converting single object to object array...'\n $json.exportScopes = @($json.exportScopes)\n }\n }\n\n Write-Output \" Renaming to 'scopes'...\"\n $json | Add-Member -MemberType NoteProperty -Name scopes -Value $json.exportScopes\n $json.PSObject.Properties.Remove('exportScopes')\n }\n}\n\n# Set default if not found\nif (!$json) {\n Write-Output 'No existing settings.json file found. Creating new file...'\n $json = [ordered]@{\n '$schema' = 'https://aka.ms/finops/hubs/settings-schema'\n type = 'HubInstance'\n version = ''\n learnMore = 'https://aka.ms/finops/hubs'\n scopes = @()\n }\n}\n\n# Updating settings\nWrite-Output \"Updating version to $env:ftkVersion...\"\n$json.version = $env:ftkVersion\nif ($newScopes) {\n Write-Output \"Merging $($newScopes.Count) scopes...\"\n $json.scopes = Compare-Object -ReferenceObject $json.scopes -DifferenceObject $newScopes -Property scope -PassThru -IncludeEqual\n\n # Remove the SideIndicator property from the Compare-Object output\n $json.scopes | ForEach-Object { $_.PSObject.Properties.Remove('SideIndicator') } | ConvertTo-Json\n\n if (-not ($json.scopes -is [array])) {\n $json.scopes = @($json.scopes)\n }\n Write-Output \"$($json.scopes.Count) scopes found.\"\n}\n$text = $json | ConvertTo-Json\nWrite-Output '---------'\nWrite-Output $text\nWrite-Output '---------'\n$text | Out-File $filePath\n\n# Upload new/updated settings\nWrite-Output 'Uploading settings.json file...'\nSet-AzStorageBlobContent @storageContext -File $filePath -Force\n" }, "resources": { "identity": { @@ -5614,8 +5614,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "13617090394106970682" + "version": "0.27.1.19265", + "templateHash": "3536466427550878903" } }, "parameters": { @@ -8386,8 +8386,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.170.59819", - "templateHash": "18290965054356591240" + "version": "0.27.1.19265", + "templateHash": "805240367391579055" } }, "parameters": { diff --git a/avm/ptn/finops-toolkit/finops-hub/modules/scripts/Copy-FileToAzureBlob.ps1 b/avm/ptn/finops-toolkit/finops-hub/modules/scripts/Copy-FileToAzureBlob.ps1 index 2368d4effa..8e5e5aba83 100644 --- a/avm/ptn/finops-toolkit/finops-hub/modules/scripts/Copy-FileToAzureBlob.ps1 +++ b/avm/ptn/finops-toolkit/finops-hub/modules/scripts/Copy-FileToAzureBlob.ps1 @@ -1,6 +1,6 @@ Write-Output 'Updating settings.json file...' -Write-Output " Storage account: $env:storageAccountName" -Write-Output " Container: $env:containerName" +Write-Output "Storage account: $env:storageAccountName" +Write-Output "Container: $env:containerName" $validateScopes = { $_.Length -gt 45 } From 8d2a9c7428c587be93d4432b0b9eba88af929baa Mon Sep 17 00:00:00 2001 From: Erika Gressi <56914614+eriqua@users.noreply.github.com> Date: Fri, 24 May 2024 10:45:48 +0200 Subject: [PATCH 16/18] fix: Cleanup and order codeowners and issue template (#1988) ## Description Cleanup outdated comments and order alphabetically codeowners and issue template files ## Pipeline Reference | Pipeline | | -------- | | N/A | ## Type of Change - [x] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [ ] I'm sure there are no other open Pull Requests for the same update/change - [ ] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --- .github/CODEOWNERS | 22 ++++++--------------- .github/ISSUE_TEMPLATE/avm_module_issue.yml | 3 --- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7355262163..64ec6b5fac 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,6 +6,12 @@ /avm/ptn/authorization/policy-assignment/ @Azure/avm-ptn-authorization-policyassignment-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/ptn/authorization/resource-role-assignment/ @Azure/avm-ptn-authorization-resourceroleassignment-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/ptn/authorization/role-assignment/ @Azure/avm-ptn-authorization-roleassignment-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/ptn/avd-lza/insights/ @Azure/avm-ptn-avd-lza-insights-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/ptn/avd-lza/management-plane/ @Azure/avm-ptn-avd-lza-managementplane-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/ptn/avd-lza/networking/ @Azure/avm-ptn-avd-lza-networking-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/ptn/avd-lza/session-hosts/ @Azure/avm-ptn-avd-lza-sessionhosts-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/ptn/finops-toolkit/finops-hub/ @Azure/avm-ptn-finopstoolkit-finopshub-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/ptn/policy-insights/remediation/ @Azure/avm-ptn-policyinsights-remediation-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/ptn/security/security-center/ @Azure/avm-ptn-security-securitycenter-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/aad/domain-service/ @Azure/avm-res-aad-domainservice-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/analysis-services/server/ @Azure/avm-res-analysisservices-server-module-owners-bicep @Azure/avm-core-team-technical-bicep @@ -14,12 +20,6 @@ /avm/res/app/job/ @Azure/avm-res-app-job-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/app/managed-environment/ @Azure/avm-res-app-managedenvironment-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/app-configuration/configuration-store/ @Azure/avm-res-appconfiguration-configurationstore-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/res/authorization/lock/ @Azure/avm-res-authorization-lock-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/res/authorization/policy-definition/ @Azure/avm-res-authorization-policydefinition-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/res/authorization/policy-exemption/ @Azure/avm-res-authorization-policyexemption-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/res/authorization/policy-set-definition/ @Azure/avm-res-authorization-policysetdefinition-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/res/authorization/role-assignment/ @Azure/avm-res-authorization-roleassignment-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/res/authorization/role-definition/ @Azure/avm-res-authorization-roledefinition-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/automation/automation-account/ @Azure/avm-res-automation-automationaccount-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/batch/batch-account/ @Azure/avm-res-batch-batchaccount-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/cache/redis/ @Azure/avm-res-cache-redis-module-owners-bicep @Azure/avm-core-team-technical-bicep @@ -50,7 +50,6 @@ /avm/res/desktop-virtualization/host-pool/ @Azure/avm-res-desktopvirtualization-hostpool-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/desktop-virtualization/scaling-plan/ @Azure/avm-res-desktopvirtualization-scalingplan-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/desktop-virtualization/workspace/ @Azure/avm-res-desktopvirtualization-workspace-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/res/dev-center/devcenter/ @Azure/avm-res-devcenter-devcenter-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/dev-test-lab/lab/ @Azure/avm-res-devtestlab-lab-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/digital-twins/digital-twins-instance/ @Azure/avm-res-digitaltwins-digitaltwinsinstance-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/document-db/database-account/ @Azure/avm-res-documentdb-databaseaccount-module-owners-bicep @Azure/avm-core-team-technical-bicep @@ -121,7 +120,6 @@ /avm/res/network/vpn-site/ @Azure/avm-res-network-vpnsite-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/operational-insights/workspace/ @Azure/avm-res-operationalinsights-workspace-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/operations-management/solution/ @Azure/avm-res-operationsmanagement-solution-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/res/policy-insights/remediation/ @Azure/avm-res-policyinsights-remediation-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/power-bi-dedicated/capacity/ @Azure/avm-res-powerbidedicated-capacity-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/purview/account/ @Azure/avm-res-purview-account-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/recovery-services/vault/ @Azure/avm-res-recoveryservices-vault-module-owners-bicep @Azure/avm-core-team-technical-bicep @@ -129,7 +127,6 @@ /avm/res/resource-graph/query/ @Azure/avm-res-resourcegraph-query-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/resources/deployment-script/ @Azure/avm-res-resources-deploymentscript-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/resources/resource-group/ @Azure/avm-res-resources-resourcegroup-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/res/resources/tags/ @Azure/avm-res-resources-tags-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/search/search-service/ @Azure/avm-res-search-searchservice-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/service-bus/namespace/ @Azure/avm-res-servicebus-namespace-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/service-fabric/cluster/ @Azure/avm-res-servicefabric-cluster-module-owners-bicep @Azure/avm-core-team-technical-bicep @@ -147,11 +144,4 @@ /avm/res/web/serverfarm/ @Azure/avm-res-web-serverfarm-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/web/site/ @Azure/avm-res-web-site-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/web/static-site/ @Azure/avm-res-web-staticsite-module-owners-bicep @Azure/avm-core-team-technical-bicep -/avm/ptn/finops-toolkit/finops-hub/ @Azure/avm-ptn-finopstoolkit-finopshub-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/ptn/avd-lza/insights/ @Azure/avm-ptn-avd-lza-insights-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/ptn/avd-lza/management-plane/ @Azure/avm-ptn-avd-lza-managementplane-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/ptn/avd-lza/networking/ @Azure/avm-ptn-avd-lza-networking-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/ptn/avd-lza/session-hosts/ @Azure/avm-ptn-avd-lza-sessionhosts-module-owners-bicep @Azure/avm-core-team-technical-bicep -/avm/ptn/policy-insights/remediation/ @Azure/avm-ptn-policyinsights-remediation-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/ptn/security/security-center/ @Azure/avm-ptn-securitycenter-module-owners-bicep @Azure/avm-core-team-technical-bicep *avm.core.team.tests.ps1 @Azure/avm-core-team-technical-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index 4817dc7792..44bf6cf839 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -85,7 +85,6 @@ body: - "avm/res/desktop-virtualization/host-pool" - "avm/res/desktop-virtualization/scaling-plan" - "avm/res/desktop-virtualization/workspace" - # - "avm/res/dev-center/devcenter" - "avm/res/dev-test-lab/lab" - "avm/res/digital-twins/digital-twins-instance" - "avm/res/document-db/database-account" @@ -156,7 +155,6 @@ body: - "avm/res/network/vpn-site" - "avm/res/operational-insights/workspace" - "avm/res/operations-management/solution" - # - "avm/res/policy-insights/remediation" - "avm/res/power-bi-dedicated/capacity" - "avm/res/purview/account" - "avm/res/recovery-services/vault" @@ -164,7 +162,6 @@ body: - "avm/res/resource-graph/query" - "avm/res/resources/deployment-script" - "avm/res/resources/resource-group" - # - "avm/res/resources/tags" - "avm/res/search/search-service" - "avm/res/service-bus/namespace" - "avm/res/service-fabric/cluster" From e7e666e51b8ddf7937c38775665c408ef61a44d8 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Fri, 24 May 2024 11:02:32 +0100 Subject: [PATCH 17/18] chore: remove remains of jekyll (#2068) ## Description Remove remains of jekyll as no longer used by this repo. ## Pipeline Reference N/A ## Type of Change - [x] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [ ] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --- .prettierignore | 1 - docs/jekyll/.gitignore | 12 --- docs/jekyll/Gemfile | 11 --- docs/jekyll/Gemfile.lock | 90 --------------------- docs/jekyll/_config.yml | 11 --- docs/jekyll/_sass/color_schemes/wider.scss | 1 - docs/jekyll/assets/images/bicep.ico | Bin 26814 -> 0 bytes 7 files changed, 126 deletions(-) delete mode 100644 docs/jekyll/.gitignore delete mode 100644 docs/jekyll/Gemfile delete mode 100644 docs/jekyll/Gemfile.lock delete mode 100644 docs/jekyll/_config.yml delete mode 100644 docs/jekyll/_sass/color_schemes/wider.scss delete mode 100644 docs/jekyll/assets/images/bicep.ico diff --git a/.prettierignore b/.prettierignore index 972e2d013a..745c1c939e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,3 @@ avm/ modules/ package-lock.json -docs/jekyll diff --git a/docs/jekyll/.gitignore b/docs/jekyll/.gitignore deleted file mode 100644 index 74cd980dd0..0000000000 --- a/docs/jekyll/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Copied from https://github.com/github/gitignore/blob/main/Jekyll.gitignore -# Ignore metadata generated by Jekyll -_site/ -.sass-cache/ -.jekyll-cache/ -.jekyll-metadata - -# Ignore folders generated by Bundler -.bundle/ -vendor/ - -index.md diff --git a/docs/jekyll/Gemfile b/docs/jekyll/Gemfile deleted file mode 100644 index 0a61c185dc..0000000000 --- a/docs/jekyll/Gemfile +++ /dev/null @@ -1,11 +0,0 @@ -source 'https://rubygems.org' - -gem "jekyll", "~> 4.3" # installed by `gem jekyll` -# gem "webrick" # required when using Ruby >= 3 and Jekyll <= 4.2.2 - -gem "just-the-docs", "0.4.2" # pinned to the current release -# gem "just-the-docs" # always download the latest release - -group :jekyll_plugins do - gem 'jekyll-remote-include', :github => 'netrics/jekyll-remote-include' -end \ No newline at end of file diff --git a/docs/jekyll/Gemfile.lock b/docs/jekyll/Gemfile.lock deleted file mode 100644 index ada6db56cc..0000000000 --- a/docs/jekyll/Gemfile.lock +++ /dev/null @@ -1,90 +0,0 @@ -GIT - remote: https://github.com/netrics/jekyll-remote-include.git - revision: 39ae1a1744c8837d5ee64b11c5f982304ea940e5 - specs: - jekyll-remote-include (1.0.2) - -GEM - remote: https://rubygems.org/ - specs: - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - colorator (1.1.0) - concurrent-ruby (1.1.10) - em-websocket (0.5.3) - eventmachine (>= 0.12.9) - http_parser.rb (~> 0) - eventmachine (1.2.7) - ffi (1.15.5) - ffi (1.15.5-x64-mingw-ucrt) - forwardable-extended (2.6.0) - http_parser.rb (0.8.0) - i18n (1.12.0) - concurrent-ruby (~> 1.0) - jekyll (4.3.0) - addressable (~> 2.4) - colorator (~> 1.0) - em-websocket (~> 0.5) - i18n (~> 1.0) - jekyll-sass-converter (>= 2.0, < 4.0) - jekyll-watch (~> 2.0) - kramdown (~> 2.3, >= 2.3.1) - kramdown-parser-gfm (~> 1.0) - liquid (~> 4.0) - mercenary (>= 0.3.6, < 0.5) - pathutil (~> 0.9) - rouge (>= 3.0, < 5.0) - safe_yaml (~> 1.0) - terminal-table (>= 1.8, < 4.0) - webrick (~> 1.7) - jekyll-sass-converter (2.2.0) - sassc (> 2.0.1, < 3.0) - jekyll-seo-tag (2.8.0) - jekyll (>= 3.8, < 5.0) - jekyll-watch (2.2.1) - listen (~> 3.0) - just-the-docs (0.4.2) - jekyll (>= 3.8.5) - jekyll-seo-tag (>= 2.0) - rake (>= 12.3.1) - kramdown (2.4.0) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - liquid (4.0.3) - listen (3.7.1) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - mercenary (0.4.0) - pathutil (0.16.2) - forwardable-extended (~> 2.6) - public_suffix (5.0.0) - rake (13.0.6) - rb-fsevent (0.11.2) - rb-inotify (0.10.1) - ffi (~> 1.0) - rexml (3.2.8) - strscan (>= 3.0.9) - rouge (4.0.0) - safe_yaml (1.0.5) - sassc (2.4.0) - ffi (~> 1.9) - strscan (3.1.0) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - unicode-display_width (2.3.0) - webrick (1.7.0) - -PLATFORMS - arm64-darwin-21 - x64-mingw-ucrt - x86_64-darwin-19 - x86_64-linux - -DEPENDENCIES - jekyll (~> 4.3) - jekyll-remote-include! - just-the-docs (= 0.4.2) - -BUNDLED WITH - 2.3.9 diff --git a/docs/jekyll/_config.yml b/docs/jekyll/_config.yml deleted file mode 100644 index 0d55387d25..0000000000 --- a/docs/jekyll/_config.yml +++ /dev/null @@ -1,11 +0,0 @@ -title: Bicep Registry -theme: just-the-docs - -url: https://just-the-docs.github.io - -aux_links: - Bicep Registry Repository: https://github.com/azure/bicep-registry-modules - -favicon_ico: /assets/images/bicep.ico - -color_scheme: wider diff --git a/docs/jekyll/_sass/color_schemes/wider.scss b/docs/jekyll/_sass/color_schemes/wider.scss deleted file mode 100644 index 0add096e2c..0000000000 --- a/docs/jekyll/_sass/color_schemes/wider.scss +++ /dev/null @@ -1 +0,0 @@ -$content-width: 960px; diff --git a/docs/jekyll/assets/images/bicep.ico b/docs/jekyll/assets/images/bicep.ico deleted file mode 100644 index de6ed3b3cb5e0e8dd2e1f420b8e231c994d7025d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26814 zcmW(+1yEbf7EVHd;8NV7xLbkZ?(XhdytoG_P>Q=3D_*>~h2l`8IK@&Z?(XpTUov-c zGnu)$**$wc+W-K-*XMr^Ab<)`ZVvzuyq-s^tIA=ZlAykxVkpQ5f0)aq%=NT+SbWTRg4|^X zx}Wk9=Y_-sxaGP{XW8fvzB|9`CY4_9i4XLaZs#1AjE!n`!VmV8IV zt7jYPriZf|Va)g_aFjil5#>?^>D*^@U!lr>FrxxP12SJP#$^12(4ufMTlMeY?ajz; zr{lkOLGHRK^`xo<}r=wM~>d>pPV#jG)C;54pgmad>far;A(z2U;TffB zS8Tnd#RXjgnD>jfTCj&lj7OR)3wa4UcUA}=1(M2%hY!*qA2@cjN7PdYCMK&%lWBUE z`sjO(b5rpC?O8>9Mlp*mWPRi@tuPF;CEkD)6#e_;v&Dt}`hls5tPzCPziLTKu zY*hgVtUJqf9d6+r=D3?$8~|1RaknTuycLOoEq@=WcYCv7D-ja({b?Qhmg9{CrK8xC zw>+S?)T&_1;~4cxIYI5BCMXf(blxtYCK-omXZZGszm2dTg~6G66U_y?C|}j;v`Tu{ zK&4y2&E%^18xm<(LAz#ed}BeqaLVAFL^b+jMsz`o3bsAF5_0b~PXVdE-kF7E`08!Y z0{Fv3+CMFSXL-dpsI61bH>!p*vV9#s zVCHA?a)pWVGUA69t)}CE~VBjv@bR4#*u|HspwoL&xiVtr-X}ibT*l!8W z_9(%k*1i10k>ZMJMz?v+;lYvqMzwzgYluC0^4vAK7SovF>Fq=c2bO6HW%7#ocS8LD z?>;^0;RhhkC2^qEB-gj4qPIQ+ zRR#PEBI)L|So|W;mD;3(+1j7-aU3$b_Xxd3Wp)j(#al}Oo`kZU@Zd&0FT%)Bw<`+l z_EgbBQg6|Nl-Wqi5JC6N5UDc?A2S*$+({3P+&8M~gNI#;NO{jGu_Z7!!0k|Zt`g;x zz`E90)qb}5CeD;08CFoHzd zkof81UsX`X$S0AE)`(t(1$?=~KVu!z-GZKJ=qjD!!D2o)`NiDqaR-#9gw@BU!D@Bh zV!NzgoWQYm#akze`y(qk6Gag0?`-COzC1+C(ZwpeW^y*&4)zeyFt$n#5*pK{6t~{J z{&H&KoH_AsQGKPsZ9dMtkHtKs_g$1;!8z$Zb26n(-tM5dtfzunM8xO_JwiP-da(i< zO02LrIfAqr; zRpYG$-2G~=r>Yq6^2C&Ym=xZQ4;s8j8SHyPqBIlQbHqDkr+KaM7$K9tW2URMq9Y-? zyei(UQcYKU?M@meuxEX~_tn1vaGc1sbY(67LFDf2bnZo?jtAd2?u%TZ3B-rB>bNW|ib1>ToOsnWn<6ox~!(=B7F_2Vjr z;%Rp5i;k9cimYJwUD)S#07k->oJX%H@nv?Td-L3c8cvhPT)s#|g0+G!Mv~h_-9yu0 zJ8@}~#m_(PlP+9ViYM#726pU_1T`D;Q!}upxxc@%mVB1s=H3#fj!_(5JoG=3`0eG` z6jb@)E=LVIm~6ePviT@Vb~;eba1qN(jvLyuCt&ZVXHqcfwpq4>;cq=Hdhc{FLKwiF zbk?>)|5kPJ-??2L!{){QNWo`8Zi|x=Q%Z(h{#lH2QRYRBTkRkjg55u04|ux9l~b5_ z5OFhpeo*N_$nn1+_nqMo*O|GgY%`2p zi9H5x?7v{d*H{UMa&;WEHc&GhF>HEFru>a=veuvQG_NyL z{D>OTXED_f6TU_u8W)o}!#}t7I;u^KNVf-A7Qyg8usXw&*IqA{9dLZSoc}sMeOgr` z!>u2n##|Ah49fW!(T^?4u9t{D`(tnQ<<<;pwK z*EVaHkpG|+OGj6+n_mUw6lyBP2(E|UxipE$@zsVjxUz3 zeAbE6)s?!JgR%m%My@d3rX}xU4HHq?u9Q2~*4C9|OXjbF#5!#oOYH^_MVssXFo?S< zneAH5w}q~QubX=6ZWzg4OjYGyUr6$6s{A56tgfEey zf8Q<3m3*cAeBvfR7lz_@^{>%sf)p-IkRjHU9y=+j_Ty=E_%;_!k#b-*d+%O1Z!lfw z?=(8e4)XNC&eC3!SW{8D=n{570p4`HUhi>ZlUwY)B_*lKWuz!D46F z=YiwVPG53-GAL|f3*wD0f+w&Y9BsX1bhK(3tC$U!@J)Czm4ohz4wsd9DfUmL3CgZ@ujuC+m zh}y=rDO`JV%0;`4xq^2IMO8bycRCc?u3{I`t->)(@4WD*_>o_*4vOHuI^oLjkjq;X zU+b?>EaxDht)K%#M5vnq(rHVcU*&<3-(zvPhityxJX0{9fy$umTw@l_~usQnqaPXhec9i@<){jsSkZi@o^%kbDTkZxvDd{1|^DX&nxh5+^!-+Uu8iV z5jl6lbe{CYJ3UrGR1PUZJDJ00p;L(Ve#JHDkr@>%H1W)&pM)T&+dGBKW<)r*hSy4| z9}pfe=uRJ>A;YP5x|$6^4Q^q7iuL>+CSJ!%db>O+JbzIoeCj^iwk|eVv&Y5wEi6`{ zl3ok*cqqDt?5ki(tZD`A8+L*ejWZqV^WW`0kwZEOhlKjXmB+?`F+Kj2_H3Wc|Cm1f z@;R7>Q=0!p$=Se!QDVKMqCA!m*7Uj>*~`xk^m1tO53;~ku(qL}R0n(>5Ot-o5~@h} z&&*eQWEu35T^m>`c80f*^*Q=(@RB&Oa-?3orEgGYrFhOw*}~U(tCU|kYCoL0SibqIBWNP1zV>6 z4(9S(%}EM7_en2#^nFDvBP|b=Ba2QohA`2Xd_$SR678fjVx51*aAt14CRsl>>9mS? zpF5`Kd96vX61VseH`HKiRf21<>2l(e6935v_!;T`g5dHd`y=wIju+dLL@OjQ2zh=myJe zK6*MKZq=?}{F$eYOC7YmnS8Mdpyw^pVhB=F1(#MM%;dQ{|o#q4{#nogCTU}38AX`|JW zeaPTi2r!PXo|Ikle-M~}jo=3Y`e$!2O zD4lZ;602>uvM;c6=->LZd}R^stBGNOeP{aox5XQ+bp@-|#)gP@0_Z$H@c)Tbec4@5 z>_slbA8QgGt@I_051kSv_8bqAf$@8t@Kri)Aep_7>^oCjYJYqVd<3t|B=kL`n-kEW6JKVXP^+Q( zvOJN5gOc{|3I6fiPqt(w>hD%#z(Xo|q5HGd^EB%#&Ui1&d;!F-=&`=J%@+g|~P zIn>;Z_vk`4RiaKF1Hfh-2S2B$sCjmcC17DzaT4Y0R-6WN`WNF{ivp55{PPi$L}6S_i^Ar>my`R!xTfsw=@eR@R@wy{g~k%nRHvS&n$RyoV|5St{@qYLNaAJXK6 zR3(h(Vtm5C%X5FpZjGWLoI9V|7}f?~Wx1&)A7kr^i?Sr5S+ElEU20@aaa*$`)0;pI z8Ea)rd}6uYmtb*}JLam59!j~?mp{v8y!ky(m-&xd?+cvqFCTk7VkYB&XS)NT>K~u8 zwc#8!9jGaMvL0|(8(diJpY%|eSvp2%by@zdUj{{iqtE@P*wGs#q}1tmCW^B31j#5G z#xF1yndPppmly5wvV&L^-M@! z)K;)De2&GEY4&dL2=2Eyd-_B=_%z#Po4)sj?&t%4=0&tT+@;B4#<7y~Fx6h>7jG(! zEZ{7Rv~nIFhV5|Ew8aMc*2RMI2Nk{LM#@{ck8Z+8=RdTi&sJ0mf%ao??Z0oyP_nn! zo0a(E{kjhIJKaes!$kq4d|I!L2avx#HfXMH=XTT$vJft>7DCUi86e!tUK6J8{?4D3 zswO*-o}90VHqv^Vd`z~Pk?4Ajm-3gZ&R^g!HQ;liX6PF2qU+nIMw|Znyw?iiQwt* z<1x2e5r;RNRm8$I$x|!1=!URQT-Y*3<;MIFB;^zYw<4w7?&(+yz+Fvgw!uEl*auN?(r=>gbLEUN3nDUQcfc?j%-v zH9wc#S4dG^bk*E-8)x!4Vl2z68#Q6g`rxna?&xiZu~s?_AO74A2_(~1?AowAcL?O- z`QAJ>lDSKB?%;&7*VLRG`-O=B9hKF6>LVMw-m)9sCvSxU$L(>Tnd}^&CsI%uZR^vJ zR5TbWX0`uS76lgZF8$@Lqbp=72#G?>ZnNoOrIm0O-&Th8f?|1ot4+ja* zQYH7-E?N@*)@qD*I2M%&gw5^1|Q|Fd~X~=)wwiA&LidGAhB3zg)=1^h&)9ZnH(y!~ zG~MufL|y%UfUkw?sWFef_ri?a#M^KY2PqrX>%2;wYo42XUqXgy?7NtgMwVxhFa97* zCUJT>5b>(P&oGU@CDu;X?}-UiIec$5xM?a|*8wjZ&Lk5N7#|!l!|ucMQ3KW5p(dF! zzZ}N2Cx`JP|J=e*n0#Jj)QfhbJ}oCLC^ywcld@tCM;KYsBr?x9xv?O&6GShkfbK2(2%~r&7&_a zM$r@tkt0Nm8DyBz*X9ZI5TF51l zt+N_{T&fI{metN5<4j$px`TFa0zCBz$2kT7XB!`^w@&a9`Eo9X%Km(mlbZa3c`Bl$ zP>>kg%b9~w)ZF+P9fgTD1uM@tz~1-oX|Sk_#7}ZN7jqWcneG@VdI5NgD3C}n++!4{ z!uE+Ds5ms$);Ij&e9+#OGK_b+3kA+W1B)}wWa-Pza(l=!daaMQk&y&(U)yhfElWwE zp|~QW$(y^mp%CMke!K1t`TaqqjA!nHBNh)^V*7>2dw5GBiwNu&hGv4UJYxA{<2%m& z!JPV^!aY9XsVv^ydvGAbV@5PLXM zEB**=gsMmKZCbSqR+=8A1$QslT`YPL5DO2aa0!lFKD6meMen=nILjhkPrm>^hZxh}T(uJ8T@Yz(n{WZY7 z1~?Yp{9K@;GXx_hxVH`Em3yKtK+j9gT6a)84YA%rEWESJnu#dESJt)cI2X@#a~kfl z+cwF$LvVFRVyqTL`cQ=x?q&ll|1Pv74{+MWsAHsm>LNukmmsrWmVQNlu)0*Up%;-a zW>$go_E2hpfHV1#)duRZ!_YUOwVVd&>k;PLkA9F8u+xF#ObLwK`WWa^F=Q>6fL-<` zz$O=p#VVZD^Lur@KE8qwQZAwM@f-U~b}`;fRyp>pGC)Ud^wB`f5w;RZ0=Q2)8 z!EQxK0R6u}JlsyAt#?soRDh%pOR}Nn?t+X$6y`39eS%?a3LA-nJb|#q=`_-BYPb$BcaPm+Ox1EI`(wD4s2Ma+xeqOp6s5`+z)inTuEDjJX`ml z%JA7gJ0i%>RVW3CGT^%VIq=lCY&1(~3vUz(n)aTaN=$*QPt;qG0e%WJQdL1S4j3>u z(&OPxFO2N$2%xM*7C}Qx@N5lKYQBJ=efePVm@GcUwcC9lzWfW>_uAn#e*skz?bHs3 zpxm<314ux=d3P~@@(98*60}VTuR99*=D$lNVb_6XQ&QV<3kg~kc)i?#p;%<>9RRkZ z_No@xiU`I%Q8dXH>!-lgK}T_Nf%UjZ0t+}%Hdd3K%gA<;_B)tQyhVZI6#2SXP%!;h zsvmI(C26xlra^dM7W>?UL5kfpzrDQs=56eG?I-Ut zT)E58M`&zj-()p#m6C?7NBgVP6jd~+w32E&3;_kZ|&{u@fqGz z>P7LSi(W|68F%}sRZ4uT%~f%`;@dq@PR;ghPyUdF9F{G`$EX=oE4$D&*dSa1oiqj2 z`6dB=+Ry;C(aC*Nfn2PxsA8M#mt3~@Q~WKk4)f_s?D9@Gd_$#U5nr&XLKJb;nsr>x z%0ohRCw%7}4!Q!I;;Egb#isd^1J$36Z#JNJE%EsB_=ieYXb+=-_Pmi7_|w>h4d~en zoX9`}n4Don0kSdyr(!fry*}fLE58N)P_7RVPo)%)r_@a@cN$Ivl(i#unE!|)JQUGE ztY*V{2}FNhVz9`JMJp6w?gxC3_*_FYpwI+0$owIzp->RxO=bgi zQ06M(qv~mB1sd3nBD_p3U(yJ|cXY`1JpzGlr||FKsxCrM3cPu%y8gieKv7~dg|U`} zlYSK@VdFrtd3?bLv^N)d!GKM=fb>@d!Kb+pr()T1EEyv;|Y79OvJhHue06sFRLq(tmSM zz3(D+@*@i8O_hmeyc7(z=ijh?E_1vBFxCpL5bfBtR5m06z9@wFC<5YB0ZxoS{fL(6 zN)Bak+7_bnlDau5mY_agKcWqM)<#DzgfOse5ldC6KI2&s@j)@c`u5KXad*92q$`_= zd7i7FaqGv%K3GV`;A80fGV|6rekp5{3y~wsQU85ioPtg#4#sLh#mz_1lzuWowo-$U z$y^1Owy6{f?CBewWdb5tUGjc9#=-HwnTZF7nlLlg3c!S&0(aXZYfO?MoSR~^Kgl(* z@RU?HMEuby>I41;IO-wYj(cSpX?FO7^yy&rW(rZGu-SKrNaH@QCfrcKMlM2Dd0s0v^$0^u5d#GrJwr|jr3KDfU#P*UpP>uQGDCRla6NB^&k3#`$MM5 z#|^bdKO`nZJBw)=wcBmP(_#kBpbARuCd;K$uVvqQV#Yf+;>A0gJ|7k-i-HOkDa&?~ zS)m!eE8tfXRIeMX@XAfzjYV#eS8G>+fXvLb@=?0Z^1Cy~&v?RO?9jgQwWna@gOM+rbsmM&PBKdFSt z`K;$f7P#}`y%LBnY}WM|c97#^GH(I@82QXoIkX7~R%B?MDk^puO~cSzhn|I|egq2SOp_Cfr4KFN9f^odRa zeYa`vozE&!56&&`>}(x6BEIc}%<|yf1`1UMF06hiXjN7J73d@Z42(dsz}T8LNa|6o z;XDo%j9iBW3yOM$%F8MxO=^Pf&6f;((6L)nC9*m|e3<;2vZ5{36+SLuPzx5ddK%B` zwGJZ6?eW|kUwMcM`B_nkbUGn`L_eXx`7xCLk_!(fZNdZYqq91K4e&0lv=|d?x+U7F zveH{i{*L*32MqVmr2|8$0bQ9pU~}U6!qI=o1a>^^+1Ww#_Tx8J#vhui*u8R)b6Uma z=TV|n1oZW=l*B+{e_FfnZp0E^D%xdE95ke`ykGLlJ0Z^o3>L@NzC=y1C}7w1iY(0M z%^cqu=s8UKgEnRuDh5x)?9;sG@V>f^nIww2*IQs6Ju+NT1e%6S>8KwA5wAjM+Pj!@ z$SrN)rNm>xfh*1OU4P@y`2k%j@7ims#+ZmePN3yKJ$!G9sYMoOdT?L-{9y-?VQeDi zNvN!fUGlJ-hOwe!)M<)|@p}aM8M68Oya3UMtr6o4Zyu#}a=eAlm|i!C^BM&|z5`Op z5&)F^mG406#RyH&fK+`M01>E_tVR0+Eub2c9=cuWAk!BXF{11u?yzKvtuMpPp_gGJ z4F&M55FlsNtS3R9llPH04GEbE9y1?}HnJf;{bZ!i|Ms->iBk~}VofpypUNY;Pm>~$ zt?qtoADjDQx1;UKw5KfPm0M7tk7B-3g3#LLPAG`ILrUVM2sH-+i(sRM>M+qkt-jMj zccjh9M?7(uYYJ*|r7-O?5xERL@L>bn=gfq_uqEb^s6b1RkgR}d7M|zvs{luS`4tbt z$33B|1^7WpDBd9r8b`zBD#oRX;%1;0rFxSRLPT0rMMns<{h{qU=y?q6_6xUu2p0>0 z0te7R8Btr+36jMbi%Ix?7nNd=%vAi&h~qGk29ZmFA?+3f$BDt^+BCOZyl!z5Q76^L z17VaYA(4(!1KRWP*OH*i_N7H4<=#9kK3xi<6c#PuK81qB&i|H+o} zuuLvS)V(lD|CImT3tSY+VCjvq*6{+_c8Mzi&vxJZqFC)!rX-kdD)@8&JL5b}-wMC? zfH&&pzkyz8DD_haL159iFjPf=ZYqFPQYbVMT52P3$|Y9kM9IiDf?Oy=gjmw}8N#_Y zH?O#PC__=b7joyI{RQKBpK=~vMd z=M`bwXXjSzrkz7*g=1gg{f1XGywtwFgl0-P)<2cF2Q!81Hsax!M@|pU1P6_~FYXp#`hX5OZXE(stmwAQIm1 zWg|bxYjg17hQH(i;)Rd;1)!Kvil;WWpD2F=K`gmzwr zr~Eg7MJWJZD!|(dA7r0Ssh zLRJW_d*K?KffUpfDF?$uQ%11L|CG$gY%Mz_jg$&{w1khu3$*%Q)JUHa>X?B0-ci3s zG*=mj6mF-nw<2IU#eMTnO$=TyEiJngg5e*C{id+lfp~5$6qpGH$x@*yzisfMm8Ahu zORq-%ya%$n&_JcqKZ_7xixc<8?74ucrqyjh zN$1I{1+71z74LX#q>R7eJ@W8LOIG6{ymz}#7}%E2N)A@}LLt>Vf_i4vH_E|ThWhuN zm+h&Qftq}NTe6g>)s^36Th+3iH|c4QGQh^^4AB0nG+wu4WKzn5=oCSUm>r?61gQCJ z=Mh$PUddRyogS3)_urZzukg^gO7XV20g51zyMg8TA*NQyhhe6$!ZJf9G{ODZRNZW) z>s3i$!P0SI*T*V=kS$~yaW4(Q-S@|#<>6@pje3@i2_e%!Tkmy#?yw%FJHk|d`9nM7 zZg;|9Q{rid>Ynr@!kMgS@@(d@8y>IIW5{C$1Zs&{KO_l)9M97hIO2eEHWRqqTW;Og z083Gd<_%pfv(?=pYR_%36Q!rj8EC%(^HXf7tZE+Tgb%P=hZm#zdRpLx>a;^6mxCNg zW{m<==l{mvMO0$*K;ft_4NW(2YtdGjy^}ECPe)vEeWCt+C8ni;bzc>3qk8{N?fI`L z(EV+H;wPsSO>xJ0K6>hr;8i3+51-IPQKgcBMIU$USzJgrK}2Ub=e(c`Wo zfKf8EMA~&(LPvB^JZqbP8FUpO?i0zVe*zWoz^|teqpkJceynf-rZdaRH>G(B`dFz_pJV z7Ig4QDID#aVDJGf%j1_p5S!Oij%Q^!77w|8t~D7`xCH%#@&(p1w3f$*0kVS}mTbbu zI3-|)&%nLp0n3O3TpQ6`3xIaz57c_aJnUZOg})KaZN1Kdla+ML{u9Ui^TO@7Xu4dv z5K$zd6HofPA-=I%i+>o*XqqIiDuaFE+7w)L7j)&#z~``jqigl;T8&Wwb|xa+Q0!Z@L} zsV$2SzxJesvOTck1h7&;r(NU#`?Ephot)^nn5hcjmtwJesM@2bt?@$6;FJB+jw>We zX`w%WOteBynyv|Rq579XFX#fBWM?OOu$s#~SP*%ep9UHp2Zp87Qgsmxl*EC>*{t;R zL@vx1liX6>n+EtFgu^eNWPWj(usK}&r6I%d^{Bja7pnuX%t!yY?LWIBl3xM@rT6S6 zmw@|&-36VD;Y*w)4c7b+uHF{LxEX%+NPsRC5U-9AWl@9rjSqkXO`hsRD#=c$d05a_>clQSGwVOcN){mR{IQ~@M-k9~x4Ki3+YaC;M? zf}GGMT=3!iZO>wV+NBhG4eIla-2alq8o^g5=JN;dJiNo8qHkp$at8&(651d3$jm#C zfvFmxMI{t-4mv0&YC4R)4Dt+|r^e041qtzQp}m^mUH-L1Jk$e40QC%f=0uRX>?hCBR0yCFTv3U@pz# z0p8muMrZgERK{$vkGrnx@su+d+$MK|qa^C{>wL zd$;}Kr8#=vK~&dFe_CInZ-0oT7}Fqbu@|gvl8Yu8tMp#o8y)CmjsZ-4WBqo3E}$I` zNOdb_NQB8G$NbuW)b7tdFQKg)wKX%}YMj~E-L97P=im6;5yYAhV^z*a7oiIyGKJ|N z-vEDraO{bE!@f9kYt3twhM3hc<{`Xgfk5#UU|-l30A1tZu=jVZiw+$sUomjQQ2YxRD z*CoD%&igzuB?hmif_6w~6D~wU_K+5xVqE6FnO$UsEJ_Bpsy`;ay1ayNE0Rj{;d4!p zM=Q8*lITRGb*LuYd}p1VDOyG=LLaKv=vh zj$$rK2S|abR=qBix33H3x#;3`p(wl;PAqN;T^GJA z3c^}sADEAPo(jP`%-(o(!u|+55eC=sCCI#os=oG+!Dn-LE=q>$N(v;HY{2FR-?wqt z`j0a^JAHN@CGizEjkhgi*d%vjzCAP`^&(h?k$0xX0G&#DJ_3;(U^Nyn%XHggh>slc zPLGcNVWks!Y&H20?dE#tWzFAl~<_k3p)DC65_1nf#Zcy{n z;48NXhFNeh&Tv9|7{d`rZQ3h|gZQIKq2cjh-SBB!!$N(f{OKKHnSWU9dG4Jrs; z_dK0<76tMs5-w+^gF^7%Zcx{p755Jp{`iYUrO>Hnp*P|+be6T712xI^T6~%XV5)Cm z=$XBphSRmXk_bvdzP!uzl1CBf7rzYbG48MV{Oz2l6j7tnWMU*Z(os^{Y+ai4CGy*Z zSnj=*^6E?dn5Fs3xqkh9g%vLPx;~(fj~vQ1wrT!IBX%2D$Y1<+-nREMM#taAEv{dI zeeNt9=9k&%MD1Igfelxju}hg)Rtr1cpW~Mk(Kh*yA~~A>^<`-H{Aow-{wA0n9CG=` ziKu}a;a=!l*kG77eCcy)!ne8tM;6MIR;3%U`TtD9czm$B#Hpn;7+W%QYu&*!be*C# z!0cSW)v3SDZHs;;DXQbxtw$^!=(_TZz;4#l=QQKY=G^L^rv-c->28Q4x)5f2?Dh2 z^7QzfP|r>uFn73-V`i9qn%!)I_q5>hl@WwH598dr8&RMeXJZ=1l$ZQav6Pn(N0D5N zNbTXoNNIkB_GB`E6d=YY1_EuLbJ8nJqEuc={&_j&F^&I^xKZa}dRz?dw2JY0#SDlh zmGCno=cB}AyrD`CExi-AH}HuRSVxCk&_ypN4Jf6~13ea#)BII;5Q)lRXH|1p)RpV{ z{HgezIleEEs9_SOq#{@zDK8s9pbpXx11I(+lfh^d0aZfCuZ?=W1v9ifOf!01G}n{7 z?d6m!x4az`&&6Q=E1MO&K+b$-B z2$dkim_c=}GypN(P_tLA(D4o>F{D{_LtIU@J;S)igVLBM#$tCgR&^8O)E&T5B@Fll z6tL9*4F|CDq99?Q3N$kw`#*STapxvme#>aJ#!Uy-<3Q}rt)h>)ny7YFMT7c;LCqJI z#AoMm>_rPQUwBXr!#|weZ49#fSoGZnc89a@A9KqU|Ap|eMr{Q6rUYUJ%dRbw+?TF*k=kHe7 zss0d<7N&&!y@f4GwXBw_c832`+NFhNd!ZkmVq6_KMw$Cxn5jBzthHor!n{wj7m{q?@Q z+c$3Sw|%s>aVHAW8nSp_`dg@~+Uu>k>J2Hq`_+IfG_;ah-^Hx4v0ETlCEe8?osaTO!y^YNyh`&41^h3xWH zhrs!s{8d}mJ@~#i?()*xpA<7Pm{canUTw`3%Gs~Q1cjiQx~IgL&6&pKrH$Zyw_lB0 zh!REiQL44#)1gJAMR2?q?qZ_;Yw%^->Fl{`{GOUML3qLt*H;z5!efw(P$wpLYPl%+ z${s+y{0Fhx?$m}5u$kMRzp~gnqyPPfN&FIDD)MJ#4DJmvuOB5ba!h6rqWzWNo7

<`@Va(>UGKnmhvqZQ@c+(R7CIZA3bocpHLy1&>TR-zv4m( z93ilpB)}mF3N)%@NWMflKD=#zcQ`zhKF#1yn>=)H_!D(=lY@0xsR5*l zfSA`j9*0F3${a>mY@YF&j(-mO-Lk4<4@WSoj*alZ;HPUXP&2=&|7go~Y;OmGP4>Hq zYub_=>?xVNF*rh`lA!ySZ~5Ugxr$8B0>kTWN!C2Zd*Ekb;~Xvq(sM_MDbuBCslv{4DzdNJ-k8MY)fbvXNm}4UCE@Iu% zy(`89fly7SU-X;4PX?^CjisHivt+~Qo6z@kRwqHQ7G-lRAnR+W({@GaKnX0_sa0UT zvaszghOMU(^*grG7wVdNoC&gc0DVRD9mR~Ov-&yt>qc23=DwnvlJXJY=XWrk1;=X} zhgq;p3$CD^J1HVN?}Ng!Ff^1yzKOP`;S3A%Y-*tFv_|}F{XU!^7e*Y79#>0x`wYd1X1VrAii$>js z4&bDo^aGBnstf(yj$PUo!3H0)DA{2*PFpm>N%qcExE+~S!|wMLS;UVXDs0g{a~-~4 zD!)}d7Q@T`R{fzBWplv>I9a(V50yhHAfyBIlUXK+fa}ViP{dyYQ2QtdV@(G$Uv%S( zXWB0+ueh~7>$cw}J@+I7AuJW$;5C#7YkEQ_QUV46N^C3b=GIr+19Hfq*3a-<$3U6m zYTb5Rs`_*P)x4I?kK|WpJ)?FQGgRnpq^Kj+o>ooDW3nr!vjqZK)ofl(} zJHXj$s#wgzL!E#HQc!WIryHPf*eHHlb1I*{mwjfpa97F`1gdTxzquCC-^rlXg1oD$ z7yjJOqB?ru3R|4pgEcCfp97DoP+*O!k*3!H(yvMZKM0^B?eQL0T$Kl8Pl-Qq5jls0 zJistZQp1xKVgO4B0PWYVq&oB>jwYI~tPz=hc9idJo*O>2di8w*Ba_crU(Ijxn%J&~ zR%AFli`m{;LapCAC{vjl&on@PP@{gR-s;)Pub&HU)XxKTa((3wtd=sEP=COfCfgQa zvlwu9>Q;>EA$BnW+O(F(J^zHJsyo^_L^h_NisQ$PGydE1jE>P$VM!ctQ7#k*#buDU zB?=kSJnzdNn6|%QQc+p1cU4aiCm3k>mtx!XyerWry*;)Y2j;dQjHKD1eNiSy|Dk3( z8b&Vr`i=V2D4*X0{JD-)06+air`Cl5AJx9TIrwUYOmWPVL6F+YwJVFZqCs|yljmW) zZwz>2OU8=p=&YKSXJb3q?X|plEXEga z(KZCReObYH&D>fRk64S*resFl5c(n*QAymRpzyH4#g4aKfY@U9{2qa#pI?yXXKcDu zm6`coI`k!A5>&}bwHz$nRx6HE!nM<$u^r!U4yZy;r-W9LJiqt@$mIYe-`_$pPXVGo zFhfUUYS&}KgnAOkTNEtP+;ON4y3r* z^$kpu4+n;;2tZ>55@~`8U`|;0`vHAJe1+;EM_1weQ)zM;rX(#ZOqTqq3nsyv|K6w; zxhE{Ka8ZALiQk0vJ7Y^i;TuQgx`yC5M6a2Fxo0k>$oghhVjtGuCC4m*4k-kaD zofkSvi|m-!MACH66${+1`erimF9|}gcR=i-E!*XFY}ZYAr2#9VZD0q}By%*bxF{Wz zHvYc>j~u<9M+JT)uA)64YwMu{F=bj^s4B_`Im&am(bQsmC zN&PS9Cm7gLJ_nCjZ~i&=DLJE;CyO_Ql?`zcy1N*3Y~n@9z-~(b_LKk|k~)L{`kMtn z&dKUlEd%09Z^>wBS;CE`&&rgd3~1PaWk3Y1xC9`&!P1T~dtKQNN2+_p7qt3VHmvBp zIP^nQ7L=~Ndgcm$aH9r6Tsd%m(Rt`^m((!?Krx5dV*vrwPy$G7cLG4wG4h3goU^Uk z2!ub2@u*CPOHeC@z5P4GWph>8-Uwu{CKyhX)`^|2wB9dW>TAT3Tyw&ENC^Z-c(>I| z#*i0!ID;|}g7+aA`+t-x8wI?(+OWH$8_iP`SL2;WKCp>)+hVLSnAJa_jG5;l4t~yOC51Ty#mB27>IR9*9u~CjfC{49@ivM^YADlmO5Ju%qD(hLEov zGe z=18!n`F~|%LV#;sJp^!;A_Q0x&k+D#Oh*#Hh=pNLBK1(v9BwG6lza6})`!9wF#|v` z31}y65|Dzay_0}gm3b)%cxWr4J1Qg?`UB3AT47HEz<^A+G>o8Eg_#hM}r|yqOy;_ z_EcT;xUl6`+V_k`H@6}hJbbZi<4pq-RwK?z_X5MM?mIS+1Zlc^!;3cY#w<99q#KhlBnh#o?gmudwPs zklBsfV$ssSP7W7V%eWaR^rg)}*iV2HecE<2P?e0EfkNS!gj0jI#2*Jmn=DYaP7`$l z;>MshC4g8;00|-jSQN*SGT`FsMgquuj22l$0BxmSz%vR4MayK6w@7BD0&7m)09wDT zp}H0{C$9x-rv#~v2gl7Jd)oSV@aTNzGo(Ep+?{2|gEt+dDXwAZeB;0knYRPq{(HEF zEyr!P9hiO=wk*rE9e9W2?ZBok8K7tw3i8wgmAB0XRjcGi5@kT^HXICMk!T zc$%z7Ygi!sirIer+mD|}K0p(%73Tw@)sgiyown}=j9@19ycP(LpD_6i*$0B0ZJU80 z$*nUG#64z@*qxfSmu|qf%Oij{+k=LpzMy0l3v#wupkSK`YLBuWH1pk$Lyt#y2x5P%#*0I`;cJ$6(9+JFEA`d&2752JB@DkxCP&zs{8 znp4(OkgpL-0H_+21ioG5Emc0CxfMp24_kDxLB%<8_j9X)z57l2s&fG`jEJDWv;TA@ zlf{2s((IK(KQ3H_74Au_JVE#QwpeNn#Z@mZUB4|=KfvE~Wg83y&3@HT(8Dqh1zk$R z9z5hk+&*)+q$41pYx3o_OV_P24GXBJ+G{}$C|joh&pfV;2%v@~1h9Y-fOR7Q%<+6< zyu#E?G`?TM#Q4&x#;1;5y$fht(} z5Z6f}_E(^ASg&iQA`v_qi!zAE=}IFZCMt1U17zrg9}`n+-}dtYyLivly9G%{;!5 z0HWDL0O~C!0Qz8zf|&;+Z=MKh^m`h=(Kvtd>Q(`Nld4k^_{4Z8cqbo%+fvly+e*-5 zz~PijToYj4nqa2c1Y>Mew1#Z;29N;EH%Bn7)Zzp;-C+DYTShNnZJQhpnl9Vnp!8=Q zJyYPy4e`4)YiMZrg+nN{{_ZU>be>FppcnOUd)Z`z%JZ;5RszU*tZvH|0$5T*gtoF1 z0MEdm#`*C~oUej&1fIwFZKLQ3^qIZ^taimSA;Fut?=6*Qjl1A5R>fo^P7ua&6ZV`V zDVS}Fcn-L~D?3QQE3QCnX-qW6tX$@DX7l60ajR~N^~T*69S`0#{pirya}wWSQ07U{ zoa^2qPq#_=`aYm+8b!l`OpvFl%7g{75rEK!62Ov9NC0^AO7mkFcVEdk90b2_Z&Tp^ zodiTgFviRoc17=HP7dzqumE`olS*hkOR5_f*A?4Xv72p`S1QC@H&blY-Ymz2yVe`NuB|_AkhlHiQ?bDza{HuW0DuQVT+$DaR+{083p`VZ$<#m zjo>{0#{57QC|ajeaL08*0$5x}gf>x@y{sq!%%uc?iIG>RV2&547==-HKNS?_#{qAq zizrz~0jWuW*`BICX*Fp7wiYH>yTi_?Jy3YA;f>A%Xnj(6##|33$FQjdGv!#9j?O;L zOyR|0mG(|?VBD;=6$xQhqqY?}%$A}-hA9hPyA5l6(qt}eGWW3J&2)APJ`?wa+`MyN z;ydw3Is)2r=~{lx8gH4>FxUXY;1nh-P^KO+55od#BwwX!NEo(A08y5Ry-q*?^akez zf`VBhjq{UeobOHJ{0$uf{1;UNiGY&OnEB3NzC9M=@=rs>`K!!gc=qfWuu-u3fMEod z<a3lr&M)!DDleLx6W5AzX>6t8Xq*+scKT{f9!9Ehe`*!q7fvWRY zCEmxwM}NW+-&_#35%~Xx8Hyn#(-@Gm&ISeR494YuB>_;(A@*3119*`D+?leTvT-Ez zvPc6(<0u;E@1#Im%S!OS>=lRzLGa5eCQKM+xC6el@qi!fd>CSI^vi(2v?H+RWEB)u zUxEuXcE9)F5wKOTcLB5FO&$YoPQoT8r-(cT0CqH#K#u|AU|9%3nwS}h30rd)v1o^5 zlP9y*H;sCGSEPD|v3m<)fU!3SW_h$Z0W>LZUI61Y%h9kv(K3~W1#vGEz!KIFKqMuA zbfytV&9JeoXO;lGS#HcWU6H%rIaPhqDiBT-sXA2*8?+{`VZ4Q*2HW9Bdmk{}8V&0L zvmk&HL+-J1CXB!xi8%2_Tur$n1(Tom?muD<_dH#Gfob$XV!+0nl{A#Vk>GefUQ>^M zQ}VGX&w!!8o)hI#eb(8^dRXSx@qG{Y`RD=R=A{c)Zc26SB{i2}!s5_I*S~#LFvk;A z4MUiOITIFGB#DxM3;{&4hybi2Eaj}T9%GD;^?X%*Ur?X2p;H528r7I2A_c)Oj+7uA zL2v3h_+a*C7-75{HP(5?8oet&G% zK_KIqg}IG6hLnP{QVly{T72(4fW-XM%((IP>Tf`A1$zR}<;VGk(rnLW<{i?oZmDk$ zXv}f%Q1CZbHG=@?X`KlaYmX6gWJ!6)<6qG#iD;Px@fEC7H_6##U65}q@o6w6zV1f? z;>bZ1PB=0i1O7L94DZj{#N-(;l)y^FmasgC$~y^rPF6A-nQ*HzTS)=03)_Qb33Kpw z@T>tmcmCU#n4gB6n0_1n^S8shk{ZPEKd&%l#f|BeSfmK{}TXpK#C za<}f0V5fCd=7o?UA$0c%q^o z2)x0@B;mgTWlD zL~p(~8ECqLXv_~GZ`+(Ae9JJNqE+ewIh*W~US>(4I@=S}C$E1V6Lb0o+UO|=e-VWf zI=`)jkLGS=JceJW$6&cL4t7N5L*m}EaGb{LRrS}HUWNw`|70~8VATX$qOtQ4n~j1} z4l$)loF0TtMX31SxZwyZ6*vS0=Ky3KDu$BkOUyk=8{@y!=6n4PIQZm%uD&M&ezgAW zuWrDnI8inXWqN0N+GbbK(Ar*T9w8(vP7)2XP}1AjiD;Sn5Y|c`b7%6A=_;aZ6;DV4 z5T1ENe|eiUr=B*MSLIEkf%p5a#$=geBN=e|rrJEWAozJD^WW%kX-{4QeWqdZaToly zJOFHV#lyDn-4LF09QK_#2X$9&!~I837|=XpX@k>)M~|622(}la;(z0bL`}Je*6?-; z&s~B!TM|JX0my*ABh(#K=LJA7^F-)jlYOI?O}dAgW#k7W6^M#e0vR@UD;Z$qNjin} zG4>$jA3zA)CTdm@q@U@Qe=A$XPM|lH(9AchAvHlZ?@)xl}9%0j6VX22g~7Tc>`3`-+*hk|M=Tv-L5FO*CIN<5I)B`|4i2o z0=^rl&GljS$Mm$$yiYNbreYocb#D{b|KnLilW~p|P#d<8-kk;f&1KNcE#%$hX+&rd zNaSoXiJpmD4<+lwIrP7e$XO&mRmRT2-_~;=ZFfSz{K87;Wf%s%Ok$ode)ZYz(9hTh z#@UC#)Qt&Xmcy+ z=XipWaRgAK_>5xWq@q=#k&cPGTo0QpA}|jl3TC^Bu7wln64Kk)h3J@27F+t12+Tr- zidHErDdU~%H9rAVsoSOg>slJyIzG>cbK2JG2nt5wpkNvc0(5&D6 z^xj-WE`K{Kb``1^1cRJ;3bp!KH40WK>v`tknj{{Gwy77D(KGWP zDi$$h%9o$NrD_p1MBX~px2ILcO?l%;;7{8@7v^e?2s)4eUb$drpt>@|&|EdB$1vE; zAHHyifN2{O!NxO}h7w01;XoOjuDZl{3^(tITB19;iRm0ADUYP1@ZGXVCf3L67jyvl zRprfx_C zd*3{8OF0e+2P@!6*+n>iV}(;d!h<9gcrcX)v@pa4PEdW@LD;<#n+EA9 zSSD}kX_Z;u%QOzuX1Fk0bU7mETmyKW*Fdh0AH{sDe=#C4d+v8O8?XKTke7Gm78q zZQuzX%=d+%mi{o(HV8&92!;{Xfz1C8rr+-~&x8J)=y&jU`7=8HKGf#=0`7^SM(l1c z%hXu9mdExr*#0j{0ObGXCKF%{viBq0|(w#(Uq(tfY*@%Z-$FVe{a+oe<>RP-=sY1?kgHc(l|f;DP6ONl`Z0C z=*`>lZZGR}>e>1eo_RRYHuB_vE>)s%jtl8M$C(hjvqWGV$Wye4x9e$@Q7*@n4Lw9< z!(Uc%KyInhXQx18Qw0-B05e=!O#*CJEbA#7gg{U8RJ8h)iWUjW1g1ekVt;U5`&uJ)FySrqAOyo+d?_;5geQ_-F0Le} zrplXzWxa6f?x#Ja;6JTo7Hh0wf(hV^3_&di*f#S9D6{_0BAp0~{RnZq zM!)*1E0`xZ_Owi|#j>F)mX@%akO>R8&_FT)MAL=s!h!p>>cS|NFqN^aXO@gszh1#S zaXsJ2Uz@C>YtY1xyls<3`WQHK0BtW(H4P?0Ll43?4j_Z3EPPwVBy^~}d13$!3vS3$ zhUQJ(_B`3e>8o@FC4kUo1i2S7H6_N;A0Z_Z9KVCF{hu6YLa47`*T=SR<{ z-oP2T`H7%1(-VY~)`G^*E2ysIlu=3m29yAdDFOWM!f7+*&vXZ+d7(7UPoeI9TCw8% z7;Ax%uiAgD3P_*X8;Q=`osa~1sV%rq4#&kX=M(?qoTSLMy(ckzq^2apJ$ zYbu-?n8id>e=`R()>jN>ZzXy3E6>!Q44z{Bj*3a}7&-H#$R6gYcjXPjfH!3;1%YEX zCxO2RfX@*CsLu9*UdFKq{6FYgiBmNS`F!B?#sA_N1(Rvx>6)6mjRRKa5+Ew;yokDi z7wKh|Ks4vGc_CK_{H2w7Z$FI7#^G}f-CMM_0a(|Ayx z;SNGP9r)+vjbzYWbU7t}5WtQ~5dm~B#a)6*^MZjI18VhCs})RRmkIT~1;n9(XwGvZ zN=9KEaJp1jFl!qP4Ymv8THbz)zF_ z=5J$el*UHM|b}^^$xvNje>@J`^_i+P%{i9I&*eV-Od4~tBPRuPNMbudZI8t zhP*#<@jq#-_qCi^Vp0#Y)JF>XLBRiYBL#;DAfm5bRT~Mwh!Vhb=Wd~@vpkvmrSATp za%M>xD#jr{4w&lrZ-x1BM0fTEqN%@=14wretr?q$x>X73J#8aVFpVJ^vz--`jKk;A z81QHh)5K>=v%EntaV z%qTLYxq;9V%X((1^$I328+iKOx@6r$!kgz$hWzp~(VW92_&6bW$Y}-<=(~`yBPRSq z#V}x4ulezT)MDK1We^U$DceMubB+KyiPAWKwhzc*S@>;9{37&=jIeqvsa`N@wI<^%vA%Z3~QushD5 zN#lIONVNEm<>n{tRW=HlqBU*P|K#Q;5RKn=l72rqb7ehF#EbfDSEBvxz zooM~CnrSBb@aws6tIqWs+siaAnuY~`patdqwx!XcF6(3ju&mP)fa)wS=w%!Y@0q7P zkTZ=>q-6cYpl__-Q8ElAeJJpCW^$Q*PFg`Urfed8zO^EP87@TCFo1>yn|mr61kdO> zKjA03w*(;VT6*orGlNB_f#RMmDRFu79o`Cml@!Yl(_w zEa4e=63ySYkgxT${-L7p^Fc3@7!SHAukHsW-D#`2g~thtNZ_|M zr0>smL}j)&Av^C7{tPD_dBgDabOE22GmHim>c$G0uwZF3GU!NMN(msS83C|EdDESl zvR+Sxsr!3yJD1Z}Q=UDQ~OJ z@*LXBC_0#i1-E)pi>5YttH=WXK#AaoWgV!L0BD#ZZ$JrP>MmBO%1m#NGmJrZ|E`=- zOoZx8uTcX(H+e^8jt}WGX%W$ywvJn1oUmUsrmkiVh~WM1L{#+sh~~s)?<&p>nAp=e zHou2S!k-GW{eeGmJx2hIs`k4($j=K$SO3qRCUN@}^@FD9Oj_GRQQx2Nrn?dcWkNKk ztmPIKC!I@pKR6O~Ll?rIwwb(RbC3w9ZcPXJif zQ*0{hnIu*y=!e(~r*2mJ=fdMeIBh49Gj=C^e%!<@Bu+ZFD9>?cJOPc#8wegDp*F+4 zzx=$g9dyw*$QeX}>Xe-z{BdQsApl-uSr2BH%nMR)( zcc>;OT}d1riS{pRiPpqrL~&j)dH-|$f2z*%8c$>0nD47^`AJqKr3B>$z$!-B_(Gkt*n%UV$XerY2Sw2^+l1mx!gGO;(GdLaC%ZK(9`t4((m zmGw*#pHjkOx`S1 zm>aT;l0bD&!x&JW<|+ycC{P(PXmx#o0NO-%0B@=jD9#Q5%z5-MPN<`CzN28uR!w4G zK~!hDk-=ZgB3i$$;uZua-BWu1KrJ5t;Z5C1M!rAcAF9(`2FuNh@OsZU;kulD7^qI( z2ErefgOCDNNP*jWx&(Sr0_ZglCm!u=eWmWc67^OMg*w-?mYCv&%Sw^ zH*FVj?M;X#*SF6JCn8ZaiXwvFwi4x8UPR}6oBt@!^!~P|VRSkrf``4R1y!54zS9tZ z%5-<2BnI!ABt4?{kfSu)`zO60?f$1U+mrC8Z6!TSQn(k+2`4WKQ#PEko~Tb;Mcy@y zBf?+TD=5tIGwop%cj7&xI8glE6Zk)^Y9()=^$9?AnhRhulUjVB*L+rCmcNB?@_MCr zO;d=*uWN|fw6$DWj}uNLiE#2(qWi-d!lxbqSr0^Y+Adv%IYArVGm1ao(;y0zr|bg3 zk1OAh2wIN-)G*GU<%gh$_ly%S%Fhkjt~%YRFNQY!-?mWCW(^H-wsJ3)6Ha6i^Y%1Rp1yw44OKTTHZmT2A^+Fe1t`Jc;_xE8bI@;WdeZ zE}wb?kL0Q4<^8h0-3fpK9eU}9p~ZjP(=cYg(oC;u!k--FlxF&pzF$}q%^z10-N_Ez z3*>~8Rw06)RuB>oMD4e2~PiOs8 zdAjQaN&pFz2>$3bI|$S!ZloYzDuxK8Cjgb-JwR?=B)n^!_&`oSJWXl3+c$kaH~Y83 zEMIyJtBJ~#9o!4ygp&^O2qvy2>fhQEIfHN_{Ap!R#os;W^e~7z_?}_hGld!6!25Yk ziwQtf)(b)4Ll1-KV@fl;4Ap;h?Deie9MSxK8R1Rh_UCiLNr#Joh6U>1I#5fzlk`|n zKzI{3Ys$~`U;dt9>^VvXphV+#K9&vt<_)}_0I;m5Fw391`*A>tq*i{G-&)?}ExKfB z4N?1T2hsj<5z+W%8Ta5h;e<`n^UIY)`0;c~1iQ#|8k?(4-u6+iIlU5M7#wuJl~&h6Oagp$S3@t*ASJ( z$G8W{2`8OdR3>jB{2!MSauSH@#0`4X3)nn=jeBsMaMD#o_`_nN{+%6>|J{pxsMY^%rOCS{$ju7QK~?&7$Jc`geDRL_3|}Jr hZZTD^gRi^D{{ucI0)Ba$$0z^*002ovPDHLkV1f&$)r0^5 From 67c4156b2fec8c18116c2fe15791931708716666 Mon Sep 17 00:00:00 2001 From: Kris Baranek Date: Sat, 25 May 2024 11:10:08 +0200 Subject: [PATCH 18/18] feat: Additional outputs to the `avm/res/databricks/workspace` module (#1288) ## Description Added two additional outputs to the `avm/res/databricks/workspace` module, useful when configuring the Databricks workspace: - `workspaceId` - `workspaceUrl` ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.databricks.workspace](https://github.com/krbar/bicep-registry-modules/actions/workflows/avm.res.databricks.workspace.yml/badge.svg?branch=users%2Fkrbar%2FdatabricksUpdate)](https://github.com/krbar/bicep-registry-modules/actions/workflows/avm.res.databricks.workspace.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [x] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/databricks/workspace/README.md | 2 ++ avm/res/databricks/workspace/main.bicep | 6 ++++++ avm/res/databricks/workspace/main.json | 18 ++++++++++++++++-- avm/res/databricks/workspace/version.json | 2 +- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/avm/res/databricks/workspace/README.md b/avm/res/databricks/workspace/README.md index 6fe8579a1a..2cf204bb21 100644 --- a/avm/res/databricks/workspace/README.md +++ b/avm/res/databricks/workspace/README.md @@ -1510,6 +1510,8 @@ Address prefix for Managed virtual network. | `resourceId` | string | The resource ID of the deployed databricks workspace. | | `storageAccountId` | string | The resource ID of the DBFS storage account. | | `storageAccountName` | string | The name of the DBFS storage account. | +| `workspaceId` | string | The unique identifier of the databricks workspace in databricks control plane. | +| `workspaceUrl` | string | The workspace URL which is of the format 'adb-{workspaceId}.{random}.azuredatabricks.net'. | ## Cross-referenced modules diff --git a/avm/res/databricks/workspace/main.bicep b/avm/res/databricks/workspace/main.bicep index 7ebb6dff37..a001cf6f4b 100644 --- a/avm/res/databricks/workspace/main.bicep +++ b/avm/res/databricks/workspace/main.bicep @@ -431,6 +431,12 @@ output storageAccountId string = resourceId( workspace.properties.parameters.storageAccountName.value ) +@description('The workspace URL which is of the format \'adb-{workspaceId}.{random}.azuredatabricks.net\'.') +output workspaceUrl string = workspace.properties.workspaceUrl + +@description('The unique identifier of the databricks workspace in databricks control plane.') +output workspaceId string = workspace.properties.workspaceId + @description('The private endpoints for the Databricks Workspace.') output privateEndpoints array = [ for (pe, i) in (!empty(privateEndpoints) ? array(privateEndpoints) : []): { diff --git a/avm/res/databricks/workspace/main.json b/avm/res/databricks/workspace/main.json index 7e7925b110..35ec12fa08 100644 --- a/avm/res/databricks/workspace/main.json +++ b/avm/res/databricks/workspace/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "8925128185589273318" + "version": "0.26.170.59819", + "templateHash": "7425369674344103053" }, "name": "Azure Databricks Workspaces", "description": "This module deploys an Azure Databricks Workspace.", @@ -1539,6 +1539,20 @@ }, "value": "[resourceId(last(split(reference('workspace').managedResourceGroupId, '/')), 'microsoft.storage/storageAccounts', reference('workspace').parameters.storageAccountName.value)]" }, + "workspaceUrl": { + "type": "string", + "metadata": { + "description": "The workspace URL which is of the format 'adb-{workspaceId}.{random}.azuredatabricks.net'." + }, + "value": "[reference('workspace').workspaceUrl]" + }, + "workspaceId": { + "type": "string", + "metadata": { + "description": "The unique identifier of the databricks workspace in databricks control plane." + }, + "value": "[reference('workspace').workspaceId]" + }, "privateEndpoints": { "type": "array", "metadata": { diff --git a/avm/res/databricks/workspace/version.json b/avm/res/databricks/workspace/version.json index c177b1bb58..3f863a2bec 100644 --- a/avm/res/databricks/workspace/version.json +++ b/avm/res/databricks/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" ]