Skip to content

feat(hubs): add Azure Resource Manager ingestion - #2277

Draft
Brett Wilson (MSBrett) wants to merge 9 commits into
devfrom
features/finops-hubs-arm-app
Draft

feat(hubs): add Azure Resource Manager ingestion#2277
Brett Wilson (MSBrett) wants to merge 9 commits into
devfrom
features/finops-hubs-arm-app

Conversation

@MSBrett

Copy link
Copy Markdown
Contributor

🛠️ Description

This PR added a reusable Azure Resource Manager ingestion path for GET-only APIs and connected its output to the existing FinOps hub ingestion flow.

Managed Azure Resource Manager ingestion

  • Added a shared deployment module for manifest-driven Azure Resource Manager queries.
  • Wired the module into hub deployment, ingestion-query generation, Azure Resource Graph, and the portal deployment experience.
  • Added explicit schemas and transforms so Azure Data Factory and Azure Data Explorer receive stable column types.

Quota and recommendation data

  • Added quota ingestion for App Service, capacity reservations, Azure AI services, compute, Premium SSD v2 disks, SQL, and Storage.
  • Added one-year and three-year Azure savings plan recommendation ingestion.
  • Added normalized Quota() functions and query-catalog entries for current usage, headroom, source-specific quota views, and savings plan recommendation breakdowns.

Documentation and tests

  • Documented the normalized quota schema, supported source types, latest-state query pattern, and inventory caveats.
  • Added regression coverage for manifests, schemas, source filtering, generated identifiers, KQL operators, and deployment wiring.
  • Ignored local .copilot-tracking/ workflow artifacts.

This work provides shared ingestion groundwork for MACC consumption. MACC Lots and MACC Events ingestion are not included in this PR. Quota and capacity support extends beyond the current iteration described in #1721.

Refs #1721

Related to #2120 and #2129

📷 Screenshots

Not applicable.

📋 Checklist

🔬 How did you test this change?

  • 🤏 Lint tests
  • 🤞 PS -WhatIf / az validate
  • 👍 Manually deployed + verified
  • 💪 Unit tests
  • 🙌 Integration tests

The full unit suite passed with 2,453 tests and no failures. The full lint suite passed with 3,439 tests and no failures. App Service ingestion completed through Data Factory and Azure Data Explorer, and all seven quota catalog projections returned the expected schema and key behavior.

📦 Deploy to test?

  • Hubs + ADX (managed)
  • Hubs + Fabric (manual) — URI:
  • Hubs (manual)
  • Hubs (no data)
  • Workbooks
  • Alerts

🙋‍♀️ Do any of the following that apply?

  • 🚨 This is a breaking change.
  • 🤏 The change is less than 20 lines of code.

📑 Did you update docs/changelog.md?

  • ✅ Updated changelog (required for dev PRs)
  • ➡️ Will add log in a future PR (feature branch PRs only)
  • ❎ Log not needed (small/internal change)

A changelog entry is pending before this draft is ready for review.

📖 Did you update documentation?

  • ✅ Public docs in docs (required for dev)
  • ✅ Public docs in docs-mslearn (required for dev)
  • ✅ Internal dev docs in docs-wiki (required for dev)
  • ✅ Internal dev docs in src (required for dev)
  • ➡️ Will add docs in a future PR (feature branch PRs only)
  • ❎ Docs not needed (small/internal change)

Public documentation remains pending before this draft is ready for review.

Brett Wilson (MSBrett) and others added 2 commits August 23, 2026 17:26
Add generic ARM query ingestion with quota and Savings Plan definitions, normalized KQL surfaces, catalog queries, documentation, and regression coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

Hubs + ADX (managed) deployment failed. View logs.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds reusable Azure Resource Manager ingestion to FinOps hubs, supporting savings-plan recommendations and quota/capacity datasets.

Changes:

  • Adds manifest-driven ARM GET ingestion and deployment wiring.
  • Adds normalized quota and savings-plan schemas and KQL queries.
  • Extends portal configuration, documentation, and regression tests.

Reviewed changes

