diff --git a/go.mod b/go.mod index 228176c1..6368fd18 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/costmanagement/armcostmanagement v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 diff --git a/go.sum b/go.sum index 269868b2..25bf2cb3 100644 --- a/go.sum +++ b/go.sum @@ -477,6 +477,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armconta github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v1.0.0/go.mod h1:tOckqrJq0CXsb/AlUYCdD7DqpULgOaexk+5rz+isAjM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 h1:figxyQZXzZQIcP3njhC68bYUiTw45J8/SsHaLW8Ax0M= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0/go.mod h1:TmlMW4W5OvXOmOyKNnor8nlMMiO1ctIyzmHme/VHsrA= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/costmanagement/armcostmanagement v1.1.0 h1:1MRED2aeLx/BPHC23XRtr8Mk6zcc70HNRYPQ73R0gHw= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/costmanagement/armcostmanagement v1.1.0/go.mod h1:Am1cUioOk0HdZIsjpXJkQ4RIeQbwYsW6LkNIc5z/5XY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 h1:8iR6OLffWWorFdzL2JFCab5xpD8VKEE2DUBBl+HNTDY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0/go.mod h1:copqlcjMWc/wgQ1N2fzsJFQxDdqKGg1EQt8T5wJMOGE= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E= diff --git a/scrapers/azure/cost.go b/scrapers/azure/cost.go new file mode 100644 index 00000000..12463d8a --- /dev/null +++ b/scrapers/azure/cost.go @@ -0,0 +1,87 @@ +package azure + +import ( + "context" + "fmt" + + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/costmanagement/armcostmanagement" + "github.com/flanksource/commons/logger" + v1 "github.com/flanksource/config-db/api/v1" + "github.com/flanksource/duty" +) + +type CostScraper struct { + cred *azidentity.ClientSecretCredential +} + +func (t CostScraper) CanScrape(config v1.ConfigScraper) bool { + // At least one of the azure configuration must have the subscription ID set + for _, c := range config.Azure { + if c.SubscriptionID != "" { + return true + } + } + + return false +} + +func (t CostScraper) Scrape(ctx *v1.ScrapeContext, config v1.ConfigScraper) v1.ScrapeResults { + var results v1.ScrapeResults + for _, config := range config.Azure { + clientId, err := duty.GetEnvValueFromCache(ctx.Kubernetes, config.ClientID, ctx.Namespace) + if err != nil { + results.Errorf(err, "failed to get client id") + continue + } + + clientSecret, err := duty.GetEnvValueFromCache(ctx.Kubernetes, config.ClientSecret, ctx.Namespace) + if err != nil { + results.Errorf(err, "failed to get client secret") + continue + } + + cred, err := azidentity.NewClientSecretCredential(config.TenantID, clientId, clientSecret, nil) + if err != nil { + results.Errorf(err, "failed to get credentials for azure") + continue + } + t.cred = cred + + if err := t.GetCost(ctx.Context, config.SubscriptionID); err != nil { + results.Errorf(err, "failed to get cost") + continue + } + } + + return results +} + +func (t *CostScraper) GetCost(ctx context.Context, subscriptionID string) error { + costClient, err := armcostmanagement.NewQueryClient(t.cred, nil) + if err != nil { + return fmt.Errorf("failed to create cost client: %w", err) + } + + var ( + scope = fmt.Sprintf("/subscriptions/%s", subscriptionID) + timeFrame = armcostmanagement.TimeframeTypeTheLastBillingMonth + queryType = armcostmanagement.ExportTypeActualCost + granularity = armcostmanagement.GranularityTypeDaily + ) + queryDef := armcostmanagement.QueryDefinition{ + Dataset: &armcostmanagement.QueryDataset{ + Granularity: &granularity, + }, + Timeframe: &timeFrame, + Type: &queryType, + } + usageRes, err := costClient.Usage(ctx, scope, queryDef, nil) + if err != nil { + return fmt.Errorf("failed to get usage: %w", err) + } + + logger.Debugf("usage: %v", usageRes) + + return nil +} diff --git a/scrapers/common.go b/scrapers/common.go index d9e05cc3..690d7a1b 100644 --- a/scrapers/common.go +++ b/scrapers/common.go @@ -18,7 +18,8 @@ import ( // All is the scrappers registry var All = []v1.Scraper{ - azure.Scraper{}, + // azure.Scraper{}, + azure.CostScraper{}, aws.Scraper{}, aws.CostScraper{}, file.FileScraper{},