Skip to content

Commit e01365a

Browse files
Merge pull request #1 from visiativ-agora/feat/budgetv1
Feat/budgetv1
2 parents 32960d7 + 6d9c37d commit e01365a

File tree

7 files changed

+285
-157
lines changed

7 files changed

+285
-157
lines changed

config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type (
1717
Iam CollectorBase `yaml:"iam"`
1818
Graph CollectorGraph `yaml:"graph"`
1919
Costs CollectorCosts `yaml:"costs"`
20+
Budgets CollectorBudgets `yaml:"budgets"`
2021
Reservation CollectorReservation `yaml:"reservation"`
2122
Portscan CollectorPortscan `yaml:"portscan"`
2223
} `yaml:"collectors"`

config/config_budget.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package config
2+
3+
type (
4+
CollectorBudgets struct {
5+
CollectorBase `yaml:",inline"`
6+
7+
Scopes []string `yaml:"scopes"`
8+
}
9+
)

default.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ collectors:
2323

2424
costs: {}
2525

26+
budgets: {}
27+
2628
reservation: {}
2729

2830
portscan:

example.yaml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ collectors:
5555
application: ""
5656
servicePrincipal: ""
5757

58-
# Azure cost metrics (cost queries, budgets)
58+
# Azure cost metrics (cost queries)
5959
# needs queries below
6060
costs:
6161
scrapeTime: 60m
@@ -104,6 +104,23 @@ collectors:
104104
# optional, additional static labels
105105
labels: {}
106106

107+
# Azure budget metrics
108+
budgets:
109+
scrapeTime: 1h
110+
111+
# optional, see https://learn.microsoft.com/en-us/rest/api/cost-management/query/usage?tabs=HTTP
112+
# will disable fetching by subscription and will enable fetching by scope
113+
#scopes: [...]
114+
# '/subscriptions/{subscriptionId}/' for subscription scope
115+
# '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}' for resourceGroup scope
116+
# '/providers/Microsoft.Billing/billingAccounts/{billingAccountId}' for Billing Account scope
117+
# '/providers/Microsoft.Billing/billingAccounts/{billingAccountId}/departments/{departmentId}' for Department scope
118+
# '/providers/Microsoft.Billing/billingAccounts/{billingAccountId}/enrollmentAccounts/{enrollmentAccountId}' for EnrollmentAccount scope
119+
# '/providers/Microsoft.Management/managementGroups/{managementGroupId} for Management Group scope
120+
# '/providers/Microsoft.Billing/billingAccounts/{billingAccountId}/billingProfiles/{billingProfileId}' for billingProfile scope
121+
# '/providers/Microsoft.Billing/billingAccounts/{billingAccountId}/billingProfiles/{billingProfileId}/invoiceSections/{invoiceSectionId}' for invoiceSection scope
122+
# '/providers/Microsoft.Billing/billingAccounts/{billingAccountId}/customers/{customerId}' specific for partners
123+
107124
reservation:
108125
scrapeTime: 1h
109126

main.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,21 @@ func initMetricCollector() {
258258
logger.With(zap.String("collector", collectorName)).Infof("collector disabled")
259259
}
260260