Copilot reviewed 45 out of 46 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/templates/finops-hub/test/main.test.bicep Adds feature deployment combinations.
.../Recommendations/schemas/recommendations_1.1.json Defines savings-plan translation.
.../Recommendations/queries/Recommendations-Microsoft-SavingsPlan-P3Y.json Adds three-year recommendations.
.../Recommendations/queries/Recommendations-Microsoft-SavingsPlan-P1Y.json Adds one-year recommendations.
.../Recommendations/app.bicep Registers new recommendation assets.
.../Quota/schemas/quota_1.0-usage.json Defines usage translation.
.../Quota/schemas/quota_1.0-sql.json Defines SQL quota translation.
.../Quota/schemas/quota_1.0-disk.json Defines disk translation.
.../Quota/schemas/quota_1.0-capacity-reservation.json Defines capacity translation.
.../Quota/queries/Quota-Microsoft-StorageUsage.json Adds Storage usage ingestion.
.../Quota/queries/Quota-Microsoft-SqlSubscriptionUsage.json Adds SQL usage ingestion.
.../Quota/queries/Quota-Microsoft-PremiumSSDv2Disk.json Adds disk inventory ingestion.
.../Quota/queries/Quota-Microsoft-ComputeUsage.json Adds compute usage ingestion.
.../Quota/queries/Quota-Microsoft-CognitiveServicesUsage.json Adds AI quota ingestion.
.../Quota/queries/Quota-Microsoft-CapacityReservation.json Adds capacity inventory ingestion.
.../Quota/queries/Quota-Microsoft-AppServiceUsage.json Adds App Service usage ingestion.
.../Quota/app.bicep Deploys quota queries and schemas.
.../IngestionQueries/app.bicep Integrates ARM staging and finalization.
.../AzureResourceManager/README.md Documents the ARM engine.
.../AzureResourceManager/metadata.bicep Defines ARM app metadata.
.../AzureResourceManager/app.bicep Implements ARM request pipelines.
.../AzureResourceGraph/app.bicep Extends the shared engine contract.
.../Analytics/scripts/IngestionSetup_v1_2.kql Normalizes savings-plan recommendations.
.../Analytics/scripts/IngestionSetup_v1_0.kql Adds quota transforms and tables.
.../Analytics/scripts/IngestionSetup_RawTables.kql Adds the quota raw table.
.../Analytics/scripts/HubSetup_v1_0.kql Adds the versioned quota function.
.../Analytics/scripts/HubSetup_Latest.kql Adds the latest quota alias.
src/templates/finops-hub/modules/hub.bicep Wires ARM and quota apps.
.../fx/hub-deploymentScript.bicep Forces deployment-script refreshes.
src/templates/finops-hub/main.bicep Exposes quota configuration.
src/templates/finops-hub/createUiDefinition.json Adds portal controls and guidance.
src/scripts/Build-HubIngestionQueries.ps1 Generates manifests for both apps.
src/queries/INDEX.md Catalogs the new queries.
src/queries/finops-hub-database-guide.md Documents the quota contract.
src/queries/catalog/savings-plan-recommendation-breakdown.kql Summarizes savings-plan recommendations.
src/queries/catalog/quota-storage-usage.kql Queries Storage quota state.
src/queries/catalog/quota-sql-subscription-usage.kql Queries SQL quota state.
src/queries/catalog/quota-premium-ssd-v2-disks.kql Queries Premium SSD v2 inventory.
src/queries/catalog/quota-headroom.kql Calculates quota headroom.
src/queries/catalog/quota-current-usage.kql Queries current quota state.
src/queries/catalog/quota-compute-usage.kql Queries compute quota state.
src/queries/catalog/quota-cognitive-services-usage.kql Queries AI quota state.
src/queries/catalog/quota-capacity-reservations.kql Queries capacity reservations.
src/queries/catalog/quota-app-service-usage.kql Queries App Service quota state.
src/powershell/Tests/Unit/HubsIngestionQueries.Tests.ps1 Extends ingestion regression coverage.
.gitignore Ignores Copilot tracking artifacts.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/queries/catalog/savings-plan-recommendation-breakdown.kql Outdated
Brett Wilson (MSBrett) and others added 6 commits August 24, 2026 08:27
Adds an eighth view to the FinOps hub dashboard canvas extension, porting
the AI and emerging workloads page from the Azure Data Explorer dashboard
into the extension's own query and rendering pipeline.

Query layer (kusto.mjs):
- getAi() supplies 16 named queries across estate composition, token and
  model economics, workload detail, allocation, and rate posture.
- Month-over-month comparisons anchor to the last closed month. The newest
  month in a window is normally still ingesting, so comparing it against a
  full month reported a spend collapse that was not real.
- Token direction uses word-boundary regexes rather than substring matching,
  so meters such as "Cd Wr Std" classify correctly instead of falling into
  an Other bucket, and a future "Throughput" meter cannot be read as output.
- Owner tags fold case-insensitively while retaining the highest-cost
  casing for display, so one owner cannot appear as several rows.
- Follows repo KQL guidance: no bare joins, and contains reserved for
  genuinely fused substrings.

Rendering (public/):
- Six KPI cards, two new chart primitives, and fifteen panels.
- Formatters gain fmtRate, fmtQty, fmtShare, moneyColumn and rateColumn.
  Column helpers hold one precision per column, because a reader compares
  cells down a column rather than against each value's own magnitude, and
  mark sub-unit values rather than rounding real spend to zero.
