Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fbfe49a
feat(hubs): add optional invoice download app
carreiracloudbr-source Aug 20, 2026
4143594
docs: expand agent instructions with verified setup and test commands
carreiracloudbr-source Aug 20, 2026
4e72cce
docs: add multicloud FOCUS ingestion plan and design
carreiracloudbr-source Aug 20, 2026
beb09c2
feat(hubs): add FOCUS 1.2 schema for Amazon Web Services exports
carreiracloudbr-source Aug 20, 2026
8b45596
docs: document the AWS Data Exports source topology
carreiracloudbr-source Aug 20, 2026
f7e40d9
fix(hubs): poll invoice download URL in a dedicated child pipeline
carreiracloudbr-source Aug 21, 2026
5147603
docs: translate multicloud FOCUS design docs to English
carreiracloudbr-source Aug 21, 2026
86102ac
fix(hubs): poll invoice download URL in a dedicated child pipeline
carreiracloudbr-source Aug 21, 2026
da72c53
fix(hubs): use string expression for invoice download linked service URL
carreiracloudbr-source Aug 23, 2026
f266109
fix(hubs): correct invoice download pipeline nesting and billing role…
carreiracloudbr-source Aug 24, 2026
533d54c
Merge branch 'arthursilvany/invoice-download' into dev
carreiracloudbr-source Aug 24, 2026
10caa90
Merge branch 'arthursilvany/multicloud-focus' into dev
carreiracloudbr-source Aug 24, 2026
0342723
feat(hubs): add AWS FOCUS collection connector
carreiracloudbr-source Aug 24, 2026
fd44c12
fix(hubs): correct AWS connector settings that fail at runtime
carreiracloudbr-source Aug 24, 2026
685ee0a
Merge branch 'arthursilvany/multicloud-focus' into dev
carreiracloudbr-source Aug 24, 2026
88a1dee
feat: make AWS and Google FOCUS ingestion optional in FinOps hub setup
carreiracloudbr-source Aug 25, 2026
b641286
feat: add Add-FinOpsHubResourceGraphReader cmdlet to grant Reader rol…
carreiracloudbr-source Aug 26, 2026
9b8ade9
Add AI billing + Cost Optimization ADX dashboard view to FinOps hub d…
carreiracloudbr-source Aug 26, 2026
1b00050
Merge remote-tracking branch 'upstream/dev' into arthursilvany/ai-bil…
carreiracloudbr-source Aug 27, 2026
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
102 changes: 82 additions & 20 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,27 @@

This file provides guidance to AI Agents when working with code in this repository.

> [!IMPORTANT]
> `AGENTS.md` is the single source of truth for agent guidance. `CLAUDE.md` and `.github/copilot-instructions.md` are git symlinks (mode `120000`) pointing here. Never replace those symlinks with real files — edit this file instead.

## Repository Overview

The FinOps Toolkit is an open-source collection of tools for adopting and implementing FinOps capabilities in the Microsoft Cloud. It contains templates, PowerShell modules, workbooks, optimization engines, and supporting documentation organized in a modular architecture.

## Common Commands

### First-time setup

`src/scripts/Init-Repo.ps1` installs the required tooling (Az PowerShell, Bicep CLI) and optional tooling (VS Code, Bicep module, NPM, Pester):

```powershell
./src/scripts/Init-Repo -All # required + optional tooling
./src/scripts/Init-Repo -Pester # required + Pester only
./src/scripts/Init-Repo -All -WhatIf # preview
```

**Pester 6.0.0 or later is required.** The suite uses `-AllowNullOrEmptyForEach`, which Pester 5 rejects during discovery — the affected file is silently skipped. `Test-PowerShell.ps1` resolves the module version explicitly and fails with an actionable message if only an older Pester is present. CI pins the same floor (`.github/workflows/dev.yml`).

### Building and Development

