# Azure FinOps Deep Dive

Build and validate an intermediate-to-advanced Azure FinOps lab covering tag inheritance, REST-based cost queries, scheduled cost exports, budgets, and an automated VM shutdown guardrail.

The bundle is designed for PowerShell 7 orchestration with Azure CLI and `az rest`. Runtime identifiers, callback URLs, and webhook secrets are written only to `work/azure-finops-deep-dive` beside the bundle and are removed by cleanup. The public evidence never contains exported cost rows or actual cost amounts.

## What the lab creates

- Resource group `rg-finops-deepdive-we` in West Europe.
- Secure StorageV2 account with the private `cost-exports` container.
- Two private Ubuntu 22.04 `Standard_B1s` VMs with no public IP addresses.
- Azure Automation account with a PowerShell 7.4 runtime and managed identity.
- A least-privilege custom role scoped only to the workshop resource group.
- Logic App, Monitor Action Group, and a monthly resource-group budget.
- A daily month-to-date cost export plus an immediate run for the previous completed month.

## Prerequisites

- PowerShell 7 or later.
- Azure CLI signed in to the target subscription.
- OpenSSH `ssh-keygen` on `PATH`.
- Owner or User Access Administrator permission for the temporary custom role and assignment.
- Cost Management access for queries, exports, budgets, and tag inheritance.
- A subscription with enough quota for two `Standard_B1s` VMs in `westeurope`.

The tested Visual Studio Enterprise/WebDirect agreement accepts `Usage` where newer agreements accept the literal `ActualCost`. Microsoft documents those definitions as equivalent for agreements that do not support the newer literal. The script tries `ActualCost` first and applies the compatible fallback only when Azure returns that agreement-specific error.

## Run the workshop

```powershell
$subscriptionId = az account show --query id --output tsv

./Start-AzureFinOpsWorkshop.ps1 `
  -SubscriptionId $subscriptionId `
  -Location westeurope `
  -BudgetAmount 10 `
  -Stage Preflight

./Start-AzureFinOpsWorkshop.ps1 `
  -SubscriptionId $subscriptionId `
  -Stage All
```

`Preflight` is read-only. Run it independently first when teaching the lab, then use `All` or run the stages individually:

`Preflight`, `Foundation`, `Tagging`, `Inheritance`, `CostQuery`, `Export`, `Guardrail`, `Budget`, `TestGuardrail`, `Validate`.

Stages are safely rerunnable for the active private session. The scripts refuse to adopt an unexpected pre-existing workshop resource group or export.

## Collect evidence

```powershell
./Get-AzureFinOpsEvidence.ps1 -SubscriptionId $subscriptionId
```

This produces `evidence/workshop-evidence.json` and `evidence/sanitization-report.txt`. It publishes only counts, statuses, configured thresholds, tags, and power states. Blob contents and actual financial values are never read.

## Deliberate guardrail test

The test sends schema-accurate synthetic Azure Budget notifications through the live Logic App callback:

- At 79%, both VMs remain running.
- At 80%, only `vm-finops-stop` is deallocated.
- `vm-finops-keep` remains running because its opt-in tag is `finops-action=keep`.

Azure evaluates real budgets daily, and Cost Management data is delayed. The synthetic events validate the automation chain deterministically; the lab does not claim that a newly created budget fired naturally.

## Cleanup

```powershell
./Remove-AzureFinOpsWorkshop.ps1 `
  -SubscriptionId $subscriptionId `
  -ConfirmCleanup
```

Cleanup targets only the named workshop budget, export, custom role assignment, custom role definition, and resource group. It restores tag inheritance to the original state recorded during preflight. If the setting did not exist originally, it is deleted. The script then verifies the original subscription-budget baseline and removes the private runtime directory.

## Files

- `Start-AzureFinOpsWorkshop.ps1` — staged orchestrator.
- `Get-AzureFinOpsEvidence.ps1` — sanitized proof collector.
- `Remove-AzureFinOpsWorkshop.ps1` — bounded cleanup and restoration.
- `FinOpsWorkshop.Common.ps1` — Azure CLI, REST, state, retry, and sanitizer helpers.
- `runbooks/Stop-FinOpsTaggedVm.ps1` — guarded VM deallocation runbook.
- `logic-app/workflow-definition.json` — request validation and private webhook forwarding.
- `workshop-article.md` — publishable workshop article.
- `publishing-brief.md` — Wix metadata and visual placement brief.
- `screenshots/` and `evidence/` — sanitized proof artifacts.

## Security notes

- VMs have no public IPs.
- The Storage account requires HTTPS, TLS 1.2, and disallows public blob access.
- The Automation identity can only read VMs, read instance view, deallocate VMs, and read the resource group at the workshop resource-group scope.
- The runbook validates the Azure Budget schema, expected budget name, threshold boundary, and `finops-action=stop` opt-in tag.
- Webhook and Logic App callback URLs never enter the public bundle.
- The public screenshots are cropped; raw captures remain private until cleanup deletes them.

## References

- [Cost Management automation overview](https://learn.microsoft.com/en-us/azure/cost-management-billing/automate/automation-overview)
- [Group and allocate costs using tag inheritance](https://learn.microsoft.com/en-us/azure/cost-management-billing/costs/enable-tag-inheritance)
- [Cost Management Query API 2025-03-01](https://learn.microsoft.com/en-us/rest/api/cost-management/query/usage?view=rest-cost-management-2025-03-01)
- [Cost Management Exports API 2025-03-01](https://learn.microsoft.com/en-us/rest/api/cost-management/exports/get?view=rest-cost-management-2025-03-01)
- [Create and manage exported data](https://learn.microsoft.com/en-us/azure/cost-management-billing/costs/tutorial-export-acm-data)
- [Manage Azure Automation runtime environments](https://learn.microsoft.com/en-us/azure/automation/manage-runtime-environment)
- [Azure Automation webhooks](https://learn.microsoft.com/en-us/azure/automation/automation-webhooks)