- Axis ceilings round up to readable steps and never clip the series.
- Every table is wrapped so it scrolls within its panel. An unwrapped table
  previously forced horizontal scrolling on the whole page at narrow widths.
- The active tab is scrolled into view, including on deep links.
- Panels are paired by content height so no card renders mostly empty.

Also fixes two latent defects reachable from existing views: table swatches
were not displayed, and threshold colours were defined only under .kpi so
they had no effect inside tables.

Verified against a live hub across 1.2M rows, with no horizontal overflow
at 1600, 1280, 950, 768 and 480 CSS pixels. Test suite covers the new
behaviour and passes 34/34.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Quota answered only the demand side of capacity: how much of my limit have
I used. It could not answer the supply side: can a deployment land in this
region and zone at all. Two Azure Resource Manager quota queries close that
gap.

- Quota-Microsoft-AvailabilityZoneMapping records each subscription's
  logical-to-physical availability zone mapping, so zonal capacity is
  comparable across subscriptions.
- Quota-Microsoft-ComputeResourceSku records VM SKU family, zone, and the
  region and zone restrictions that block placement regardless of quota.

Both queries get a schema, a raw table, an ingestion transform, a Hub
function, and a catalog query for rehydration.

Fix rehydration filters that returned the wrong rows. Each catalog query
now filters on x_SourceType, ResourceType, and ResourceName together
instead of x_SourceType alone. Premium SSD v2 and disk quota come from the
Compute provider but describe storage, so they belong to the Storage view,
not the Compute view. Compute usage now keeps only core and VM family
metrics.

Raise the ingestion query schedule from daily to every four hours. Quota
and restriction state changes faster than a day.

Publish a manifest only when the query's own parquet file is present.
Checking folder existence alone published manifests for queries that wrote
nothing, because a sibling query had already created the folder.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A repository-scoped Copilot canvas that reads a FinOps hub cluster
directly, so a capacity manager can inspect quota and capacity without
building a report first. It carries seven capacity views: App Service,
Azure AI, Compute, Azure SQL, Storage, capacity reservations, and Premium
SSD v2.

The Compute view adds an estate-wide VM family by region heatmap. Supply
and demand are separate channels, because they are separate questions:

- Supply is a bar on the left edge of the cell. Red means a deployment
  cannot land here, whether the whole region is restricted or every zone
  is. Amber means some zones are restricted. Green means open.
- Demand is the colour of the percentage itself, measured against a
  selectable high-water mark that defaults to 70 percent.

Colour never carries the alarm alone. Each cell also states its condition
in words, so the heatmap reads without colour vision.

At estate scale the matrix is a lookup grid, not a picture. It keeps
frozen row and column headers, always-visible scrollbars, a family search,
region chips, and four condition lenses.

Add npm run test-dashboard and run it in the development workflow.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
graphify-out/ is generated locally and must not enter the repository.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Brings in the AI and emerging workloads view. Both branches had first
committed the dashboard independently while it was untracked, so git saw
every dashboard file as an add/add conflict with no common ancestor.

Resolved as a real three-way merge against checkpoint 3370dbac5, the last
shared state of the untracked working copy. Six of the eight files split
cleanly: the capacity work and the AI work touched disjoint regions. Two
needed a decision:

- extension.mjs: both sides rewrote the canvas description. Kept the new
  view and kept "capacity views" rather than "capacity evidence views".
- The test file: both sides appended to the same tail. Rebuilt as the
  union, 31 capacity tests plus 12 AI tests, with no name collisions.

The merged AI code reintroduced the word "evidence", which this dashboard
no longer uses. Renamed the "Evidence" table column to "Detail", reworded
the rate-posture panel, and reworded four comments, so the vocabulary test
passes on the union rather than by suspending the rule.

Verified live against the hub: nine tabs, the AI view rendering all 15
panels with data and no horizontal overflow, and the Compute heatmap still
reporting 135 region-restricted and 73 all-zones-restricted cells with red
supply bars. 43 of 43 tests pass and the design detector is clean.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- add filter-aware family and subscription detail tabs
- page subscription results with search and focus restoration
- align Kusto filters and preserve unknown headroom states
- add contract and accessibility regression coverage

🗺️ - Generated by Copilot
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread .github/workflows/dev.yml Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 60 out of 63 changed files in this pull request and generated 8 comments.

Suppressed comments (3)