261+
collectorName = "budgets"
262+
if Config.Collectors.Budgets.IsEnabled() {
263+
c := collector.New(collectorName, &MetricsCollectorAzureRmBudgets{}, logger)
264+
c.SetScapeTime(*Config.Collectors.Budgets.ScrapeTime)
265+
c.SetCache(
266+
Opts.GetCachePath(collectorName+".json"),
267+
collector.BuildCacheTag(cacheTag, Config.Azure, Config.Collectors.Budgets),
268+
)
269+
if err := c.Start(); err != nil {
270+
logger.Fatal(err.Error())
271+
}
272+
} else {
273+
logger.With(zap.String("collector", collectorName)).Infof("collector disabled")
274+
}
275+
261276
collectorName = "defender"
262277
if Config.Collectors.Defender.IsEnabled() {
263278
c := collector.New(collectorName, &MetricsCollectorAzureRmDefender{}, logger)

metrics_azurerm_budgets.go

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
package main
2+
3+
import (
4+
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/consumption/armconsumption"
5+
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions"
6+
"github.com/prometheus/client_golang/prometheus"
7+
"github.com/webdevops/go-common/azuresdk/armclient"
8+
"github.com/webdevops/go-common/prometheus/collector"
9+
"github.com/webdevops/go-common/utils/to"
10+
"go.uber.org/zap"
11+
)
12+
13+
// Define MetricsCollectorAzureRmBudgets struct
14+
type MetricsCollectorAzureRmBudgets struct {
15+
collector.Processor
16+
17+
prometheus struct {
18+
consumptionBudgetInfo *prometheus.GaugeVec
19+
consumptionBudgetLimit *prometheus.GaugeVec
20+
consumptionBudgetCurrent *prometheus.GaugeVec
21+
consumptionBudgetForecast *prometheus.GaugeVec
22+
consumptionBudgetUsage *prometheus.GaugeVec
23+
}
24+
}
25+
26+
// Setup method to initialize Prometheus metrics
27+
func (m *MetricsCollectorAzureRmBudgets) Setup(collector *collector.Collector) {
28+
m.Processor.Setup(collector)
29+
30+
// ----------------------------------------------------
31+
// Budget
32+
m.prometheus.consumptionBudgetInfo = prometheus.NewGaugeVec(
33+
prometheus.GaugeOpts{
34+
Name: "azurerm_budgets_info",
35+
Help: "Azure ResourceManager consumption budget info",
36+
},
37+
[]string{
38+
"scope",
39+
"resourceID",
40+
"subscriptionID",
41+
"budgetName",
42+
"resourceGroup",
43+
"category",
44+
"timeGrain",
45+
},
46+
)
47+
m.Collector.RegisterMetricList("consumptionBudgetInfo", m.prometheus.consumptionBudgetInfo, true)
48+
49+
m.prometheus.consumptionBudgetLimit = prometheus.NewGaugeVec(
50+
prometheus.GaugeOpts{
51+
Name: "azurerm_budgets_limit",
52+
Help: "Azure ResourceManager consumption budget limit",
53+
},
54+
[]string{
55+
"scope",
56+
"resourceID",
57+
"subscriptionID",
58+
"resourceGroup",
59+
"budgetName",
60+
},
61+
)
62+
m.Collector.RegisterMetricList("consumptionBudgetLimit", m.prometheus.consumptionBudgetLimit, true)
63+
64+
m.prometheus.consumptionBudgetUsage = prometheus.NewGaugeVec(
65+
prometheus.GaugeOpts{
66+
Name: "azurerm_budgets_usage",
67+
Help: "Azure ResourceManager consumption budget usage percentage",
68+
},
69+
[]string{
70+
"scope",
71+
"resourceID",
72+
"subscriptionID",
73+
"resourceGroup",
74+
"budgetName",
75+
},
76+
)
77+
m.Collector.RegisterMetricList("consumptionBudgetUsage", m.prometheus.consumptionBudgetUsage, true)
78+
79+
m.prometheus.consumptionBudgetCurrent = prometheus.NewGaugeVec(
80+
prometheus.GaugeOpts{
81+
Name: "azurerm_budgets_current",
82+
Help: "Azure ResourceManager consumption budget current",
83+
},
84+
[]string{
85+
"scope",
86+
"resourceID",
87+
"subscriptionID",
88+
"resourceGroup",
89+
"budgetName",
90+
"unit",
91+
},
92+
)
93+
m.Collector.RegisterMetricList("consumptionBudgetCurrent", m.prometheus.consumptionBudgetCurrent, true)
94+
95+
m.prometheus.consumptionBudgetForecast = prometheus.NewGaugeVec(
96+
prometheus.GaugeOpts{
97+
Name: "azurerm_budgets_forecast",
98+
Help: "Azure ResourceManager consumption budget forecast",
99+
},
100+
[]string{
101+
"scope",
102+
"resourceID",
103+
"subscriptionID",
104+
"resourceGroup",
105+
"budgetName",
106+
"unit",
107+
},
108+
)
109+
m.Collector.RegisterMetricList("consumptionBudgetForecast", m.prometheus.consumptionBudgetForecast, true)
110+
}
111+
112+
func (m *MetricsCollectorAzureRmBudgets) Reset() {}
113+
114+
func (m *MetricsCollectorAzureRmBudgets) Collect(callback chan<- func()) {
115+
if Config.Collectors.Budgets.Scopes != nil && len(Config.Collectors.Budgets.Scopes) > 0 {
116+
for _, scope := range Config.Collectors.Budgets.Scopes {
117+
// Run the budget query for the current scope
118+
m.collectBudgetMetrics(logger, scope, callback)
119+
}
120+
} else {
121+
// using subscription iterator
122+
iterator := AzureSubscriptionsIterator
123+
124+
err := iterator.ForEach(m.Logger(), func(subscription *armsubscriptions.Subscription, logger *zap.SugaredLogger) {
125+
m.collectBudgetMetrics(
126+
logger,
127+
*subscription.ID,
128+
callback,
129+
)
130+
})
131+
if err != nil {
132+
m.Logger().Panic(err)
133+
}
134+
}
135+
}
136+
137+
func (m *MetricsCollectorAzureRmBudgets) collectBudgetMetrics(logger *zap.SugaredLogger, scope string, callback chan<- func()) {
138+
clientFactory, err := armconsumption.NewClientFactory("<subscription-id>", AzureClient.GetCred(), AzureClient.NewArmClientOptions())
139+
if err != nil {
140+
logger.Panic(err)
141+
}
142+
143+
infoMetric := m.Collector.GetMetricList("consumptionBudgetInfo")
144+
usageMetric := m.Collector.GetMetricList("consumptionBudgetUsage")
145+
limitMetric := m.Collector.GetMetricList("consumptionBudgetLimit")
146+
currentMetric := m.Collector.GetMetricList("consumptionBudgetCurrent")
147+
forecastMetric := m.Collector.GetMetricList("consumptionBudgetForecast")
148+
149+
pager := clientFactory.NewBudgetsClient().NewListPager(scope, nil)
150+
151+
for pager.More() {
152+
result, err := pager.NextPage(m.Context())
153+
if err != nil {
154+
logger.Panic(err)
155+
}
156+
157+
if result.Value == nil {
158+
continue
159+
}
160+
161+
for _, budget := range result.Value {
162+
resourceId := to.String(budget.ID)
163+
164+
azureResource, _ := armclient.ParseResourceId(resourceId)
165+
166+
infoMetric.AddInfo(prometheus.Labels{
167+
"scope": scope,
168+
"resourceID": stringToStringLower(resourceId),
169+
"subscriptionID": azureResource.Subscription,
170+
"resourceGroup": azureResource.ResourceGroup,
171+
"budgetName": to.String(budget.Name),
172+
"category": stringToStringLower(string(*budget.Properties.Category)),
173+
"timeGrain": string(*budget.Properties.TimeGrain),
174+
})
175+
176+
if budget.Properties.Amount != nil {
177+
limitMetric.Add(prometheus.Labels{
178+
"scope": scope,
179+
"resourceID": stringToStringLower(resourceId),
180+
"subscriptionID": azureResource.Subscription,
181+
"resourceGroup": azureResource.ResourceGroup,
182+
"budgetName": to.String(budget.Name),
183+
}, *budget.Properties.Amount)
184+
}
185+
186+
if budget.Properties.CurrentSpend != nil {
187+
currentMetric.Add(prometheus.Labels{
188+
"scope": scope,
189+
"resourceID": stringToStringLower(resourceId),
190+
"subscriptionID": azureResource.Subscription,
191+
"resourceGroup": azureResource.ResourceGroup,
192+
"budgetName": to.String(budget.Name),
193+
"unit": to.StringLower(budget.Properties.CurrentSpend.Unit),
194+
}, *budget.Properties.CurrentSpend.Amount)
195+
}
196+
197+
if budget.Properties.ForecastSpend != nil {
198+
forecastMetric.Add(prometheus.Labels{
199+
"scope": scope,
200+
"resourceID": stringToStringLower(resourceId),
201+
"subscriptionID": azureResource.Subscription,
202+
"resourceGroup": azureResource.ResourceGroup,
203+
"budgetName": to.String(budget.Name),
204+
"unit": to.StringLower(budget.Properties.ForecastSpend.Unit),
205+
}, *budget.Properties.ForecastSpend.Amount)
206+
}
207+
208+
if budget.Properties.Amount != nil && budget.Properties.CurrentSpend != nil {
209+
usageMetric.Add(prometheus.Labels{
210+
"scope": scope,
211+
"resourceID": stringToStringLower(resourceId),
212+
"subscriptionID": azureResource.Subscription,
213+
"resourceGroup": azureResource.ResourceGroup,
214+
"budgetName": to.String(budget.Name),
215+
}, *budget.Properties.CurrentSpend.Amount / *budget.Properties.Amount)
216+
}
217+
}
218+
}
219+
}

0 commit comments

Comments
 (0)