```bash
Expand All @@ -19,11 +34,19 @@ pwsh -Command ./src/scripts/Build-Toolkit
# Build FinOps hubs
pwsh -Command ./src/scripts/Build-Toolkit finops-hub

# Build a single workbook
pwsh -Command ./src/scripts/Build-Toolkit "<workbook-name>-workbook"

# Build specific components
npm run build-ps # PowerShell module only
pwsh -Command ./src/scripts/Build-Bicep # Bicep templates
pwsh -Command ./src/scripts/Build-Workbook # Azure Monitor workbooks
pwsh -Command ./src/scripts/Build-OpenData # Open data files
npm run build-ps # PowerShell module (Invoke-Task Build.PsModule)
pwsh -Command ./src/scripts/Build-PowerShell # PowerShell module (wraps the Invoke-Build task)
pwsh -Command ./src/scripts/Build-Bicep ../bicep-registry/<module> # single Bicep Registry module
pwsh -Command ./src/scripts/Build-Workbook # Azure Monitor workbooks
pwsh -Command ./src/scripts/Build-OpenData # Open data files (check in generated files manually)
pwsh -Command ./src/scripts/Invoke-Task -Task <TaskName> # Invoke-Build tasks (e.g. Build.PsModule)

# Load the locally built module
pwsh -Command 'Remove-Module FinOpsToolkit -EA SilentlyContinue; Import-Module -FullyQualifiedName ./src/powershell/FinOpsToolkit.psm1'

# Deploy for testing
npm run deploy-test
Expand All @@ -38,22 +61,43 @@ pwsh -Command ./src/scripts/Package-Toolkit -Build

### Testing

`src/scripts/Test-PowerShell.ps1` is the entry point for all Pester runs. It runs unit tests by default; naming any test type runs only those types.

```bash
# Run PowerShell unit tests
npm run pester
pwsh -Command ./src/scripts/Test-PowerShell
# or
pwsh -Command Invoke-Pester -Output Detailed -Path ./src/powershell/Tests/Unit/*
npm run pester

# Run integration tests
# Run lint / integration / everything
pwsh -Command ./src/scripts/Test-PowerShell -Lint
pwsh -Command ./src/scripts/Test-PowerShell -Integration
pwsh -Command ./src/scripts/Test-PowerShell -AllTests

# Run specific test categories
# Run specific test categories (combine freely)
# Cost, Data, Docs, Exports, FOCUS, Hubs, Toolkit, Workbooks, Actions, Private
pwsh -Command ./src/scripts/Test-PowerShell -Hubs -Exports

# Lint PowerShell code
pwsh -Command ./src/scripts/Test-PowerShell -Lint
# Re-run only the tests that failed in the previous run
pwsh -Command ./src/scripts/Test-PowerShell -RunFailed
```

Run a **single test file** or a **single test case** with Pester directly (import Pester 6 explicitly so a side-by-side Pester 3/5 install cannot win):

```bash
# One file
pwsh -Command 'Import-Module Pester -MinimumVersion 6.0.0; Invoke-Pester -Output Detailed -Path ./src/powershell/Tests/Unit/Get-FinOpsRegion.Tests.ps1'

# One Describe/Context/It by name
pwsh -Command 'Import-Module Pester -MinimumVersion 6.0.0; Invoke-Pester -Output Detailed -Path ./src/powershell/Tests/Unit/Get-FinOpsRegion.Tests.ps1 -FullNameFilter "*returns all regions*"'
```

After a `Test-PowerShell` run, inspect these globals to debug:

- `$global:ftk_TestPowerShell_Results` — full result object from the last run
- `$global:ftk_TestPowerShell_Summary` — failed tests only
- `$global:ftk_TestPowerShell_FailedTests` — the Pester config used by `-RunFailed`

### Bicep Development

```bash
Expand All @@ -68,23 +112,27 @@ az deployment group what-if --resource-group myRG --template-file template.bicep

### High-Level Structure

- **`/src/templates/`** - ARM/Bicep infrastructure templates with modular namespace organization
- **`/src/templates/`** - ARM/Bicep infrastructure templates (`finops-hub`, `finops-alerts`, `finops-workbooks`, `agent-plugin`, `finops-hub-copilot*`)
- **`/src/powershell/`** - PowerShell module with public/private functions and comprehensive tests
- **`/src/queries/`** - KQL query catalog (`catalog/`, `INDEX.md`, `KPI.md`, `finops-hub-database-guide.md`)
- **`/src/bicep-registry/`** - Bicep Registry modules (multi-scope build)
- **`/src/optimization-engine/`** - Azure Optimization Engine for cost recommendations
- **`/src/workbooks/`** - Azure Monitor workbooks for governance and optimization
- **`/src/open-data/`** - Reference data (pricing, regions, services) with utilities
- **`/src/scripts/`** - Build automation and development tools
- **`/src/power-bi/`** - Power BI reports (built manually, not by the build scripts)
- **`/src/scripts/`** - Build automation and development tools (see `src/scripts/README.md`)
- **`/plugins/`**, **`/.claude-plugin/`** - Agent plugin packaging (`plugins/microsoft-finops-toolkit`)
- **`/docs/`** - Jekyll documentation website
- **`/docs-mslearn/`** - Microsoft Learn documentation website
- **`/docs-wiki/`** - GitHub wiki documentation
- **`/docs-mslearn/`** - Microsoft Learn documentation website (includes `toolkit/changelog.md`)
- **`/docs-wiki/`** - GitHub wiki documentation (authoritative dev process + coding guidelines)

### Current Architectural Reorganization

The FinOps hubs solution is actively migrating to a namespace-based modular structure:
The FinOps hubs solution is actively migrating to a namespace-based modular structure under `src/templates/finops-hub/modules/`:

- **`Microsoft.FinOpsHubs/`** - Core FinOps Hub infrastructure modules
- **`Microsoft.FinOpsHubs/`** - Core FinOps Hub infrastructure modules, split into `Core`, `Analytics`, `IngestionQueries`, `Recommendations`, `AzureResourceGraph`, and `RemoteHub`
- **`Microsoft.CostManagement/`** - Cost management exports and schemas
- **`fx/`** - Shared foundation components (hub-types, scripts, utilities)
- **`fx/`** - Shared foundation components: `hub-types.bicep`, `hub-app.bicep`, `hub-storage.bicep`, `hub-database.bicep`, `hub-identity.bicep`, `hub-vault.bicep`, `hub-deploymentScript.bicep`, `hub-eventTrigger.bicep`, plus `scripts/` and version/tag files

### Template Architecture

Expand All @@ -106,8 +154,11 @@ Key patterns:

- **`Public/`** - User-facing cmdlets (Get-_, Set-_, New-\*, etc.)
- **`Private/`** - Internal utilities and helpers
- **`Tests/Unit/`** - Pester unit tests with mocking
- **`en-US/`** - Localized strings (validated by `Tests/Unit/LocalizedData.Tests.ps1`)
- **`Tests/Lint/`** - Repo-wide standards tests (`Lint.Tests.ps1`, `KqlJoinKinds.Tests.ps1`, `MsLearnDocs.Tests.ps1`)
- **`Tests/Unit/`** - Pester unit tests with mocking. Note these cover far more than cmdlets — hub Bicep/KQL guards (`HubsKqlOperators`, `HubsIngestionQueries`, `HubsPrivateNetworking`, `HubsContractedCostGuard`, `HubsAdfTriggerTimeZones`), GitHub Actions parity, and docs links all live here
- **`Tests/Integration/`** - End-to-end Azure integration tests
- **`Tests/Initialize-Tests.ps1`** - Reimports `FinOpsToolkit.psm1` and dot-sources `src/scripts/Monitor.ps1`; test files reference it rather than importing the module themselves
- **Module manifest** defines exports and dependencies

### Data Flow and Integration
Expand Down Expand Up @@ -152,8 +203,8 @@ The PowerShell-based build system:

### Version Management

- Central version in `package.json` (currently 12.0.0)
- Synchronized across all components via build scripts
- Central version in `package.json` (source of truth; prerelease format is `<major>.0.0-dev.0`)
- Synchronized across all components via `src/scripts/Update-Version.ps1`; read the current value with `src/scripts/Get-Version`
- Individual `ftkver.txt` files distributed to modules
- Git tags correspond to release versions

Expand Down Expand Up @@ -206,6 +257,17 @@ This repository supports production infrastructure managing significant revenue.
- Documentation uses Jekyll conventions
- Build artifacts are generated, not checked in

### Changelog

User-facing changes must be added to `docs-mslearn/toolkit/changelog.md`. Full rules are in the "Changelog" section of `docs-wiki/Coding-guidelines.md`. Key points:

- All changes for the upcoming release go in **one** version section — never create a duplicate `## v{version}` heading
- Group by tool with an H3 heading linking to the tool's doc page plus its version (e.g. `### [FinOps hubs](...) v14`), matching the tool order used in previous releases
- Category order: Added, Changed, Fixed, Deprecated, Removed. Omit empty categories
- One past-tense sentence per entry, ending with a period, linking the issue as `([#{number}]({url}))` when one exists
- Write for users, not developers — no implementation details, no filler entries
- Prefix breaking changes with `**Breaking:**` and list them first in their category

### Coding Standards

- Always follow the content and coding standards defined in `docs-wiki/Coding-guidelines.md`
Expand Down
6 changes: 5 additions & 1 deletion docs-mslearn/toolkit/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: FinOps toolkit changelog
description: Review the latest features and enhancements in the FinOps toolkit, including updates to FinOps hubs, Power BI reports, and more.
author: MSBrett
ms.author: brettwil
ms.date: 08/19/2026
ms.date: 08/26/2026
ms.topic: reference
ms.service: finops
ms.subservice: finops-toolkit
Expand All @@ -28,7 +28,11 @@ The following section lists features and enhancements that are currently in deve
### [FinOps hubs](hubs/finops-hubs-overview.md)

- **Added**
- Added optional invoice download, which saves your Microsoft invoice files in the hub data lake every month, organized by billing period, billing profile, and purchase order number. Enable it in the **Invoices** step of the deployment wizard.
- Added VNet and private network modes, including opt-in NAT Gateway support for private mode; NAT Gateway incurs additional cost when enabled ([#2163](https://github.com/microsoft/finops-toolkit/pull/2163)).
- Added optional AWS FOCUS ingestion, which copies FOCUS 1.2 cost and usage exports from an Amazon S3 bucket into the hub data lake every day so AWS costs are normalized alongside Microsoft Cloud costs. Enable it in the **Multicloud** step of the deployment wizard.
- Added a **Usage optimization** page to the Azure Data Explorer dashboard that surfaces Azure Advisor cost recommendations and the hub's built-in recommendations, with estimated savings summarized by impact, recommendation type, resource type, and subscription, plus detail tables and a collection freshness view.
- Added the `Add-FinOpsHubResourceGraphReader` PowerShell command to grant the Data Factory managed identity Reader access to subscriptions or management groups used by Resource Graph recommendations.
- **Changed**
- Replaced redundant `tolower()` comparisons in hub KQL with case-insensitive operators (`has`, `=~`, `!~`) so the engine can use the term index instead of scanning every row ([#2213](https://github.com/microsoft/finops-toolkit/issues/2213)).
- Replaced whole-term `contains` matches with `has` across hub KQL and the query catalog (resource ID paths, licensing phrases, SKU description terms) and added a per-row operator-equivalence regression harness with unit test coverage ([#2220](https://github.com/microsoft/finops-toolkit/pull/2220)).
Expand Down
14 changes: 14 additions & 0 deletions docs-mslearn/toolkit/hubs/configure-recommendations.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ Before you begin, you must have:
- [Deployed a FinOps hub instance](finops-hubs-overview.md#create-a-new-hub).
- Assigned the **Reader** role to the Data Factory managed identity on the management groups or subscriptions you want to query. This permission must be configured separately from the FinOps hub deployment.

If the role has not been assigned, use the `Add-FinOpsHubResourceGraphReader` cmdlet after connecting
to Azure. The command is safe to rerun because it checks for an existing assignment first:

```powershell
Connect-AzAccount
Add-FinOpsHubResourceGraphReader `
-Scope '<subscription-id-or-management-group-resource-id>' `
-HubName '<finops-hub-name>'
```

Use a subscription ID to grant access to one subscription, or a management group resource ID to grant
access to all subscriptions below that management group. The command must be run by an account that
can create role assignments at the requested scope.

<br>

## How recommendations are processed
Expand Down
Loading