Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/templates/finops-hub/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,21 @@ param enablePublicAccess bool = true
@description('Optional. Address space for the workload. Minimum /26 subnet size is required for the workload. Default: "10.20.30.0/26".')
param virtualNetworkAddressPrefix string = '10.20.30.0/26'

@description('Optional. Name of an existing VNet to deploy private endpoints into (BYO-VNet for hub-and-spoke). Leave empty to create a new VNet. Default: "".')
param existingVNetName string = ''

@description('Optional. Resource group of the existing VNet. Leave empty if same as deployment RG. Default: "".')
param existingVNetResourceGroupName string = ''

@description('Optional. Subnet name for private endpoints. Default: "snet-finops-pe-01".')
param peSubnetName string = 'snet-finops-pe-01'

@description('Optional. Subnet name for deployment scripts (delegated to Microsoft.ContainerInstance/containerGroups). Default: "snet-finops-script-01".')
param scriptSubnetName string = 'snet-finops-script-01'

@description('Optional. Subnet name for Azure Data Explorer. Default: "snet-finops-adx-01".')
param dataExplorerSubnetName string = 'snet-finops-adx-01'


//==============================================================================
// Resources
Expand Down Expand Up @@ -196,6 +211,11 @@ module hub 'modules/hub.bicep' = {
remoteHubStorageKey: remoteHubStorageKey
enablePublicAccess: enablePublicAccess
virtualNetworkAddressPrefix: virtualNetworkAddressPrefix
existingVNetName: existingVNetName
existingVNetResourceGroupName: existingVNetResourceGroupName
peSubnetName: peSubnetName
scriptSubnetName: scriptSubnetName
dataExplorerSubnetName: dataExplorerSubnetName
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@ param hub HubProperties
// Variables
//==============================================================================

var nsgName = '${hub.routing.networkName}-nsg'
var nsgName = 'nsg-finops-${hub.name}'

// Workaround https://github.com/Azure/bicep/issues/1853
var finopsHubSubnetName = 'private-endpoint-subnet'
var scriptSubnetName = 'script-subnet'
var dataExplorerSubnetName = 'dataExplorer-subnet'
// True when the user supplies their own VNet (BYO / hub-and-spoke).
// In BYO mode subnets must already exist in the existing VNet and we skip creating NSG/VNet here.
var bringYourOwnNetwork = !empty(hub.routing.existingVNetResourceGroupName) || (!empty(hub.routing.networkName) && !startsWith(hub.routing.networkName, 'vnet-finops-'))
var createNetwork = hub.options.privateRouting && !bringYourOwnNetwork

var subnets = !hub.options.privateRouting ? [] : [
// Use the subnet names from hub.routing so they always match what was passed to newHub.
var finopsHubSubnetName = hub.routing.peSubnetName
var scriptSubnetName = hub.routing.scriptSubnetName
var dataExplorerSubnetName = hub.routing.dataExplorerSubnetName

var subnets = !createNetwork ? [] : [
{
name: finopsHubSubnetName
properties: {
Expand Down Expand Up @@ -80,7 +85,7 @@ var subnets = !hub.options.privateRouting ? [] : [
// Network
//------------------------------------------------------------------------------

resource nsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = if (hub.options.privateRouting) {
resource nsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = if (createNetwork) {
name: nsgName
location: hub.location
tags: getHubTags(hub, 'Microsoft.Storage/networkSecurityGroups')
Expand Down Expand Up @@ -168,7 +173,7 @@ resource nsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = if (hub.opti
}
}

resource vNet 'Microsoft.Network/virtualNetworks@2023-11-01' = if (hub.options.privateRouting) {
resource vNet 'Microsoft.Network/virtualNetworks@2023-11-01' = if (createNetwork) {
name: hub.routing.networkName
location: hub.location
tags: getHubTags(hub, 'Microsoft.Storage/virtualNetworks')
Expand Down Expand Up @@ -295,7 +300,7 @@ resource tablePrivateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = if
resource scriptStorageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = if (hub.options.privateRouting) {
name: hub.routing.scriptStorage
dependsOn: [
vNet::scriptSubnet
vNet
]
location: hub.location
sku: {
Expand Down Expand Up @@ -324,9 +329,9 @@ resource scriptStorageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = i
}

resource scriptEndpoint 'Microsoft.Network/privateEndpoints@2023-11-01' = if (hub.options.privateRouting) {
name: '${scriptStorageAccount.name}-blob-ep'
name: 'pep-finops-stgblob-${replace(hub.name, '-01', '-02')}'
dependsOn: [
vNet::scriptSubnet
vNet
]
location: hub.location
tags: getHubTags(hub, 'Microsoft.Network/privateEndpoints')
Expand Down Expand Up @@ -369,21 +374,20 @@ resource scriptEndpoint 'Microsoft.Network/privateEndpoints@2023-11-01' = if (hu
output config HubProperties = hub

@description('Resource ID of the virtual network.')
output vNetId string = !hub.options.privateRouting ? '' : vNet.id
output vNetId string = !hub.options.privateRouting ? '' : hub.routing.networkId

@description('Virtual network address prefixes.')
#disable-next-line BCP318 // Null safety warning for conditional resource access
output vNetAddressSpace array = !hub.options.privateRouting ? [] : vNet.properties.addressSpace.addressPrefixes
output vNetAddressSpace array = !hub.options.privateRouting ? [] : [hub.options.networkAddressPrefix]

@description('Virtual network subnets.')
#disable-next-line BCP318 // Null safety warning for conditional resource access
output vNetSubnets array = !hub.options.privateRouting ? [] : vNet.properties.subnets
output vNetSubnets array = !createNetwork ? [] : vNet.properties.subnets

@description('Resource ID of the FinOps hub network subnet.')
output finopsHubSubnetId string = !hub.options.privateRouting ? '' : vNet::finopsHubSubnet.id
output finopsHubSubnetId string = !hub.options.privateRouting ? '' : hub.routing.subnets.dataFactory

@description('Resource ID of the script storage account network subnet.')
output scriptSubnetId string = !hub.options.privateRouting ? '' : vNet::scriptSubnet.id
output scriptSubnetId string = !hub.options.privateRouting ? '' : hub.routing.subnets.scripts

@description('Resource ID of the Data Explorer network subnet.')
output dataExplorerSubnetId string = !hub.options.privateRouting ? '' : vNet::dataExplorerSubnet.id
output dataExplorerSubnetId string = !hub.options.privateRouting ? '' : hub.routing.subnets.dataExplorer
23 changes: 19 additions & 4 deletions src/templates/finops-hub/modules/fx/hub-app.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ resource factorySelfRoleAssignments 'Microsoft.Authorization/roleAssignments@202

// Create managed identity to start/stop triggers
resource triggerManagerIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = if (usesDataFactory) {
name: '${dataFactory.name}_triggerManager'
name: 'id-finops-${app.hub.name}'
location: app.hub.location
tags: union(app.tags, app.hub.tagsByResource[?'Microsoft.ManagedIdentity/userAssignedIdentities'] ?? {})
}
Expand Down Expand Up @@ -391,7 +391,7 @@ resource blobPrivateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' exist
}

resource blobEndpoint 'Microsoft.Network/privateEndpoints@2023-11-01' = if (usesStorage && app.hub.options.privateRouting) {
name: '${storageAccount.name}-blob-ep'
name: 'pep-finops-stgblob-${app.hub.name}'
location: app.hub.location
tags: getAppPublisherTags(app, 'Microsoft.Network/privateEndpoints')
properties: {
Expand Down Expand Up @@ -430,7 +430,7 @@ resource dfsPrivateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' existi
}

resource dfsEndpoint 'Microsoft.Network/privateEndpoints@2023-11-01' = if (usesStorage && app.hub.options.privateRouting) {
name: '${storageAccount.name}-dfs-ep'
name: 'pep-finops-stgdfs-${app.hub.name}'
location: app.hub.location
tags: getAppPublisherTags(app, 'Microsoft.Network/privateEndpoints')
properties: {
Expand Down Expand Up @@ -515,7 +515,7 @@ resource keyVaultPrivateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' =
}

resource keyVaultEndpoint 'Microsoft.Network/privateEndpoints@2023-11-01' = if (usesKeyVault && app.hub.options.privateRouting) {
name: '${keyVault.name}-ep'
name: 'pep-finops-kv-${app.hub.name}'
location: app.hub.location
tags: getAppPublisherTags(app, 'Microsoft.Network/privateEndpoints')
properties: {
Expand Down Expand Up @@ -550,6 +550,21 @@ resource keyVaultEndpoint 'Microsoft.Network/privateEndpoints@2023-11-01' = if (
}


//==============================================================================
// Event Grid System Topic (explicit name so it doesn't get an auto-generated GUID)
//==============================================================================

resource eventGridSystemTopic 'Microsoft.EventGrid/systemTopics@2024-06-01-preview' = if (usesStorage) {
name: 'evg-finops-${app.hub.name}'
location: app.hub.location
tags: getAppPublisherTags(app, 'Microsoft.EventGrid/systemTopics')
properties: {
#disable-next-line BCP318
source: storageAccount.id
topicType: 'Microsoft.Storage.StorageAccounts'
}
}

//==============================================================================
// Outputs
//==============================================================================
Expand Down
48 changes: 36 additions & 12 deletions src/templates/finops-hub/modules/fx/hub-types.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ type IdNameObject = { id: string, name: string }
type HubRoutingProperties = {
networkId: string
networkName: string
existingVNetResourceGroupName: string
peSubnetName: string
scriptSubnetName: string
dataExplorerSubnetName: string
scriptStorage: string
dnsZones: {
blob: IdNameObject
Expand Down Expand Up @@ -195,6 +199,10 @@ func newHubInternal(
networkName string,
networkAddressPrefix string,
isTelemetryEnabled bool,
existingVNetResourceGroupName string,
peSubnetName string,
scriptSubnetName string,
dataExplorerSubnetName string
) HubProperties => {
id: id
name: name
Expand All @@ -217,21 +225,25 @@ func newHubInternal(
storageSku: storageSku
}
routing: {
networkId: enablePublicAccess ? '' : resourceId('Microsoft.Network/virtualNetworks', networkName)
networkId: enablePublicAccess ? '' : (empty(existingVNetResourceGroupName) ? resourceId('Microsoft.Network/virtualNetworks', networkName) : resourceId(existingVNetResourceGroupName, 'Microsoft.Network/virtualNetworks', networkName))
networkName: enablePublicAccess ? '' : networkName
scriptStorage: enablePublicAccess ? '' : '${take(safeStorageName(name), 16 - length(suffix))}script${suffix}'
existingVNetResourceGroupName: existingVNetResourceGroupName
peSubnetName: peSubnetName
scriptSubnetName: scriptSubnetName
dataExplorerSubnetName: dataExplorerSubnetName
scriptStorage: enablePublicAccess ? '' : take('stgfinops${replace(safeStorageName(name), '-', '')}02', 24)
dnsZones: {
blob: enablePublicAccess ? { id:'', name:'' } : dnsZoneIdName('blob')
dfs: enablePublicAccess ? { id:'', name:'' } : dnsZoneIdName('dfs')
queue: enablePublicAccess ? { id:'', name:'' } : dnsZoneIdName('queue')
table: enablePublicAccess ? { id:'', name:'' } : dnsZoneIdName('table')
}
subnets: {
dataExplorer: enablePublicAccess ? '' : resourceId('Microsoft.Network/virtualNetworks/subnets', networkName, 'dataExplorer-subnet')!
dataFactory: enablePublicAccess ? '' : resourceId('Microsoft.Network/virtualNetworks/subnets', networkName, 'private-endpoint-subnet')!
keyVault: enablePublicAccess ? '' : resourceId('Microsoft.Network/virtualNetworks/subnets', networkName, 'private-endpoint-subnet')!
scripts: enablePublicAccess ? '' : resourceId('Microsoft.Network/virtualNetworks/subnets', networkName, 'script-subnet')!
storage: enablePublicAccess ? '' : resourceId('Microsoft.Network/virtualNetworks/subnets', networkName, 'private-endpoint-subnet')!
dataExplorer: enablePublicAccess ? '' : (empty(existingVNetResourceGroupName) ? resourceId('Microsoft.Network/virtualNetworks/subnets', networkName, dataExplorerSubnetName)! : resourceId(existingVNetResourceGroupName, 'Microsoft.Network/virtualNetworks/subnets', networkName, dataExplorerSubnetName)!)
dataFactory: enablePublicAccess ? '' : (empty(existingVNetResourceGroupName) ? resourceId('Microsoft.Network/virtualNetworks/subnets', networkName, peSubnetName)! : resourceId(existingVNetResourceGroupName, 'Microsoft.Network/virtualNetworks/subnets', networkName, peSubnetName)!)
keyVault: enablePublicAccess ? '' : (empty(existingVNetResourceGroupName) ? resourceId('Microsoft.Network/virtualNetworks/subnets', networkName, peSubnetName)! : resourceId(existingVNetResourceGroupName, 'Microsoft.Network/virtualNetworks/subnets', networkName, peSubnetName)!)
scripts: enablePublicAccess ? '' : (empty(existingVNetResourceGroupName) ? resourceId('Microsoft.Network/virtualNetworks/subnets', networkName, scriptSubnetName)! : resourceId(existingVNetResourceGroupName, 'Microsoft.Network/virtualNetworks/subnets', networkName, scriptSubnetName)!)
storage: enablePublicAccess ? '' : (empty(existingVNetResourceGroupName) ? resourceId('Microsoft.Network/virtualNetworks/subnets', networkName, peSubnetName)! : resourceId(existingVNetResourceGroupName, 'Microsoft.Network/virtualNetworks/subnets', networkName, peSubnetName)!)
}
}
core: {
Expand All @@ -253,6 +265,11 @@ func newHub(
enablePublicAccess bool,
networkAddressPrefix string,
isTelemetryEnabled bool,
existingVNetName string,
existingVNetResourceGroupName string,
peSubnetName string,
scriptSubnetName string,
dataExplorerSubnetName string
) HubProperties => newHubInternal(
'${resourceGroup().id}/providers/Microsoft.Cloud/hubs/${name}', // id
name,
Expand All @@ -265,9 +282,13 @@ func newHub(
keyVaultEnablePurgeProtection,
enableInfrastructureEncryption,
enablePublicAccess,
'${safeStorageName(name)}-vnet-${location}', // networkName, cSpell:ignore vnet
empty(existingVNetName) ? 'vnet-finops-${name}' : existingVNetName, // networkName, cSpell:ignore vnet
networkAddressPrefix,
isTelemetryEnabled ?? true
isTelemetryEnabled ?? true,
existingVNetResourceGroupName,
empty(peSubnetName) ? 'snet-finops-pe-01' : peSubnetName,
empty(scriptSubnetName) ? 'snet-finops-script-01' : scriptSubnetName,
empty(dataExplorerSubnetName) ? 'snet-finops-adx-01' : dataExplorerSubnetName
)

//------------------------------------------------------------------------------
Expand Down Expand Up @@ -298,13 +319,16 @@ func newAppInternal(
hub: hub

// Globally unique Data Factory name: 3-63 chars; letters, numbers, non-repeating dashes
dataFactory: replace('${take('${replace(hub.name, '_', '-')}-engine', 63 - length(suffix) - 1)}-${suffix}', '--', '-')
// Custom naming: adf-finops-<hubName>
dataFactory: take('adf-finops-${replace(hub.name, '_', '-')}', 63)

// Globally unique KeyVault name: 3-24 chars; letters, numbers, dashes
keyVault: replace('${take('${replace(hub.name, '_', '-')}-vault', 24 - length(suffix) - 1)}-${suffix}', '--', '-')
// Custom naming: kv-finops-<hubName>
keyVault: take('kv-finops-${replace(hub.name, '_', '-')}', 24)

// Globally unique storage account name: 3-24 chars; lowercase letters/numbers only
storage: '${take(safeStorageName(hub.name), 24 - length(suffix))}${suffix}'
// Custom naming: stgfinops<hubName>01 (no dashes, lowercase)
storage: take('stgfinops${replace(safeStorageName(hub.name), '-', '')}01', 24)
}

@export()
Expand Down
22 changes: 21 additions & 1 deletion src/templates/finops-hub/modules/hub.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,21 @@ param enablePublicAccess bool = true
@description('Optional. Address space for the workload. Minimum /26 subnet size is required for the workload. Default: "10.20.30.0/26".')
param virtualNetworkAddressPrefix string = '10.20.30.0/26'

@description('Optional. Name of an existing VNet to deploy the FinOps hub private endpoints into. Leave empty to create a new VNet. Default: "".')
param existingVNetName string = ''

@description('Optional. Resource group name of the existing VNet. Leave empty if same resource group as the deployment. Default: "".')
param existingVNetResourceGroupName string = ''

@description('Optional. Name of the subnet for private endpoints (in either the new or existing VNet). Default: "snet-finops-pe-01".')
param peSubnetName string = 'snet-finops-pe-01'

@description('Optional. Name of the subnet for deployment scripts (must be delegated to Microsoft.ContainerInstance/containerGroups). Default: "snet-finops-script-01".')
param scriptSubnetName string = 'snet-finops-script-01'

@description('Optional. Name of the subnet for Azure Data Explorer (only used when ADX is enabled). Default: "snet-finops-adx-01".')
param dataExplorerSubnetName string = 'snet-finops-adx-01'

@description('Optional. Enable telemetry to track anonymous module usage trends, monitor for bugs, and improve future releases.')
param enableDefaultTelemetry bool = true

Expand All @@ -195,7 +210,12 @@ var hub = newHub(
enableInfrastructureEncryption,
enablePublicAccess,
virtualNetworkAddressPrefix,
enableDefaultTelemetry
enableDefaultTelemetry,
existingVNetName,
existingVNetResourceGroupName,
peSubnetName,
scriptSubnetName,
dataExplorerSubnetName
)

var useFabric = !empty(fabricQueryUri)
Expand Down