Skip to content

Commit 320d45d

Browse files
authored
[Hub apps] Move file uploads to hub-storage (#1543)
1 parent d7bb788 commit 320d45d

File tree

6 files changed

+170
-48
lines changed

6 files changed

+170
-48
lines changed

src/templates/finops-hub/modules/dataFactory.bicep

+7-7
Original file line numberDiff line numberDiff line change
@@ -822,15 +822,15 @@ resource dataset_ftkReleaseFile 'Microsoft.DataFactory/factories/datasets@2018-0
822822
// TODO: Create apps_PublishEvent pipeline { event, properties }
823823

824824
module trigger_ExportManifestAdded 'hub-event-trigger.bicep' = {
825-
name: 'trigger_ExportManifestAdded'
825+
name: 'Microsoft.FinOpsHubs.Core_ExportManifestAddedTrigger'
826826
dependsOn: [
827827
stopTriggers
828828
]
829829
params: {
830830
dataFactoryName: dataFactory.name
831831
triggerName: exportManifestAddedTriggerName
832832

833-
// TODO: Replace pipeline with event: 'Microsoft.FinOpsToolkit.CostManagement.ExportManifestAdded'
833+
// TODO: Replace pipeline with event: 'Microsoft.CostManagement.Exports.ManifestAdded'
834834
pipelineName: pipeline_ExecuteExportsETL.name
835835
pipelineParameters: {
836836
folderPath: '@triggerBody().folderPath'
@@ -843,16 +843,16 @@ module trigger_ExportManifestAdded 'hub-event-trigger.bicep' = {
843843
}
844844
}
845845

846-
module trigger_IngestionManifestAdded 'hub-event-trigger.bicep' = {
847-
name: 'trigger_IngestionManifestAdded'
846+
module trigger_IngestionManifestAdded 'hub-event-trigger.bicep' = if (deployDataExplorer) {
847+
name: 'Microsoft.FinOpsHubs.Core_IngestionManifestAddedTrigger'
848848
dependsOn: [
849849
stopTriggers
850850
]
851851
params: {
852852
dataFactoryName: dataFactory.name
853853
triggerName: ingestionManifestAddedTriggerName
854854

855-
// TODO: Replace pipeline with event: 'Microsoft.FinOpsToolkit.Hubs.IngestionManifestAdded'
855+
// TODO: Replace pipeline with event: 'Microsoft.FinOpsHubs.Core.IngestionManifestAdded'
856856
pipelineName: pipeline_ExecuteIngestionETL.name
857857
pipelineParameters: {
858858
folderPath: '@triggerBody().folderPath'
@@ -865,15 +865,15 @@ module trigger_IngestionManifestAdded 'hub-event-trigger.bicep' = {
865865
}
866866

867867
module trigger_SettingsUpdated 'hub-event-trigger.bicep' = {
868-
name: 'trigger_SettingsUpdated'
868+
name: 'Microsoft.FinOpsHubs.Core_SettingsUpdatedTrigger'
869869
dependsOn: [
870870
stopTriggers
871871
]
872872
params: {
873873
dataFactoryName: dataFactory.name
874874
triggerName: updateConfigTriggerName
875875

876-
// TODO: Replace pipeline with event: 'Microsoft.FinOpsToolkit.Hubs.SettingsUpdated'
876+
// TODO: Replace pipeline with event: 'Microsoft.FinOpsHubs.Core.SettingsUpdated'
877877
pipelineName: pipeline_ConfigureExports.name
878878
pipelineParameters: {}
879879

src/templates/finops-hub/modules/hub-storage.bicep

+104-5
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,47 @@
55
// Parameters
66
//==============================================================================
77

8+
@description('Required. Name of the storage container to create or update.')
9+
param container string
10+
11+
@description('Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload).')
12+
param files object = {}
13+
14+
//------------------------------------------------------------------------------
15+
// Hub context
16+
//------------------------------------------------------------------------------
17+
818
@description('Required. Name of the publisher-specific storage account to create or update.')
919
param storageAccountName string
1020

11-
@description('Required. Name of the storage container to create or update.')
12-
param container string
21+
@description('Optional. Azure location where all resources should be created. See https://aka.ms/azureregions. Default: (resource group location).')
22+
param location string = resourceGroup().location
23+
24+
@description('Optional. Tags to apply to all resources.')
25+
param tags object = {}
26+
27+
@description('Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources.')
28+
param tagsByResource object = {}
29+
30+
@description('Optional. The name of the managed identity to use for uploading files.')
31+
param blobManagerIdentityName string = ''
32+
33+
@description('Required. Indicates whether public access should be enabled.')
34+
param enablePublicAccess bool
35+
36+
@description('Optional. The name of the storage account used for deployment scripts.')
37+
param scriptStorageAccountName string = ''
38+
39+
@description('Optional. Resource ID of the virtual network for running deployment scripts.')
40+
param scriptSubnetId string = ''
41+
42+
43+
//==============================================================================
44+
// Variables
45+
//==============================================================================
46+
47+
var fileCount = length(items(files))
48+
var hasFiles = fileCount > 0
1349

1450

1551
//==============================================================================
@@ -23,7 +59,7 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' existing
2359
resource blobService 'blobServices@2022-09-01' = {
2460
name: 'default'
2561

26-
resource configContainer 'containers@2022-09-01' = {
62+
resource targetContainer 'containers@2022-09-01' = {
2763
name: container
2864
properties: {
2965
publicAccess: 'None'
@@ -33,11 +69,74 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' existing
3369
}
3470
}
3571

36-
// TODO: Upload files
3772
// TODO: Enforce retention
3873

74+
//------------------------------------------------------------------------------
75+
// Upload schema file to storage
76+
//------------------------------------------------------------------------------
77+
78+
resource scriptStorageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' existing = if (hasFiles && !enablePublicAccess) {
79+
name: scriptStorageAccountName
80+
}
81+
82+
resource blobManagerIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (hasFiles) {
83+
name: blobManagerIdentityName
84+
}
85+
86+
resource uploadFiles 'Microsoft.Resources/deploymentScripts@2023-08-01' = if (hasFiles) {
87+
name: '${storageAccountName}_uploadFiles'
88+
kind: 'AzurePowerShell'
89+
// cSpell:ignore chinaeast
90+
// chinaeast2 is the only region in China that supports deployment scripts
91+
location: startsWith(location, 'china') ? 'chinaeast2' : location
92+
tags: union(tags, tagsByResource[?'Microsoft.Resources/deploymentScripts'] ?? {})
93+
identity: {
94+
type: 'UserAssigned'
95+
userAssignedIdentities: {
96+
'${blobManagerIdentity.id}': {}
97+
}
98+
}
99+
dependsOn: []
100+
properties: union(enablePublicAccess ? {} : {
101+
storageAccountSettings: {
102+
storageAccountName: scriptStorageAccount.name
103+
}
104+
containerSettings: {
105+
containerGroupName: '${scriptStorageAccount.name}cg'
106+
subnetIds: [
107+
{
108+
id: scriptSubnetId
109+
}
110+
]
111+
}
112+
}, {
113+
azPowerShellVersion: '9.0'
114+
retentionInterval: 'PT1H'
115+
environmentVariables: [
116+
{
117+
name: 'storageAccountName'
118+
value: storageAccount.name
119+
}
120+
{
121+
name: 'containerName'
122+
value: container
123+
}
124+
{
125+
name: 'files'
126+
value: string(files)
127+
}
128+
]
129+
scriptContent: loadTextContent('./scripts/Upload-StorageFile.ps1')
130+
})
131+
}
132+
133+
39134
//==============================================================================
40135
// Outputs
41136
//==============================================================================
42137

43-
output containerName string = storageAccount::blobService::configContainer.name
138+
@description('The name of the storage container.')
139+
output containerName string = storageAccount::blobService::targetContainer.name
140+
141+
@description('The number of files uploaded to the storage container.')
142+
output filesUploaded int = fileCount

src/templates/finops-hub/modules/hub.bicep

+2-2
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,8 @@ module appRegistration 'hub-app.bicep' = {
248248
name: 'pid-${telemetryId}_${telemetryString}_${uniqueString(deployment().name, location)}'
249249
params: {
250250
hubName: hubName
251-
publisher: 'FinOps hubs'
252-
namespace: 'Microsoft.FinOpsToolkit.Hubs'
251+
publisher: 'Microsoft FinOps hubs'
252+
namespace: 'Microsoft.FinOpsHubs'
253253
appName: 'Core'
254254
displayName: 'FinOps hub core'
255255
appVersion: finOpsToolkitVersion

src/templates/finops-hub/modules/scripts/Copy-FileToAzureBlob.ps1

-11
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,3 @@ $text | Out-File $filePath
168168
# Upload new/updated settings
169169
Write-Output "Uploading settings.json file..."
170170
Set-AzStorageBlobContent @storageContext -File $filePath -Force | Out-Null
171-
172-
# Save focusSchemaFile file to storage
173-
$schemaFiles = $env:schemaFiles | ConvertFrom-Json -Depth 10
174-
Write-Output "Uploading ${$schemaFiles.PSObject.Properties.Count} schema files..."
175-
$schemaFiles.PSObject.Properties | ForEach-Object {
176-
$fileName = "$($_.Name).json"
177-
$tempPath = "./$fileName"
178-
Write-Output " Uploading $($_.Name).json..."
179-
$_.Value | Out-File $tempPath
180-
Set-AzStorageBlobContent @storageContext -File $tempPath -Blob "schemas/$fileName" -Force | Out-Null
181-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
# Get storage context
5+
$storageContext = @{
6+
Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount
7+
Container = $env:containerName
8+
}
9+
10+
# Uploading files
11+
$files = $env:files | ConvertFrom-Json -Depth 10
12+
Write-Output "Uploading ${$files.PSObject.Properties.Count} files..."
13+
$files.PSObject.Properties | ForEach-Object {
14+
$filePath = $_.Name
15+
$tempPath = "./$($filePath -replace "/", "_")"
16+
Write-Output " Uploading $filePath..."
17+
$_.Value | Out-File $tempPath
18+
Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null
19+
}

src/templates/finops-hub/modules/storage.bicep

+38-23
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,19 @@ var storageAccountSuffix = uniqueSuffix
6868
var storageAccountName = '${take(safeHubName, 24 - length(storageAccountSuffix))}${storageAccountSuffix}'
6969
var scriptStorageAccountName = '${take(safeHubName, 16 - length(storageAccountSuffix))}script${storageAccountSuffix}'
7070
var schemaFiles = {
71-
// cSpell:ignore focuscost, pricesheet, reservationdetails, reservationrecommendations, reservationtransactions
72-
'actualcost_c360-2025-04': loadTextContent('../schemas/actualcost_c360-2025-04.json')
73-
'amortizedcost_c360-2025-04': loadTextContent('../schemas/amortizedcost_c360-2025-04.json')
74-
'focuscost_1.0r2': loadTextContent('../schemas/focuscost_1.0r2.json')
75-
'focuscost_1.0': loadTextContent('../schemas/focuscost_1.0.json')
76-
'focuscost_1.0-preview(v1)': loadTextContent('../schemas/focuscost_1.0-preview(v1).json')
77-
'pricesheet_2023-05-01_ea': loadTextContent('../schemas/pricesheet_2023-05-01_ea.json')
78-
'pricesheet_2023-05-01_mca': loadTextContent('../schemas/pricesheet_2023-05-01_mca.json')
79-
'reservationdetails_2023-03-01': loadTextContent('../schemas/reservationdetails_2023-03-01.json')
80-
'reservationrecommendations_2023-05-01_ea': loadTextContent('../schemas/reservationrecommendations_2023-05-01_ea.json')
81-
'reservationrecommendations_2023-05-01_mca': loadTextContent('../schemas/reservationrecommendations_2023-05-01_mca.json')
82-
'reservationtransactions_2023-05-01_ea': loadTextContent('../schemas/reservationtransactions_2023-05-01_ea.json')
83-
'reservationtransactions_2023-05-01_mca': loadTextContent('../schemas/reservationtransactions_2023-05-01_mca.json')
71+
// cSpell:ignore actualcost, amortizedcost, focuscost, pricesheet, reservationdetails, reservationrecommendations, reservationtransactions
72+
'schemas/actualcost_c360-2025-04.json': loadTextContent('../schemas/actualcost_c360-2025-04.json')
73+
'schemas/amortizedcost_c360-2025-04.json': loadTextContent('../schemas/amortizedcost_c360-2025-04.json')
74+
'schemas/focuscost_1.0r2.json': loadTextContent('../schemas/focuscost_1.0r2.json')
75+
'schemas/focuscost_1.0.json': loadTextContent('../schemas/focuscost_1.0.json')
76+
'schemas/focuscost_1.0-preview(v1).json': loadTextContent('../schemas/focuscost_1.0-preview(v1).json')
77+
'schemas/pricesheet_2023-05-01_ea.json': loadTextContent('../schemas/pricesheet_2023-05-01_ea.json')
78+
'schemas/pricesheet_2023-05-01_mca.json': loadTextContent('../schemas/pricesheet_2023-05-01_mca.json')
79+
'schemas/reservationdetails_2023-03-01.json': loadTextContent('../schemas/reservationdetails_2023-03-01.json')
80+
'schemas/reservationrecommendations_2023-05-01_ea.json': loadTextContent('../schemas/reservationrecommendations_2023-05-01_ea.json')
81+
'schemas/reservationrecommendations_2023-05-01_mca.json': loadTextContent('../schemas/reservationrecommendations_2023-05-01_mca.json')
82+
'schemas/reservationtransactions_2023-05-01_ea.json': loadTextContent('../schemas/reservationtransactions_2023-05-01_ea.json')
83+
'schemas/reservationtransactions_2023-05-01_mca.json': loadTextContent('../schemas/reservationtransactions_2023-05-01_mca.json')
8484
}
8585

8686
// Roles needed to upload files
@@ -347,27 +347,46 @@ resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2022-09-01'
347347
name: 'default'
348348
}
349349

350+
// TODO: Move to core module
350351
module configContainer 'hub-storage.bicep' = {
351-
name: 'configContainer'
352+
name: 'Microsoft.FinOpsHubs.Core_SchemaFiles'
352353
params: {
353-
storageAccountName: storageAccount.name
354354
container: 'config'
355+
files: schemaFiles
356+
357+
// Hub context
358+
storageAccountName: storageAccount.name
359+
location: location
360+
tags: tags
361+
tagsByResource: tagsByResource
362+
blobManagerIdentityName: identity.name
363+
enablePublicAccess: enablePublicAccess
364+
scriptStorageAccountName: scriptStorageAccount.name
365+
scriptSubnetId: scriptSubnetId
355366
}
356367
}
357368

369+
// TODO: Move to separate CM exports module
358370
module exportContainer 'hub-storage.bicep' = {
359-
name: 'exportContainer'
371+
name: 'Microsoft.CostManagement.Exports_ExportContainer'
360372
params: {
361-
storageAccountName: storageAccount.name
362373
container: 'msexports'
374+
375+
// Hub context
376+
storageAccountName: storageAccount.name
377+
enablePublicAccess: enablePublicAccess
363378
}
364379
}
365380

381+
// TODO: Move to core module
366382
module ingestionContainer 'hub-storage.bicep' = {
367-
name: 'ingestionContainer'
383+
name: 'Microsoft.FinOpsHubs.Core_IngestionContainer'
368384
params: {
369-
storageAccountName: storageAccount.name
370385
container: 'ingestion'
386+
387+
// Hub context
388+
storageAccountName: storageAccount.name
389+
enablePublicAccess: enablePublicAccess
371390
}
372391
}
373392

@@ -463,10 +482,6 @@ resource uploadSettings 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
463482
name: 'containerName'
464483
value: 'config'
465484
}
466-
{
467-
name: 'schemaFiles'
468-
value: string(schemaFiles)
469-
}
470485
]
471486
scriptContent: loadTextContent('./scripts/Copy-FileToAzureBlob.ps1')
472487
})

0 commit comments

Comments
 (0)