Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion azure/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ param budgetStartDate string = ''
@description('Email addresses for budget and alert notifications. Sourced from the environment by the param files — never a literal, this repository is public.')
param contactEmails array

var applicationTagKey = 'Application'

var defaultTags = {
Project: 'EPIC Analytics'
Application: 'eagle-analytics'
'${applicationTagKey}': 'eagle-analytics'
Environment: environmentName
ManagedBy: 'Bicep'
CostCenter: 'c4b0a8'
Expand Down Expand Up @@ -186,6 +188,8 @@ module costBudget './modules/cost-budget.bicep' = {
budgetAmount: budgetAmount
contactEmails: contactEmails
startDate: budgetStartDate
applicationTagKey: applicationTagKey
applicationTagValue: defaultTags[applicationTagKey]
}
}

Expand Down
18 changes: 18 additions & 0 deletions azure/modules/cost-budget.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ param budgetAmount int = 60
@description('Email addresses to receive budget threshold alerts. Passed in, never defaulted: an address in a public repository is a spam target.')
param contactEmails array

@description('Tag key and value every resource in this estate carries. main.bicep passes both from the same defaultTags entry, so the filter key and the tags cannot drift apart.')
@minLength(1)
param applicationTagKey string
@minLength(1)
param applicationTagValue string

// utcNow() is only legal as a parameter default in Bicep, which is the shape wanted here: evaluated
// once at deployment, never drifting on a redeploy of an unchanged template.
@description('First day of the budget period. Defaults to the first of the current month for a NEW budget; an existing budget REJECTS any startDate change, so param files pin the live value.')
Expand All @@ -36,6 +42,18 @@ resource costBudget 'Microsoft.Consumption/budgets@2021-10-01' = {
timePeriod: {
startDate: '${effectiveStartDate}T00:00:00Z'
}
// A budget with no filter measures the WHOLE resource group, and both groups are shared: test
// sits with eagle-demi (about 150 CAD/month on its own), prod with the eagle-public estate. That
// is what fired all three thresholds on 2026-09-06 without analytics spending anything near 60.
// Every billable resource here is stamped by main.bicep's defaultTags and its usage records
// carry the tag; `In` is the only operator a budget filter accepts.
filter: {
tags: {
name: applicationTagKey
operator: 'In'
values: [applicationTagValue]
}
}
notifications: {
Actual_80_Percent: {
enabled: true
Expand Down
4 changes: 4 additions & 0 deletions docs/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ step wrote.
Point the client at both: eagle-public and eagle-admin keep sending to penguin and also send to
`/analytics/events`. Nothing is switched off yet.

Since eao-nginx v2.7.32 (2026-09-06), the browser-facing prefix is `/api/usage/`; rproxy rewrites it
to `/analytics/` for the gateway, because ad blockers refuse paths containing "analytics". Config
`EAGLE_ANALYTICS_URL` is `/api/usage` on test.

## 3. Compare the counts

In penguin:
Expand Down