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