Skip to content

Commit a69ccdb

Browse files
RolandKrummenacherRoland Krummenacherflanakingithub-actions[bot]
authored
Improve FinOps hubs deployment UI: ADX/Fabric/Remote Hub exclusivity … (#1991)
Co-authored-by: Roland Krummenacher <roland.krummenacher@alescent.com> Co-authored-by: Michael Flanakin <flanakin@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 9afa6b3 commit a69ccdb

2 files changed

Lines changed: 153 additions & 80 deletions

File tree

docs-mslearn/toolkit/changelog.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ The following section lists features and enhancements that are currently in deve
3131
- Cost Management export modules for subscriptions and resource groups.
3232
-->
3333

34+
<br>
35+
36+
## v14
37+
38+
### [FinOps hubs](hubs/finops-hubs-overview.md) v14
39+
40+
- **Changed**
41+
- Improved deployment UI to consolidate hub mode selection into a single radio button group with four mutually exclusive options: None (storage only for Power BI reports), Azure Data Explorer, Microsoft Fabric, or Remote Hub ([#1929](https://github.com/microsoft/finops-toolkit/issues/1929)).
42+
- Remote Hub configuration (storage URI, storage key, and purge protection) is now displayed in the Basics tab when Remote Hub mode is selected, making the mutual exclusivity clear.
43+
- Data Explorer SKU and retention settings are now only visible when Azure Data Explorer mode is selected.
44+
3445
<br><a name="latest"></a>
3546

3647
## v14

src/templates/finops-hub/createUiDefinition.json

Lines changed: 142 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -48,57 +48,86 @@
4848
"visible": true
4949
},
5050
{
51-
"name": "dataExplorer",
51+
"name": "analyticsBackend",
5252
"type": "Microsoft.Common.Section",
53-
"label": "Azure Data Explorer (optional)",
53+
"label": "Analytics backend",
5454
"elements": [
5555
{
56-
"name": "dataExplorerIntro",
56+
"name": "analyticsIntro",
5757
"type": "Microsoft.Common.TextBlock",
5858
"visible": true,
5959
"options": {
60-
"text": "Azure Data Explorer is a fast, scalable service for advanced, big data analytics. Data Explorer is optional but recommended when monitoring more than $100K in spend. FinOps hubs with Data Explorer starts at $120/mo for a single node cluster plus $10/mo per million in monitored spend (<0.02% of annual spend)."
60+
"text": "Select how this hub will process and store data. Azure Data Explorer or Microsoft Fabric are recommended for medium to large accounts. Select Remote hub to push data to a central FinOps hub in another tenant. Only select storage-only for small accounts with simple requirements."
61+
}
62+
},
63+
{
64+
"name": "analyticsEngine",
65+
"type": "Microsoft.Common.OptionsGroup",
66+
"label": "Hub mode",
67+
"defaultValue": "Azure Data Explorer",
68+
"toolTip": "Select the analytics backend for this hub. Azure Data Explorer and Microsoft Fabric provide analytics. None uses storage only for Power BI reports. Remote Hub forwards data to a central hub for cross-tenant scenarios.",
69+
"constraints": {
70+
"allowedValues": [
71+
{
72+
"label": "Azure Data Explorer",
73+
"value": "adx"
74+
},
75+
{
76+
"label": "Microsoft Fabric",
77+
"value": "fabric"
78+
},
79+
{
80+
"label": "None (storage only)",
81+
"value": "none"
82+
},
83+
{
84+
"label": "Remote hub",
85+
"value": "remote"
86+
}
87+
],
88+
"required": true
89+
},
90+
"visible": true
91+
},
92+
{
93+
"name": "dataExplorerIntro",
94+
"type": "Microsoft.Common.TextBlock",
95+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'adx')]",
96+
"options": {
97+
"text": "Azure Data Explorer is a fast, scalable service for advanced, big data analytics. Recommended when monitoring more than $100K in spend. Starts at $120/mo for a single node cluster plus $10/mo per million in monitored spend (<0.02% of annual spend)."
6198
}
6299
},
63100
{
64101
"name": "dataExplorerName",
65102
"type": "Microsoft.Common.TextBox",
66103
"label": "Cluster name",
67-
"toolTip": "Name of the Azure Data Explorer cluster, if desired. If not specified, Data Explorer will not be deployed.",
104+
"toolTip": "Name of the Azure Data Explorer cluster.",
68105
"constraints": {
69-
"required": false,
106+
"required": "[equals(basics('analyticsBackend').analyticsEngine, 'adx')]",
70107
"regex": "^[a-zA-Z0-9][a-zA-Z0-9\\-]{0,20}[a-z0-9]$",
71108
"validationMessage": "Name must be between 4 and 22 characters long and can contain only lowercase letters, numbers, and hyphens. The first and last characters in the name must be alphanumeric."
72109
},
73-
"visible": true
74-
}
75-
],
76-
"visible": true
77-
},
78-
{
79-
"name": "fabric",
80-
"type": "Microsoft.Common.Section",
81-
"label": "Microsoft Fabric (optional)",
82-
"elements": [
110+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'adx')]"
111+
},
83112
{
84113
"name": "fabricIntro",
85114
"type": "Microsoft.Common.TextBlock",
86-
"visible": true,
115+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'fabric')]",
87116
"options": {
88-
"text": "Microsoft Fabric is a unified data platform for data ingestion, processing, and analytics. FinOps hubs with Fabric starts at $300/mo for an F2 SKU plus $10/mo per million in monitored spend."
117+
"text": "Microsoft Fabric is a unified data platform for data ingestion, processing, and analytics. Starts at $300/mo for an F2 SKU plus $10/mo per million in monitored spend."
89118
}
90119
},
91120
{
92121
"name": "fabricQueryUri",
93122
"type": "Microsoft.Common.TextBox",
94123
"label": "Fabric eventhouse query URI",
95-
"toolTip": "Query URI for the Microsoft Fabric eventhouse, if applicable.",
124+
"toolTip": "Query URI for the Microsoft Fabric eventhouse.",
96125
"constraints": {
97-
"required": false,
126+
"required": "[equals(basics('analyticsBackend').analyticsEngine, 'fabric')]",
98127
"regex": "^https://.*",
99128
"validationMessage": "Query URI must be a full Microsoft Fabric eventhouse URL."
100129
},
101-
"visible": true
130+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'fabric')]"
102131
},
103132
{
104133
"name": "fabricCapacity",
@@ -123,7 +152,59 @@
123152
{ "label": "F2048", "value": "2048" }
124153
]
125154
},
126-
"visible": true
155+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'fabric')]"
156+
},
157+
{
158+
"name": "remoteHubIntro",
159+
"type": "Microsoft.Common.TextBlock",
160+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'remote')]",
161+
"options": {
162+
"text": "Configure this hub as a satellite that sends data to a remote FinOps hub in another tenant or subscription. This enables cross-tenant cost management scenarios where a central hub collects cost data from multiple tenants."
163+
}
164+
},
165+
{
166+
"name": "remoteHubStorageUri",
167+
"type": "Microsoft.Common.TextBox",
168+
"label": "Remote hub storage URI",
169+
"toolTip": "Data Lake storage endpoint from the remote hub storage account. Copy from the storage account Settings > Endpoints > Data Lake storage. Example: https://myremotehub.dfs.core.windows.net/",
170+
"constraints": {
171+
"required": "[equals(basics('analyticsBackend').analyticsEngine, 'remote')]",
172+
"regex": "^https://.*\\.dfs\\.core\\.windows\\.net/?$",
173+
"validationMessage": "Must be a valid Data Lake storage endpoint URL in the format: https://storageaccount.dfs.core.windows.net/"
174+
},
175+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'remote')]"
176+
},
177+
{
178+
"name": "remoteHubStorageKey",
179+
"type": "Microsoft.Common.PasswordBox",
180+
"label": {
181+
"password": "Remote hub storage key"
182+
},
183+
"toolTip": "Storage account access key for the remote hub. Copy from the remote hub storage account Security + networking > Access keys > key1/2 > Key.",
184+
"constraints": {
185+
"required": "[equals(basics('analyticsBackend').analyticsEngine, 'remote')]",
186+
"regex": "^[A-Za-z0-9+/]{86}==$",
187+
"validationMessage": "Must be a valid storage account access key (base64 encoded, ending with ==)"
188+
},
189+
"options": {
190+
"hideConfirmation": true
191+
},
192+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'remote')]"
193+
},
194+
{
195+
"name": "remoteHubSecurityNote",
196+
"type": "Microsoft.Common.TextBlock",
197+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'remote')]",
198+
"options": {
199+
"text": "The storage key will be securely stored in Azure Key Vault and is never logged or exposed in deployment outputs."
200+
}
201+
},
202+
{
203+
"name": "enablePurgeProtection",
204+
"type": "Microsoft.Common.CheckBox",
205+
"label": "Enable Key Vault purge protection",
206+
"toolTip": "Enables purge protection for the Key Vault used to store the remote hub storage key. Purge protection prevents permanent deletion of the Key Vault for 90 days after deletion. Note: If the key is lost, you can regenerate it from the remote hub's storage account.",
207+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'remote')]"
127208
}
128209
],
129210
"visible": true
@@ -190,14 +271,14 @@
190271
{
191272
"name": "dataExplorer",
192273
"type": "Microsoft.Common.Section",
193-
"label": "Data Explorer (optional)",
274+
"label": "Data Explorer",
194275
"elements": [
195276
{
196277
"name": "dataExplorerPricingIntro",
197278
"type": "Microsoft.Common.TextBlock",
198279
"visible": true,
199280
"options": {
200-
"text": "Azure Data Explorer is optional. If a cluster name is not specified on the Basics tab, this setting is ignored."
281+
"text": "Select the Azure Data Explorer SKU for your analytics workload."
201282
}
202283
},
203284
{
@@ -478,7 +559,7 @@
478559
"visible": true
479560
}
480561
],
481-
"visible": true
562+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'adx')]"
482563
},
483564
{
484565
"name": "spacer",
@@ -505,11 +586,35 @@
505586
{
506587
"name": "retentionIntro",
507588
"type": "Microsoft.Common.TextBlock",
508-
"visible": true,
589+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'adx')]",
509590
"options": {
510591
"text": "Data retention settings indicate how long to keep data available for reporting or additional processing. Retained data contributes to monitored spend and the total storage costs for the hub instance."
511592
}
512593
},
594+
{
595+
"name": "storageOnlyRetentionInfo",
596+
"type": "Microsoft.Common.TextBlock",
597+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'none')]",
598+
"options": {
599+
"text": "Storage-only mode keeps all ingested data in Azure Data Lake Storage. Data is retained indefinitely until manually deleted. Use Power BI to connect directly to storage for reporting."
600+
}
601+
},
602+
{
603+
"name": "fabricRetentionInfo",
604+
"type": "Microsoft.Common.TextBlock",
605+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'fabric')]",
606+
"options": {
607+
"text": "Data retention for Microsoft Fabric is managed within your Fabric eventhouse. Configure retention policies directly in Fabric."
608+
}
609+
},
610+
{
611+
"name": "remoteHubRetentionInfo",
612+
"type": "Microsoft.Common.TextBlock",
613+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'remote')]",
614+
"options": {
615+
"text": "Remote Hub satellites forward data to the central hub. Data retention is managed by the central hub, not the satellite."
616+
}
617+
},
513618
{
514619
"name": "storage",
515620
"type": "Microsoft.Common.Section",
@@ -521,7 +626,7 @@
521626
"type": "Microsoft.Common.TextBlock",
522627
"visible": true,
523628
"options": {
524-
"text": "Storage retention is not implemented yet. You can set the retention period but files will not be automatically removed."
629+
"text": "Configure how long to retain data in Azure Data Lake Storage. Storage retention is not fully implemented yet - files will not be automatically removed."
525630
}
526631
},
527632
{
@@ -555,15 +660,15 @@
555660
{
556661
"name": "dataExplorer",
557662
"type": "Microsoft.Common.Section",
558-
"label": "Data Explorer (optional)",
559-
"visible": true,
663+
"label": "Data Explorer",
664+
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'adx')]",
560665
"elements": [
561666
{
562667
"name": "dataExplorerIntro",
563668
"type": "Microsoft.Common.TextBlock",
564669
"visible": true,
565670
"options": {
566-
"text": "Azure Data Explorer is optional. If a cluster name is not specified on the Basics tab, this setting is ignored."
671+
"text": "Configure data retention settings for Azure Data Explorer."
567672
}
568673
},
569674
{
@@ -692,52 +797,8 @@
692797
"message": "Address prefix must be a valid IPv4 address in the format 'XXX.XXX.XXX.XXX/YY' where YY is between 8 and 26 (minimum /26 subnet size required)."
693798
}
694799
]
695-
}
696-
}
697-
],
698-
"visible": true
699-
},
700-
{
701-
"name": "remoteHub",
702-
"type": "Microsoft.Common.Section",
703-
"label": "Remote hub configuration",
704-
"elements": [
705-
{
706-
"name": "remoteHubIntro",
707-
"type": "Microsoft.Common.TextBlock",
708-
"visible": true,
709-
"options": {
710-
"text": "Configure this hub to send data to a remote FinOps hub in another tenant or subscription. This enables cross-tenant cost management scenarios where a central tenant collects cost data from multiple tenants. Leave these fields empty if this is not a remote hub setup."
711-
}
712-
},
713-
{
714-
"name": "remoteHubStorageUri",
715-
"type": "Microsoft.Common.TextBox",
716-
"label": "Remote hub storage URI",
717-
"toolTip": "Data Lake storage endpoint from the remote hub storage account. Copy from the storage account Settings > Endpoints > Data Lake storage. Example: https://myremotehub.dfs.core.windows.net/",
718-
"constraints": {
719-
"required": false,
720-
"regex": "^$|^https://.*\\.dfs\\.core\\.windows\\.net/?$",
721-
"validationMessage": "Must be a valid Data Lake storage endpoint URL in the format: https://storageaccount.dfs.core.windows.net/"
722-
},
723-
"visible": true
724-
},
725-
{
726-
"name": "remoteHubStorageKey",
727-
"type": "Microsoft.Common.PasswordBox",
728-
"label": {
729-
"password": "Remote hub storage key"
730800
},
731-
"toolTip": "Storage account access key for the remote hub. Copy from the remote hub storage account Security + networking > Access keys > key1/2 > Key.",
732-
"constraints": {
733-
"required": false,
734-
"regex": "^$|^[A-Za-z0-9+/]{86}==$",
735-
"validationMessage": "Must be a valid storage account access key (base64 encoded, ending with ==)"
736-
},
737-
"options": {
738-
"hideConfirmation": true
739-
},
740-
"visible": true
801+
"visible": "[equals(steps('advanced').networking.enablePublicAccess, false)]"
741802
}
742803
],
743804
"visible": true
@@ -772,9 +833,9 @@
772833
"hubName": "[basics('hubName')]",
773834
"location": "[location()]",
774835
"storageSku": "[steps('pricing').storage.storageSku]",
775-
"dataExplorerName": "[basics('dataExplorer').dataExplorerName]",
776-
"fabricQueryUri": "[basics('fabric').fabricQueryUri]",
777-
"fabricCapacity": "[basics('fabric').fabricCapacity]",
836+
"dataExplorerName": "[if(equals(basics('analyticsBackend').analyticsEngine, 'adx'), basics('analyticsBackend').dataExplorerName, '')]",
837+
"fabricQueryUri": "[if(equals(basics('analyticsBackend').analyticsEngine, 'fabric'), basics('analyticsBackend').fabricQueryUri, '')]",
838+
"fabricCapacityUnits": "[if(equals(basics('analyticsBackend').analyticsEngine, 'fabric'), basics('analyticsBackend').fabricCapacity, '')]",
778839
"enableInfrastructureEncryption": "[steps('advanced').storage.enableInfrastructureEncryption]",
779840
"enableManagedExports": "[steps('advanced').managedExports.enableManagedExports]",
780841
"enablePublicAccess": "[steps('advanced').networking.enablePublicAccess]",
@@ -784,8 +845,9 @@
784845
"ingestionRetentionInMonths": "[steps('retention').storage.ingestionMonths]",
785846
"dataExplorerRawRetentionInDays": "[steps('retention').dataExplorer.rawDays]",
786847
"dataExplorerFinalRetentionInMonths": "[steps('retention').dataExplorer.finalMonths]",
787-
"remoteHubStorageUri": "[steps('advanced').remoteHub.remoteHubStorageUri]",
788-
"remoteHubStorageKey": "[steps('advanced').remoteHub.remoteHubStorageKey]",
848+
"remoteHubStorageUri": "[if(equals(basics('analyticsBackend').analyticsEngine, 'remote'), basics('analyticsBackend').remoteHubStorageUri, '')]",
849+
"remoteHubStorageKey": "[if(equals(basics('analyticsBackend').analyticsEngine, 'remote'), basics('analyticsBackend').remoteHubStorageKey, '')]",
850+
"enablePurgeProtection": "[if(equals(basics('analyticsBackend').analyticsEngine, 'remote'), coalesce(basics('analyticsBackend').enablePurgeProtection, false), false)]",
789851
"tagsByResource": "[steps('tags').tagsByResource]"
790852
}
791853
}

0 commit comments

Comments
 (0)