Skip to content

Commit ddafaf4

Browse files
authored
Merge pull request #130 from Cyb3r-Jak3/add-r2-metrics
feat: Add R2 metrics
2 parents 0521e44 + a46ee5e commit ddafaf4

File tree

4 files changed

+209
-81
lines changed

4 files changed

+209
-81
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Required authentication scopes:
3232

3333
To authenticate this way, only set `CF_API_TOKEN` (omit `CF_API_EMAIL` and `CF_API_KEY`)
3434

35+
[Shortcut to create the API token](https://dash.cloudflare.com/profile/api-tokens?permissionGroupKeys=%5B%7B%22key%22%3A%22account_analytics%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22account_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22analytics%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22firewall_services%22%2C%22type%22%3A%22read%22%7D%5D&name=Cloudflare+Exporter&accountId=*&zoneId=all)
36+
3537
### User email + API key
3638
To authenticate with user email + API key, use the `Global API Key` from the Cloudflare dashboard.
3739
Beware that this key authenticates with write access to every Cloudflare resource.
@@ -104,6 +106,9 @@ Note: `ZONE_<name>` configuration is not supported as flag.
104106
# HELP cloudflare_zone_pool_requests_total Requests per pool
105107
# HELP cloudflare_logpush_failed_jobs_account_count Number of failed logpush jobs on the account level
106108
# HELP cloudflare_logpush_failed_jobs_zone_count Number of failed logpush jobs on the zone level
109+
# HELP cloudflare_r2_operation_count Number of operations performed by R2
110+
# HELP cloudflare_r2_storage_bytes Storage used by R2
111+
# HELP cloudflare_r2_storage_total_bytes Total storage used by R2
107112
```
108113

109114
## Helm chart repository

cloudflare.go

Lines changed: 104 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import (
55
"strings"
66
"time"
77

8-
cloudflare "github.com/cloudflare/cloudflare-go"
8+
"github.com/cloudflare/cloudflare-go"
99
"github.com/machinebox/graphql"
1010
log "github.com/sirupsen/logrus"
1111
"github.com/spf13/viper"
1212
)
1313

14-
var (
14+
const (
1515
cfGraphQLEndpoint = "https://api.cloudflare.com/client/v4/graphql/"
1616
)
1717

@@ -45,6 +45,35 @@ type cloudflareResponseLogpushAccount struct {
4545
} `json:"viewer"`
4646
}
4747

48+
type r2AccountResp struct {
49+
R2StorageGroups []struct {
50+
Dimensions struct {
51+
BucketName string `json:"bucketName"`
52+
} `json:"dimensions"`
53+
Max struct {
54+
MetadataSize uint64 `json:"metadataSize"`
55+
PayloadSize uint64 `json:"payloadSize"`
56+
ObjectCount uint64 `json:"objectCount"`
57+
} `json:"max"`
58+
} `json:"r2StorageAdaptiveGroups"`
59+
60+
R2StorageOperations []struct {
61+
Dimensions struct {
62+
Action string `json:"actionType"`
63+
BucketName string `json:"bucketName"`
64+
} `json:"dimensions"`
65+
Sum struct {
66+
Requests uint64 `json:"requests"`
67+
} `json:"sum"`
68+
} `json:"r2OperationsAdaptiveGroups"`
69+
}
70+
71+
type cloudflareResponseR2Account struct {
72+
Viewer struct {
73+
Accounts []r2AccountResp `json:"accounts"`
74+
}
75+
}
76+
4877
type cloudflareResponseLogpushZone struct {
4978
Viewer struct {
5079
Zones []logpushResponse `json:"zones"`
@@ -251,60 +280,38 @@ type lbResp struct {
251280
}
252281

253282
func fetchZones() []cloudflare.Zone {
254-
var api *cloudflare.API
255-
var err error
256-
if len(viper.GetString("cf_api_token")) > 0 {
257-
api, err = cloudflare.NewWithAPIToken(viper.GetString("cf_api_token"))
258-
} else {
259-
api, err = cloudflare.New(viper.GetString("cf_api_key"), viper.GetString("cf_api_email"))
260-
}
261-
if err != nil {
262-
log.Fatal("failed to make client to fetch zones: ", err)
263-
}
264-
265283
ctx := context.Background()
266-
z, err := api.ListZones(ctx)
284+
z, err := cloudflareAPI.ListZones(ctx)
267285
if err != nil {
268-
log.Fatal("failed to fetch zones: ", err)
286+
log.Fatalf("Error fetching zones: %s", err)
269287
}
270288

271289
return z
272290
}
273291

274292
func fetchFirewallRules(zoneID string) map[string]string {
275-
var api *cloudflare.API
276-
var err error
277-
if len(viper.GetString("cf_api_token")) > 0 {
278-
api, err = cloudflare.NewWithAPIToken(viper.GetString("cf_api_token"))
279-
} else {
280-
api, err = cloudflare.New(viper.GetString("cf_api_key"), viper.GetString("cf_api_email"))
281-
}
282-
if err != nil {
283-
log.Fatal("failed to make client to fetch firewall rules: ", err)
284-
}
285-
286293
ctx := context.Background()
287-
listOfRules, _, err := api.FirewallRules(ctx,
294+
listOfRules, _, err := cloudflareAPI.FirewallRules(ctx,
288295
cloudflare.ZoneIdentifier(zoneID),
289296
cloudflare.FirewallRuleListParams{})
290297
if err != nil {
291-
log.Fatal("failed to fetch firewall rules: ", err)
298+
log.Fatalf("Error fetching firewall rules: %s", err)
292299
}
293300
firewallRulesMap := make(map[string]string)
294301

295302
for _, rule := range listOfRules {
296303
firewallRulesMap[rule.ID] = rule.Description
297304
}
298305

299-
listOfRulesets, err := api.ListRulesets(ctx, cloudflare.ZoneIdentifier(zoneID), cloudflare.ListRulesetsParams{})
306+
listOfRulesets, err := cloudflareAPI.ListRulesets(ctx, cloudflare.ZoneIdentifier(zoneID), cloudflare.ListRulesetsParams{})
300307
if err != nil {
301-
log.Fatal("failed to fetch list of rulesets for firewall rules: ", err)
308+
log.Fatalf("Error listing rulesets: %s", err)
302309
}
303310
for _, rulesetDesc := range listOfRulesets {
304311
if rulesetDesc.Phase == "http_request_firewall_managed" {
305-
ruleset, err := api.GetRuleset(ctx, cloudflare.ZoneIdentifier(zoneID), rulesetDesc.ID)
312+
ruleset, err := cloudflareAPI.GetRuleset(ctx, cloudflare.ZoneIdentifier(zoneID), rulesetDesc.ID)
306313
if err != nil {
307-
log.Fatal("failed to fetch ruleset for firewall rules: ", err)
314+
log.Fatalf("Error fetching ruleset for firewall rules: %s", err)
308315
}
309316
for _, rule := range ruleset.Rules {
310317
firewallRulesMap[rule.ID] = rule.Description
@@ -314,7 +321,7 @@ func fetchFirewallRules(zoneID string) map[string]string {
314321
if rulesetDesc.Phase == "http_request_firewall_custom" {
315322
ruleset, err := api.GetRuleset(ctx, cloudflare.ZoneIdentifier(zoneID), rulesetDesc.ID)
316323
if err != nil {
317-
log.Fatal(err)
324+
log.Fatal("Error fetching custom firewall rulesets: %s" err)
318325
}
319326
for _, rule := range ruleset.Rules {
320327
firewallRulesMap[rule.ID] = rule.Description
@@ -326,21 +333,10 @@ func fetchFirewallRules(zoneID string) map[string]string {
326333
}
327334

328335
func fetchAccounts() []cloudflare.Account {
329-
var api *cloudflare.API
330-
var err error
331-
if len(viper.GetString("cf_api_token")) > 0 {
332-
api, err = cloudflare.NewWithAPIToken(viper.GetString("cf_api_token"))
333-
} else {
334-
api, err = cloudflare.New(viper.GetString("cf_api_key"), viper.GetString("cf_api_email"))
335-
}
336-
if err != nil {
337-
log.Fatal("failed to make client to fetch accounts: ", err)
338-
}
339-
340336
ctx := context.Background()
341-
a, _, err := api.Accounts(ctx, cloudflare.AccountsListParams{PaginationOptions: cloudflare.PaginationOptions{PerPage: 100}})
337+
a, _, err := cloudflareAPI.Accounts(ctx, cloudflare.AccountsListParams{PaginationOptions: cloudflare.PaginationOptions{PerPage: 100}})
342338
if err != nil {
343-
log.Fatal("failed to fetch accounts: ", err)
339+
log.Fatalf("Error fetching accounts: %s", err)
344340
}
345341

346342
return a
@@ -580,7 +576,7 @@ func fetchWorkerTotals(accountID string) (*cloudflareResponseAccts, error) {
580576
graphqlClient := graphql.NewClient(cfGraphQLEndpoint)
581577
var resp cloudflareResponseAccts
582578
if err := graphqlClient.Run(ctx, request, &resp); err != nil {
583-
log.Error("failed to fetch worker totals: ", err)
579+
log.Errorf("Error fetching worker totals: %s", err)
584580
return nil, err
585581
}
586582

@@ -657,7 +653,7 @@ func fetchLoadBalancerTotals(zoneIDs []string) (*cloudflareResponseLb, error) {
657653
graphqlClient := graphql.NewClient(cfGraphQLEndpoint)
658654
var resp cloudflareResponseLb
659655
if err := graphqlClient.Run(ctx, request, &resp); err != nil {
660-
log.Error("failed to fetch load balancer totals: ", err)
656+
log.Errorf("Error fetching load balancer totals: %s", err)
661657
return nil, err
662658
}
663659
return &resp, nil
@@ -709,7 +705,7 @@ func fetchLogpushAccount(accountID string) (*cloudflareResponseLogpushAccount, e
709705
graphqlClient := graphql.NewClient(cfGraphQLEndpoint)
710706
var resp cloudflareResponseLogpushAccount
711707
if err := graphqlClient.Run(ctx, request, &resp); err != nil {
712-
log.Error("failed to logpush account info: ", err)
708+
log.Errorf("Error fetching logpush account totals: %s", err)
713709
return nil, err
714710
}
715711
return &resp, nil
@@ -761,13 +757,71 @@ func fetchLogpushZone(zoneIDs []string) (*cloudflareResponseLogpushZone, error)
761757
graphqlClient := graphql.NewClient(cfGraphQLEndpoint)
762758
var resp cloudflareResponseLogpushZone
763759
if err := graphqlClient.Run(ctx, request, &resp); err != nil {
764-
log.Error("failed to fetch logpush zone: ", err)
760+
log.Errorf("Error fetching logpush zone totals: %s", err)
765761
return nil, err
766762
}
767763

768764
return &resp, nil
769765
}
770766

767+
func fetchR2Account(accountID string) (*cloudflareResponseR2Account, error) {
768+
now := time.Now().Add(-time.Duration(viper.GetInt("scrape_delay")) * time.Second).UTC()
769+
s := 60 * time.Second
770+
now = now.Truncate(s)
771+
772+
request := graphql.NewRequest(`query($accountID: String!, $limit: Int!, $date: String!) {
773+
viewer {
774+
accounts(filter: {accountTag : $accountID }) {
775+
r2StorageAdaptiveGroups(
776+
filter: {
777+
date: $date
778+
},
779+
limit: $limit
780+
) {
781+
dimensions {
782+
bucketName
783+
}
784+
max {
785+
metadataSize
786+
payloadSize
787+
objectCount
788+
}
789+
}
790+
r2OperationsAdaptiveGroups(filter: { date: $date }, limit: $limit) {
791+
dimensions {
792+
actionType
793+
bucketName
794+
}
795+
sum {
796+
requests
797+
}
798+
}
799+
}
800+
}
801+
}`)
802+
803+
if len(viper.GetString("cf_api_token")) > 0 {
804+
request.Header.Set("Authorization", "Bearer "+viper.GetString("cf_api_token"))
805+
} else {
806+
request.Header.Set("X-AUTH-EMAIL", viper.GetString("cf_api_email"))
807+
request.Header.Set("X-AUTH-KEY", viper.GetString("cf_api_key"))
808+
}
809+
810+
request.Var("accountID", accountID)
811+
request.Var("limit", 9999)
812+
request.Var("date", now.Format("2006-01-02"))
813+
814+
ctx := context.Background()
815+
graphqlClient := graphql.NewClient(cfGraphQLEndpoint)
816+
graphqlClient.Log = func(s string) { log.Debug(s) }
817+
var resp cloudflareResponseR2Account
818+
if err := graphqlClient.Run(ctx, request, &resp); err != nil {
819+
log.Errorf("Error fetching R2 account: %s", err)
820+
return nil, err
821+
}
822+
return &resp, nil
823+
}
824+
771825
func findZoneAccountName(zones []cloudflare.Zone, ID string) (string, string) {
772826
for _, z := range zones {
773827
if z.ID == ID {

0 commit comments

Comments
 (0)