diff --git a/docs/changelog.md b/docs/changelog.md index 85e00617c..4709c043e 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -70,7 +70,10 @@ Legend: > ➕ Added: > -> 1. Started archiving template versions so they can be referenced easily via URL (microsoft.github.io/finops-toolkit/deploy/finops-hub-{version}.json). +> 1. Allow specifying an existing Key Vault instance. +> - If using template deployment, set the `existingKeyVaultId` parameter to the fully-qualified resource ID. +> - If using the `Deploy-FinOpsHub` PowerShell command, set the `-ExistingKeyVaultId` parameter to the fully-qualified resource ID. +> 2. Started archiving template versions so they can be referenced easily via URL (microsoft.github.io/finops-toolkit/deploy/finops-hub-{version}.json). > > 🛠️ Fixed: > diff --git a/docs/finops-hub/template.md b/docs/finops-hub/template.md index 27ea2c67b..42db2e9b4 100644 --- a/docs/finops-hub/template.md +++ b/docs/finops-hub/template.md @@ -72,13 +72,14 @@ Please ensure the following prerequisites are met before deploying this template ## 📥 Parameters -| Parameter | Type | Description | Default value | -| ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | -| **hubName** | String | Optional. Name of the hub. Used to ensure unique resource names. | `"finops-hub"` | -| **location** | String | Optional. Azure location where all resources should be created. See https://aka.ms/azureregions. | (resource group location) | -| **storageSku** | String | 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`. | `Premium_LRS` | -| **tags** | Object | Optional. Tags to apply to all resources. We will also add the `cm-resource-parent` tag for improved cost roll-ups in Cost Management. | -| **exportScopes** | Array | Optional. List of scope IDs to create exports for. | +| Parameter | Type | Description | Default value | +| ---------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | +| **hubName** | String | Optional. Name of the hub. Used to ensure unique resource names. | `"finops-hub"` | +| **location** | String | Optional. Azure location where all resources should be created. See https://aka.ms/azureregions. | (resource group location) | +| **storageSku** | String | 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`. | `Premium_LRS` | +| **existingKeyVaultId** | String | Optional. Resource ID of the existing Key Vault resource to use. If not specified, a new Key Vault instance will be created. | | +| **tags** | Object | Optional. Tags to apply to all resources. We will also add the `cm-resource-parent` tag for improved cost roll-ups in Cost Management. | | +| **exportScopes** | Array | Optional. List of scope IDs to create exports for. | |
diff --git a/docs/powershell/hubs/Deploy-FinOpsHub.md b/docs/powershell/hubs/Deploy-FinOpsHub.md index bbf374662..62dccb0bf 100644 --- a/docs/powershell/hubs/Deploy-FinOpsHub.md +++ b/docs/powershell/hubs/Deploy-FinOpsHub.md @@ -40,6 +40,7 @@ Deploy-FinOpsHub ` -Name ` -ResourceGroup ` -Location ` + [-KeyVaultId ] ` [-Version ] ` [-Preview] ` [-StorageSku ] ` @@ -57,6 +58,7 @@ Deploy-FinOpsHub ` | `‑ResourceGroup` | Required. Name of the resource group to deploy to. Will be created if it doesn't exist. | | `‑Location` | Required. Azure location to execute the deployment from. | | `‑Version` | Optional. Version of the FinOps hub template to use. Default = "latest". | +| `‑KeyVaultId` | Optional. Resource ID of the existing Key Vault instance to use. If not specified, one will be created. | | `‑Preview` | Optional. Indicates that preview releases should also be included. Default = false. | | `‑StorageSku` | Optional. Storage account SKU. Premium_LRS = Lowest cost, Premium_ZRS = High availability. Note Standard SKUs are not available for Data Lake gen2 storage. Default = "Premium_LRS". | | `‑Tags` | Optional. Tags for all resources. | @@ -88,12 +90,24 @@ Deploy-FinOpsHub ` Deploys a new FinOps hub instance named MyHub to a new resource group named MyNewResourceGroup using version {% include version.txt %} of the template. +### Use existing Key Vault instance + +```powershell +Deploy-FinOpsHub ` + -Name MyHub ` + -ResourceGroupName MyExistingResourceGroup ` + -Location westus ` + -KeyVaultId "/subscriptions/###/resourceGroups/###/providers/Microsoft.KeyVault/vaults/foo" +``` + +Deploys a new FinOps hub instance named MyHub using an existing Key Vault instance. +
--- ## 🧰 Related tools -{% include tools.md hubs="1" %} +{% include tools.md hubs="1" pbi="1" %}
diff --git a/src/powershell/Public/Deploy-FinOpsHub.ps1 b/src/powershell/Public/Deploy-FinOpsHub.ps1 index 6384a006f..74677bca3 100644 --- a/src/powershell/Public/Deploy-FinOpsHub.ps1 +++ b/src/powershell/Public/Deploy-FinOpsHub.ps1 @@ -19,6 +19,9 @@ .PARAMETER Location Required. Azure location to execute the deployment from. + .PARAMETER KeyVaultId + Optional. Resource ID of the existing Key Vault instance to use. If not specified, one will be created. + .PARAMETER Version Optional. Version of the FinOps hub template to use. Default = "latest". @@ -35,12 +38,17 @@ Deploy-FinOpsHub -Name MyHub -ResourceGroupName MyExistingResourceGroup -Location westus Deploys a new FinOps hub instance named MyHub to an existing resource group named MyExistingResourceGroup. - + .EXAMPLE Deploy-FinOpsHub -Name MyHub -Location westus -Version 0.1 - + Deploys a new FinOps hub instance named MyHub using version 0.1 of the template. + .EXAMPLE + Deploy-FinOpsHub -Name MyHub -ResourceGroupName MyExistingResourceGroup -Location westus -KeyVaultId "/subscriptions/###/resourceGroups/###/providers/Microsoft.KeyVault/vaults/foo" + + Deploys a new FinOps hub instance named MyHub using an existing Key Vault instance. + .LINK https://aka.ms/ftk/Deploy-FinOpsHub #> @@ -62,6 +70,10 @@ function Deploy-FinOpsHub [string] $Location, + [Parameter(Mandatory = $false)] + [string] + $KeyVaultId = '', + [Parameter()] [string] $Version = 'latest', @@ -112,6 +124,7 @@ function Deploy-FinOpsHub TemplateParameterObject = @{ hubName = $Name storageSku = $StorageSku + existingKeyVaultId = $KeyVaultId } } diff --git a/src/templates/finops-hub/main.bicep b/src/templates/finops-hub/main.bicep index b26b1888e..f45efa423 100644 --- a/src/templates/finops-hub/main.bicep +++ b/src/templates/finops-hub/main.bicep @@ -20,6 +20,9 @@ param location string = resourceGroup().location @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. Resource ID of the existing Key Vault resource to use. If not specified, a new Key Vault instance will be created.') +param existingKeyVaultId string = '' + @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 = {} @@ -39,6 +42,7 @@ module hub 'modules/hub.bicep' = { hubName: hubName location: location storageSku: storageSku + existingKeyVaultId: existingKeyVaultId tags: tags tagsByResource: tagsByResource exportScopes: exportScopes diff --git a/src/templates/finops-hub/modules/dataFactory.bicep b/src/templates/finops-hub/modules/dataFactory.bicep index c03387fa7..946b37ce5 100644 --- a/src/templates/finops-hub/modules/dataFactory.bicep +++ b/src/templates/finops-hub/modules/dataFactory.bicep @@ -5,11 +5,11 @@ // Parameters //============================================================================== -@description('Optional. Name of the hub. Used to ensure unique resource names. Default: "finops-hub".') +@description('Required. Name of the hub. Used to ensure unique resource names.') param dataFactoryName string -@description('Required. The name of the Azure Key Vault instance.') -param keyVaultName string +@description('Optional. The resource ID of the Azure Key Vault instance.') +param keyVaultId string @description('Required. The name of the Azure storage account instance.') param storageAccountName string @@ -293,7 +293,8 @@ resource stopHubTriggers 'Microsoft.Resources/deploymentScripts@2020-10-01' = { //------------------------------------------------------------------------------ resource keyVault 'Microsoft.KeyVault/vaults@2022-11-01' existing = { - name: keyVaultName + name: last(split(keyVaultId, '/')) + scope: resourceGroup(split(keyVaultId, '/')[2], split(keyVaultId, '/')[4]) } resource linkedService_keyVault 'Microsoft.DataFactory/factories/linkedservices@2018-06-01' = { diff --git a/src/templates/finops-hub/modules/hub.bicep b/src/templates/finops-hub/modules/hub.bicep index f6226e421..2c6e8f17e 100644 --- a/src/templates/finops-hub/modules/hub.bicep +++ b/src/templates/finops-hub/modules/hub.bicep @@ -18,6 +18,9 @@ param location string = resourceGroup().location @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. Resource ID of the existing Key Vault resource to use. If not specified, a new Key Vault instance will be created.') +param existingKeyVaultId string = '' + @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 = {} @@ -122,7 +125,7 @@ module dataFactoryResources 'dataFactory.bicep' = { params: { dataFactoryName: dataFactoryName convertToParquet: convertToParquet - keyVaultName: keyVault.outputs.name + keyVaultId: keyVault.outputs.resourceId storageAccountName: storage.outputs.name exportContainerName: storage.outputs.exportContainer ingestionContainerName: storage.outputs.ingestionContainer @@ -138,8 +141,10 @@ module dataFactoryResources 'dataFactory.bicep' = { module keyVault 'keyVault.bicep' = { name: 'keyVault' + scope: empty(existingKeyVaultId) ? resourceGroup() : resourceGroup(split(existingKeyVaultId, '/')[2], split(existingKeyVaultId, '/')[4]) params: { hubName: hubName + existingKeyVaultName: last(split(existingKeyVaultId, '/')) uniqueSuffix: uniqueSuffix location: location tags: resourceTags diff --git a/src/templates/finops-hub/modules/keyVault.bicep b/src/templates/finops-hub/modules/keyVault.bicep index 719839f6e..78166d522 100644 --- a/src/templates/finops-hub/modules/keyVault.bicep +++ b/src/templates/finops-hub/modules/keyVault.bicep @@ -11,6 +11,9 @@ param hubName string @description('Required. Suffix to add to the KeyVault instance name to ensure uniqueness.') param uniqueSuffix string +@description('Optional. Resource ID of the existing Key Vault resource to use. If not specified, a new Key Vault instance will be created.') +param existingKeyVaultName string + @description('Optional. Location for all resources.') param location string = resourceGroup().location @@ -49,11 +52,42 @@ var formattedAccessPolicies = [for accessPolicy in accessPolicies: { tenantId: contains(accessPolicy, 'tenantId') ? accessPolicy.tenantId : tenant().tenantId }] +var storageSecretProperties = { + attributes: { + enabled: true + exp: 1702648632 + nbf: 10000 + } + value: storageRef.listKeys().keys[0].value +} + //============================================================================== // Resources //============================================================================== -resource keyVault 'Microsoft.KeyVault/vaults@2022-11-01' = { +resource storageRef 'Microsoft.Storage/storageAccounts@2022-09-01' existing = { + name: storageAccountName +} + +// Get existing key vault, if existingKeyVaultName is set +resource existingKeyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = if (!empty(existingKeyVaultName)) { + name: empty(existingKeyVaultName) ? 'placeholder' : existingKeyVaultName + + resource existingKeyVault_accessPolicies 'accessPolicies@2023-07-01' = if (!empty(accessPolicies)) { + name: 'add' + properties: { + accessPolicies: formattedAccessPolicies + } + } + + resource existingKeyVault_secrets 'secrets@2023-07-01' = { + name: storageRef.name + properties: storageSecretProperties + } +} + +// Create new key vault, if existingKeyVaultName is not set +resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = if (empty(existingKeyVaultName)) { name: keyVaultName location: location tags: union(tags, contains(tagsByResource, 'Microsoft.KeyVault/vaults') ? tagsByResource['Microsoft.KeyVault/vaults'] : {}) @@ -73,30 +107,17 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-11-01' = { family: 'A' } } -} - -resource keyVault_accessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-11-01' = if (!empty(accessPolicies)) { - name: 'add' - parent: keyVault - properties: { - accessPolicies: formattedAccessPolicies - } -} -resource storageRef 'Microsoft.Storage/storageAccounts@2022-09-01' existing = { - name: storageAccountName -} - -resource keyVault_secrets 'Microsoft.KeyVault/vaults/secrets@2022-11-01' = { - name: storageRef.name - parent: keyVault - properties: { - attributes: { - enabled: true - exp: 1702648632 - nbf: 10000 + resource keyVault_accessPolicies 'accessPolicies@2023-07-01' = if (!empty(accessPolicies)) { + name: 'add' + properties: { + accessPolicies: formattedAccessPolicies } - value: storageRef.listKeys().keys[0].value + } + + resource keyVault_secrets 'secrets@2023-07-01' = { + name: storageRef.name + properties: storageSecretProperties } } @@ -105,10 +126,10 @@ resource keyVault_secrets 'Microsoft.KeyVault/vaults/secrets@2022-11-01' = { //============================================================================== @description('The resource ID of the key vault.') -output resourceId string = keyVault.id +output resourceId string = empty(existingKeyVaultName) ? keyVault.id : existingKeyVault.id @description('The name of the key vault.') -output name string = keyVault.name +output name string = empty(existingKeyVaultName) ? keyVault.name : existingKeyVault.name @description('The URI of the key vault.') -output uri string = keyVault.properties.vaultUri +output uri string = empty(existingKeyVaultName) ? keyVault.properties.vaultUri : existingKeyVault.properties.vaultUri