src/templates/finops-hub/modules/Microsoft.FinOpsHubs/AzureResourceManager/app.bicep:1005

  • Native AbsoluteUrl pagination follows the response's nextLink without checking its authority. A compromised or malformed ARM response can redirect the REST linked service to another host while it is authenticated with the managed identity, which violates the linked requirement to keep continuation requests on the current Resource Manager authority. Validate every continuation URL before issuing it; native unvalidated paging is not safe for this engine.
    src/templates/finops-hub/modules/Microsoft.FinOpsHubs/AzureResourceManager/app.bicep:1030
  • The request URL is built directly from manifest-controlled queryScope and query values and sent through the authenticated ARM linked service. There is no runtime rejection of absolute/scheme-relative URLs, fragments, backslashes, malformed resource IDs, or unsupported scopes, so a modified query manifest can escape the intended ARM-relative request contract. Add validation before this dataset is invoked and attach authentication only after the value is proven ARM-relative.
    src/templates/finops-hub/modules/Microsoft.FinOpsHubs/AzureResourceManager/app.bicep:400
  • This prefix check accepts every Microsoft.Billing resource ID, including unsupported nested resources such as invoice sections. The Savings Plan queries are only compatible with billing accounts and billing profiles, so one unsupported configured scope can make the parallel query pipeline fail. Validate the exact resource-ID shape and dispatch only scopes whose derived resource type is declared compatible by the query.

Comment on lines +215 to 216
batchCount: app.hub.options.privateRouting ? 4 : 30
isSequential: false
Comment on lines +1682 to +1686
| extend x_SourceVersion = coalesce(x_SourceVersion, case(
x_SourceType =~ 'SavingsPlan', '2026-06-01',
ProviderName == 'Microsoft', '2023-05-01',
''
))
Comment thread .github/workflows/dev.yml
Comment on lines +14 to +15
- '.github/extensions/ftk-local-dashboard/**'
- 'package.json'
Comment thread src/queries/INDEX.md
> **Note:** Refer to the [FinOps hub database documentation](./finops-hub-database-guide.md) for table and column definitions.

This catalog contains 37 scenario-specific FinOps Hub KQL queries used by the FinOps Toolkit agents and the Azure SRE Agent recipe.
This catalog contains 47 scenario-specific FinOps hub KQL queries used by the FinOps Toolkit agents and the Azure SRE Agent recipe.
Comment on lines +643 to +649
| `AppServiceUsage` | `1.0-usage` | App Service regional quota usage | Synthetic `ResourceId` |
| `CapacityReservation` | `1.0-capacity-reservation` | Capacity reservation group inventory | Native ARM `ResourceId` |
| `CognitiveServicesUsage` | `1.0-usage` | Azure AI services regional quota usage | Synthetic `ResourceId` |
| `ComputeUsage` | `1.0-usage` | Compute regional quota usage | Synthetic `ResourceId` |
| `PremiumSSDv2Disk` | `1.0-disk` | Premium SSD v2 disk inventory | Native ARM `ResourceId` |
| `SqlSubscriptionUsage` | `1.0-sql` | Azure SQL regional quota usage | Native ARM `ResourceId` |
| `StorageUsage` | `1.0-usage` | Storage regional quota usage | Synthetic `ResourceId` |
Comment on lines 918 to +919
"options": {
"text": "The Data Factory managed identity requires Reader role on the management groups or subscriptions you want to scan. After deployment, grant the hub's managed identity Reader access to the desired scopes."
"text": "The Data Factory managed identity requires the Reader role on the management groups or subscriptions to scan. After deployment, grant the hub's managed identity Reader access to each scope."
Comment on lines +32 to +33
- Added a repository-scoped FinOps hub dashboard canvas with separate capacity views for App Service, Azure AI, Compute, Azure SQL, Storage, capacity reservations, and Premium SSD v2. The Compute view adds an estate-wide VM family by region heatmap that reads supply restrictions and quota utilization as separate signals ([#2277](https://github.com/microsoft/finops-toolkit/pull/2277)).
- Added an AI and emerging workloads view to the FinOps hub dashboard canvas, covering estate composition, token and model economics, workload detail, allocation coverage, and commitment and rate posture. Month-over-month comparisons anchor to the last closed month so a partially ingested month cannot report a false decline ([#2277](https://github.com/microsoft/finops-toolkit/pull/2277)).
Comment on lines +2837 to +2838
const kustoBundleUrl = `https://cdn.jsdelivr.net/npm/@kusto/monaco-kusto@${KUSTO_MONACO_VERSION}/+esm`;
const kustoBundleSrc = await fetch(kustoBundleUrl).then((r) => r.text());
@github-actions

Copy link
Copy Markdown
Contributor

Hubs + ADX (managed) deployed to pr-2277-adx.

- validate ARM scopes and continuations using one stored response per page
- filter configured scopes and propagate source metadata
- preserve quota and recommendation account identities
- enforce deployment order and least-privilege workflow access

🔒 - Generated by Copilot
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

Hubs + ADX (managed) deployed to pr-2277-adx.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs: Review 👀 PR that is ready to be reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants