From 69bea160581b2f8ed0de5a3c7866451e65daeaed Mon Sep 17 00:00:00 2001 From: ParthaI Date: Wed, 24 Jan 2024 19:19:49 +0530 Subject: [PATCH 1/4] Add table azure_consumption_usage Closes #668 --- azure/plugin.go | 1 + azure/table_azure_consumption_usage.go | 334 +++++++++++++++++++++++++ docs/tables/azure_consumption_usage.md | 223 +++++++++++++++++ go.mod | 1 + go.sum | 2 + 5 files changed, 561 insertions(+) create mode 100644 azure/table_azure_consumption_usage.go create mode 100644 docs/tables/azure_consumption_usage.md diff --git a/azure/plugin.go b/azure/plugin.go index 15c2d62d..5aea2152 100644 --- a/azure/plugin.go +++ b/azure/plugin.go @@ -67,6 +67,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "azure_compute_virtual_machine_scale_set": tableAzureComputeVirtualMachineScaleSet(ctx), "azure_compute_virtual_machine_scale_set_network_interface": tableAzureComputeVirtualMachineScaleSetNetworkInterface(ctx), "azure_compute_virtual_machine_scale_set_vm": tableAzureComputeVirtualMachineScaleSetVm(ctx), + "azure_consumption_usage": tableAzureConsuptionUsage(ctx), "azure_container_group": tableAzureContainerGroup(ctx), "azure_container_registry": tableAzureContainerRegistry(ctx), "azure_cosmosdb_account": tableAzureCosmosDBAccount(ctx), diff --git a/azure/table_azure_consumption_usage.go b/azure/table_azure_consumption_usage.go new file mode 100644 index 00000000..7dd42dda --- /dev/null +++ b/azure/table_azure_consumption_usage.go @@ -0,0 +1,334 @@ +package azure + +import ( + "context" + "reflect" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/consumption/mgmt/2019-10-01/consumption" + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" +) + +//// TABLE DEFINITION + +func tableAzureConsuptionUsage(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "azure_consumption_usage", + Description: "Azure Consuption Usage", + List: &plugin.ListConfig{ + Hydrate: listConsuptionUsage, + KeyColumns: plugin.KeyColumnSlice{ + { + Name: "filter", + Operators: []string{"="}, + Require: plugin.Optional, + }, + { + Name: "metric", + Operators: []string{"="}, + Require: plugin.Optional, + }, + { + Name: "scope", + Operators: []string{"="}, + Require: plugin.Optional, + }, + { + Name: "expand", + Operators: []string{"="}, + Require: plugin.Optional, + }, + }, + }, + Columns: azureColumns([]*plugin.Column{ + { + Name: "name", + Type: proto.ColumnType_STRING, + Description: "The ID that uniquely identifies an event.", + }, + { + Name: "id", + Description: "The full qualified ARM ID of an event.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ID"), + }, + { + Name: "scope", + Description: "The scope associated with usage details operations.", + Type: proto.ColumnType_STRING, + }, + { + Name: "metric", + Description: "Allows to select different type of cost/usage records. Possible values are 'actualcost', 'amortizedcost' or 'usage'.", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("metric"), + }, + { + Name: "filter", + Description: "May be used to filter usageDetails by properties/resourceGroup, properties/instanceName, properties/resourceId, properties/chargeType, properties/reservationId, properties/publisherType or tags. The filter supports 'eq', 'lt', 'gt', 'le', 'ge', and 'and'. It does not currently support 'ne', 'or', or 'not'. Tag filter is a key value pair string where key and value is separated by a colon (:). PublisherType Filter accepts two values azure and marketplace and it is currently supported for Web Direct Offer Type.", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("filter"), + }, + { + Name: "expand", + Description: "May be used to expand the 'properties/additionalInfo' or 'properties/meterDetails' within a list of usage details. By default, these fields are not included when listing usage details.", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("expand"), + }, + { + Name: "kind", + Description: "Specifies the kind of usage details.", + Type: proto.ColumnType_STRING, + }, + { + Name: "type", + Description: "Type of the resource.", + Type: proto.ColumnType_STRING, + }, + { + Name: "etag", + Description: "The etag for the resource.", + Type: proto.ColumnType_STRING, + }, + { + Name: "modern_usage_detail", + Description: "The modern usage detail.", + Type: proto.ColumnType_JSON, + }, + { + Name: "legacy_usage_detail", + Description: "The legacy usage detail.", + Type: proto.ColumnType_JSON, + }, + + // Steampipe standard columns + { + Name: "title", + Description: ColumnDescriptionTitle, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Name"), + }, + { + Name: "tags", + Description: ColumnDescriptionTags, + Type: proto.ColumnType_JSON, + }, + { + Name: "akas", + Description: ColumnDescriptionAkas, + Type: proto.ColumnType_JSON, + Transform: transform.FromField("ID").Transform(idToAkas), + }, + }), + } +} + +type UsageDetails struct { + Scope *string + Kind consumption.Kind + ID *string + Name *string + Type *string + Etag *string + Tags map[string]*string + ModernUsageDetail map[string]interface{} + LegacyUsageDetail map[string]interface{} +} + +//// LIST FUNCTION + +func listConsuptionUsage(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + session, err := GetNewSession(ctx, d, "MANAGEMENT") + if err != nil { + return nil, err + } + + subscriptionID := session.SubscriptionID + + consumptionClient := consumption.NewUsageDetailsClientWithBaseURI(session.ResourceManagerEndpoint, subscriptionID) + consumptionClient.Authorizer = session.Authorizer + + scope := "/subscriptions/" + subscriptionID + "/" // Default scope is subscription + if d.EqualsQualString("scope") != "" { + scope = d.EqualsQualString("scope") + } + expand := "" + if d.EqualsQualString("expand") != "" { + expand = d.EqualsQualString("expand") + } + + /** + • The API returns an error if a billing time period is not specified for consumption usage. + • Error: Billing Period is not supported in (2021-10-01) API Version for Subscription Scope With Web Direct Offer. Please provide the UsageStart and UsageEnd dates in the $filter key as parameters. + • For consumption queries, specifying the start and end dates is required. + • By default, the time period considered is the past year. + **/ + + filter := getConsumptionFilter(d.Quals) + skiptoken := "" + // top := int32(0) + // metric := consumption.MetrictypeUsageMetricType + var metric consumption.Metrictype + if d.EqualsQualString("metric") != "" { + switch d.EqualsQualString("metric") { + case "actualcost": + metric = consumption.MetrictypeActualCostMetricType + case "amortizedcost": + metric = consumption.MetrictypeAmortizedCostMetricType + case "usage": + metric = consumption.MetrictypeUsageMetricType + } + } + result, err := consumptionClient.List(ctx, scope, expand, filter, skiptoken, nil, metric) + if err != nil { + return nil, err + } + + for _, res := range result.Values() { + result := getUsageDetailsByUsageDetailKind(res, scope) + if result != nil { + d.StreamListItem(ctx, result) + + // Check if context has been cancelled or if the limit has been hit (if specified) + // if there is a limit, it will return the number of rows required to reach this limit + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + + } + + for result.NotDone() { + err = result.NextWithContext(ctx) + if err != nil { + return nil, err + } + + for _, res := range result.Values() { + result := getUsageDetailsByUsageDetailKind(res, scope) + if result != nil { + d.StreamListItem(ctx, result) + + // Check if context has been cancelled or if the limit has been hit (if specified) + // if there is a limit, it will return the number of rows required to reach this limit + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + } + } + + return nil, err +} + +//// UTILITY FUNCTION + +// Get ussage details for all type(madern, legacy, basic) of consumption usage. +func getUsageDetailsByUsageDetailKind(res consumption.BasicUsageDetail, scope string) *UsageDetails { + result := &UsageDetails{} + modernUsageDetails, isModern := res.AsModernUsageDetail() + legacyUsageDetails, isLegacy := res.AsLegacyUsageDetail() + usageDetails, ud := res.AsUsageDetail() + if isModern { + result.Scope = &scope + result.ID = modernUsageDetails.ID + result.Etag = modernUsageDetails.Etag + result.Name = modernUsageDetails.Name + result.Tags = modernUsageDetails.Tags + result.Type = modernUsageDetails.Type + result.Kind = modernUsageDetails.Kind + result.ModernUsageDetail = extractUsageDetailProperties(modernUsageDetails.ModernUsageDetailProperties) + } + if isLegacy { + result.Scope = &scope + result.ID = legacyUsageDetails.ID + result.Etag = legacyUsageDetails.Etag + result.Name = legacyUsageDetails.Name + result.Tags = legacyUsageDetails.Tags + result.Type = legacyUsageDetails.Type + result.Kind = legacyUsageDetails.Kind + result.LegacyUsageDetail = extractUsageDetailProperties(legacyUsageDetails.LegacyUsageDetailProperties) + } + if ud { + result.Scope = &scope + result.ID = usageDetails.ID + result.Etag = usageDetails.Etag + result.Name = usageDetails.Name + result.Tags = usageDetails.Tags + result.Type = usageDetails.Type + result.Kind = usageDetails.Kind + } + + return result +} + +func extractUsageDetailProperties(value interface{}) map[string]interface{} { + data := make(map[string]interface{}) + + switch item := value.(type) { + case *consumption.ModernUsageDetailProperties: + if item != nil { + + // Use reflection to iterate over the struct fields + data = structToMap(reflect.ValueOf(*item)) + } + case *consumption.LegacyUsageDetailProperties: + if item != nil { + + // Use reflection to iterate over the struct fields + data = structToMap(reflect.ValueOf(*item)) + } + } + + return data +} + +func structToMap(val reflect.Value) map[string]interface{} { + result := make(map[string]interface{}) + + for i := 0; i < val.NumField(); i++ { + field := val.Type().Field(i) + fieldValue := val.Field(i) + + // Check if field is a struct and not a zero value + if fieldValue.Kind() == reflect.Struct && !fieldValue.IsZero() { + result[field.Name] = structToMap(fieldValue) + } else if !fieldValue.IsZero() { + result[field.Name] = fieldValue.Interface() + } else { + result[field.Name] = nil + } + } + + return result +} + +func getConsumptionFilter(quals plugin.KeyColumnQualMap) (filter string) { + filter = "" + if quals["filter"] != nil { + for _, q := range quals["filter"].Quals { + if q.Operator == "=" { + val := q.Value.GetStringValue() + filter = val + } + } + } + + outputLayout := "2006-01-02T15:04:05Z" + endData := time.Now().AddDate(-1, 0, 0).Format(outputLayout) + startData := time.Now().Format(outputLayout) + + // Default time period is last one year + if filter != "" && !strings.Contains(filter, "properties/usageEnd") && !strings.Contains(filter, "properties/usageStart") { + filter = filter + " and properties/usageEnd eq '" + endData + "' and properties/usageStart eq '" + startData + "'" + } + if filter == "" && !strings.Contains(filter, "properties/usageEnd") && !strings.Contains(filter, "properties/usageStart") { + filter = "properties/usageEnd eq '" + endData + "' and properties/usageStart eq '" + startData + "'" + } + + return filter +} diff --git a/docs/tables/azure_consumption_usage.md b/docs/tables/azure_consumption_usage.md new file mode 100644 index 00000000..69463b0b --- /dev/null +++ b/docs/tables/azure_consumption_usage.md @@ -0,0 +1,223 @@ +--- +title: "Steampipe Table: azure_consumption_usage - Query Azure Cost Management using SQL" +description: "Allows users to query Azure Cost Management usage details for the defined scope." +--- + +# Table: azure_consumption_usage - Query Azure Container Groups using SQL + +Azure Consumption Usage provide the ability to explore cost and usage data via multidimensional analysis, where creating customized filters and expressions allow you to answer consumption-related questions for your Azure resources. + +## Table Usage Guide + +The `azure_consumption_usage` table provides the comprehensive data about the resources and services you have used in your Azure subscription, along with the associated costs. This information is crucial for managing and optimizing Azure costs, understanding billing, and monitoring resource utilization. + +**Important notes:** +- By default this table returns the result for subscription scope. +- This table can provide consumption usage details for the previous one year. +- For improved performance, it is advised that you use the optional qual `filter` to limit the result set to a specific time period . +- This table supports optional quals. Queries with optional quals are optimized to use Consumption Usage filters. Optional quals are supported for the following columns: + - `filter`: May be used to filter usageDetails by properties/resourceGroup, properties/instanceName, properties/resourceId, properties/chargeType, properties/reservationId, properties/publisherType or tags. The filter supports 'eq', 'lt', 'gt', 'le', 'ge', and 'and'. It does not currently support 'ne', 'or', or 'not'. Tag filter is a key value pair string where key and value is separated by a colon (:). PublisherType Filter accepts two values azure and marketplace and it is currently supported for Web Direct Offer Type." + - `metric`: Allows to select different type of cost/usage records. Possible values are 'actualcost', 'amortizedcost' or 'usage'. + - `scope`: The scope associated with usage details operations. This includes '/subscriptions/{subscriptionId}/' for subscription scope, '/providers/Microsoft.Billing/billingAccounts/{billingAccountId}' for Billing Account scope, '/providers/Microsoft.Billing/departments/{departmentId}' for Department scope, '/providers/Microsoft.Billing/enrollmentAccounts/{enrollmentAccountId}' for EnrollmentAccount scope and '/providers/Microsoft.Management/managementGroups/{managementGroupId}' for Management Group scope. For subscription, billing account, department, enrollment account and management group, you can also add billing period to the scope using '/providers/Microsoft.Billing/billingPeriods/{billingPeriodName}'. For e.g. to specify billing period at department scope use '/providers/Microsoft.Billing/departments/{departmentId}/providers/Microsoft.Billing/billingPeriods/{billingPeriodName}'. Also, Modern Commerce Account scopes are '/providers/Microsoft.Billing/billingAccounts/{billingAccountId}' for billingAccount scope, '/providers/Microsoft.Billing/billingAccounts/{billingAccountId}/billingProfiles/{billingProfileId}' for billingProfile scope, 'providers/Microsoft.Billing/billingAccounts/{billingAccountId}/billingProfiles/{billingProfileId}/invoiceSections/{invoiceSectionId}' for invoiceSection scope, and 'providers/Microsoft.Billing/billingAccounts/{billingAccountId}/customers/{customerId}' specific for partners. + - `expand`: May be used to expand the 'properties/additionalInfo' or 'properties/meterDetails' within a list of usage details. By default, these fields are not included when listing usage details. + +## Examples + +### Basic info +This query is useful for getting a broad overview of resource consumption in Azure. It can help in cost management, resource optimization, and understanding how different Azure resources are being utilized. The data retrieved can be instrumental for in-depth analysis, especially when dealing with complex Azure environments with multiple resources and services. + +```sql+postgres +select + name, + id, + scope, + kind, + etag, + type +from + azure_consumption_usage; +``` + +```sql+sqlite +select + name, + id, + scope, + kind, + etag, + type +from + azure_consumption_usage; +``` + +### Get legacy consumption usage in a subscription + This is beneficial for organizations looking to get insights into their legacy resource usage in Azure, aiding in decision-making regarding migration, cost management, and resource optimization. + +```sql+postgres +select + name, + id, + scope, + kind, + etag, + type +from + azure_consumption_usage +where + kind = 'legacy'; +``` + +```sql+sqlite +select + name, + id, + scope, + kind, + etag, + type +from + azure_consumption_usage +where + kind = 'legacy'; +``` + +### Filter actual cost stastics of legacy consumption usage +Extract detailed insights into their Azure consumption, focusing on legacy resources and actual cost metrics. It aids in financial management, resource optimization, and strategic planning in the context of Azure cloud services. + +```sql+postgres +select + name, + id, + metric, + kind, + legacy_usage_detail ->> 'BillingAccountID' as billing_account_id, + legacy_usage_detail ->> 'BillingAccountName' as billing_account_name, + legacy_usage_detail ->> 'BillingPeriodStartDate' as billing_period_start_date, + legacy_usage_detail ->> 'BillingPeriodEndDate' as billing_period_end_date, + legacy_usage_detail ->> 'Product' as product, + legacy_usage_detail ->> 'Quantity' as quantity, + legacy_usage_detail ->> 'Cost' as cost, + legacy_usage_detail ->> 'BillingCurrency' as billing_currency, + legacy_usage_detail ->> 'ChargeType' as charge_type, + legacy_usage_detail ->> 'IsAzureCreditEligible' as is_azure_credit_eligible, + legacy_usage_detail ->> 'ResourceID' as resource_id +from + azure_consumption_usage; +where + kind = 'legacy' + and metric = 'actualcost'; +``` + +```sql+sqlite +select + name, + id, + metric, + kind, + json_extract(legacy_usage_detail, '$.BillingAccountID') as billing_account_id, + json_extract(legacy_usage_detail, '$.BillingAccountName') as billing_account_name, + json_extract(legacy_usage_detail, '$.BillingPeriodStartDate') as billing_period_start_date, + json_extract(legacy_usage_detail, '$.BillingPeriodEndDate') as billing_period_end_date, + json_extract(legacy_usage_detail, '$.Product') as product, + json_extract(legacy_usage_detail, '$.Quantity') as quantity, + json_extract(legacy_usage_detail, '$.Cost') as cost, + json_extract(legacy_usage_detail, '$.BillingCurrency') as billing_currency, + json_extract(legacy_usage_detail, '$.ChargeType') as charge_type, + json_extract(legacy_usage_detail, '$.IsAzureCreditEligible') as is_azure_credit_eligible, + json_extract(legacy_usage_detail, '$.ResourceID') as resource_id +from azure_consumption_usage +where + kind = 'legacy' + and metric = 'actualcost'; +``` + +### Get top 10 legacy consumption usages in a year +Analyze the distribution of Azure container groups based on their operating system type. This can help in understanding the usage pattern of different OS types within your Azure container groups. + +```sql+postgres +select + name, + id, + metric, + kind, + legacy_usage_detail ->> 'BillingAccountID' as billing_account_id, + legacy_usage_detail ->> 'BillingAccountName' as billing_account_name, + legacy_usage_detail ->> 'BillingPeriodStartDate' as billing_period_start_date, + legacy_usage_detail ->> 'BillingPeriodEndDate' as billing_period_end_date, + legacy_usage_detail ->> 'Cost' as cost, + legacy_usage_detail ->> 'BillingCurrency' as billing_currency +from + azure_consumption_usage +where + kind = 'legacy' +and + metric = 'actualcost' +order by + cost desc limit 10; +``` + +```sql+sqlite +select + name, + id, + metric, + kind, + json_extract(legacy_usage_detail, '$.BillingAccountID') as billing_account_id, + json_extract(legacy_usage_detail, '$.BillingAccountName') as billing_account_name, + json_extract(legacy_usage_detail, '$.BillingPeriodStartDate') as billing_period_start_date, + json_extract(legacy_usage_detail, '$.BillingPeriodEndDate') as billing_period_end_date, + json_extract(legacy_usage_detail, '$.Cost') as cost, + json_extract(legacy_usage_detail, '$.BillingCurrency') as billing_currency +from + azure_consumption_usage +where + kind = 'legacy' +and + metric = 'actualcost' +order by + cost desc limit 10; +``` + +### Filter consumption usage by resource group +Discover the segments that provide information about IP addresses associated with each group. This is useful in understanding the network connectivity and accessibility of these groups within the Azure container ecosystem. + +```sql+postgres +select + name, + id, + metric, + kind, + legacy_usage_detail ->> 'BillingAccountID' as billing_account_id, + legacy_usage_detail ->> 'BillingAccountName' as billing_account_name, + legacy_usage_detail ->> 'BillingPeriodStartDate' as billing_period_start_date, + legacy_usage_detail ->> 'BillingPeriodEndDate' as billing_period_end_date, + legacy_usage_detail ->> 'Cost' as cost, + legacy_usage_detail ->> 'BillingCurrency' as billing_currency, + legacy_usage_detail ->> 'ResourceID' as resource_id +from + azure_consumption_usage +where + kind = 'legacy' + and metric = 'actualcost' + and filter = 'properties/resourceGroup eq ''turbot_rg'''; +``` + +```sql+sqlite +select + name, + id, + metric, + kind, + json_extract(legacy_usage_detail, '$.BillingAccountID') as billing_account_id, + json_extract(legacy_usage_detail, '$.BillingAccountName') as billing_account_name, + json_extract(legacy_usage_detail, '$.BillingPeriodStartDate') as billing_period_start_date, + json_extract(legacy_usage_detail, '$.BillingPeriodEndDate') as billing_period_end_date, + json_extract(legacy_usage_detail, '$.Cost') as cost, + json_extract(legacy_usage_detail, '$.BillingCurrency') as billing_currency +from + azure_consumption_usage +where + kind = 'legacy' + and metric = 'actualcost' + and filter = 'properties/resourceGroup eq ''turbot_rg'''; +``` \ No newline at end of file diff --git a/go.mod b/go.mod index 7215214d..55239025 100644 --- a/go.mod +++ b/go.mod @@ -102,6 +102,7 @@ require ( github.com/prometheus/procfs v0.8.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/sethvargo/go-retry v0.2.4 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/stevenle/topsort v0.2.0 // indirect github.com/stretchr/testify v1.8.4 // indirect github.com/tkrajina/go-reflector v0.5.6 // indirect diff --git a/go.sum b/go.sum index 9199eb14..913419a1 100644 --- a/go.sum +++ b/go.sum @@ -605,6 +605,8 @@ github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= From 8ce05e284296f0ff352c6fc7b72424c79979771e Mon Sep 17 00:00:00 2001 From: ParthaI Date: Wed, 24 Jan 2024 19:35:54 +0530 Subject: [PATCH 2/4] Added a few commant as per the code changes and added error log in the code --- azure/table_azure_consumption_usage.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/azure/table_azure_consumption_usage.go b/azure/table_azure_consumption_usage.go index 7dd42dda..7f8a85e9 100644 --- a/azure/table_azure_consumption_usage.go +++ b/azure/table_azure_consumption_usage.go @@ -144,6 +144,7 @@ type UsageDetails struct { func listConsuptionUsage(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { session, err := GetNewSession(ctx, d, "MANAGEMENT") if err != nil { + plugin.Logger(ctx).Error("azure_consumption_usage.listConsuptionUsage", "sessin_error", err) return nil, err } @@ -170,8 +171,6 @@ func listConsuptionUsage(ctx context.Context, d *plugin.QueryData, _ *plugin.Hyd filter := getConsumptionFilter(d.Quals) skiptoken := "" - // top := int32(0) - // metric := consumption.MetrictypeUsageMetricType var metric consumption.Metrictype if d.EqualsQualString("metric") != "" { switch d.EqualsQualString("metric") { @@ -185,6 +184,7 @@ func listConsuptionUsage(ctx context.Context, d *plugin.QueryData, _ *plugin.Hyd } result, err := consumptionClient.List(ctx, scope, expand, filter, skiptoken, nil, metric) if err != nil { + plugin.Logger(ctx).Error("azure_consumption_usage.listConsuptionUsage", "api_error", err) return nil, err } @@ -205,6 +205,7 @@ func listConsuptionUsage(ctx context.Context, d *plugin.QueryData, _ *plugin.Hyd for result.NotDone() { err = result.NextWithContext(ctx) if err != nil { + plugin.Logger(ctx).Error("azure_consumption_usage.listConsuptionUsage", "paging_error", err) return nil, err } @@ -227,7 +228,7 @@ func listConsuptionUsage(ctx context.Context, d *plugin.QueryData, _ *plugin.Hyd //// UTILITY FUNCTION -// Get ussage details for all type(madern, legacy, basic) of consumption usage. +// Get usage details for all type(madern, legacy, basic) of consumption usage. func getUsageDetailsByUsageDetailKind(res consumption.BasicUsageDetail, scope string) *UsageDetails { result := &UsageDetails{} modernUsageDetails, isModern := res.AsModernUsageDetail() @@ -266,6 +267,9 @@ func getUsageDetailsByUsageDetailKind(res consumption.BasicUsageDetail, scope st return result } + +// When directly accessing an inner attribute using the "FromField()" function, the value is being populated as null even though the response contains a value. +// Therefore, it's necessary to extract the value of the nested attributes from the response. func extractUsageDetailProperties(value interface{}) map[string]interface{} { data := make(map[string]interface{}) @@ -307,6 +311,7 @@ func structToMap(val reflect.Value) map[string]interface{} { return result } +// Construct the filter query parameter in accordance with the API's behavior. func getConsumptionFilter(quals plugin.KeyColumnQualMap) (filter string) { filter = "" if quals["filter"] != nil { From a84f86807e62fec5ca4d50fc18b821906327a453 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Thu, 25 Jan 2024 14:45:02 +0530 Subject: [PATCH 3/4] Rectified the spelling mistake --- azure/table_azure_consumption_usage.go | 14 +++++++------- docs/tables/azure_consumption_usage.md | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/azure/table_azure_consumption_usage.go b/azure/table_azure_consumption_usage.go index 7f8a85e9..251cc690 100644 --- a/azure/table_azure_consumption_usage.go +++ b/azure/table_azure_consumption_usage.go @@ -15,12 +15,12 @@ import ( //// TABLE DEFINITION -func tableAzureConsuptionUsage(_ context.Context) *plugin.Table { +func tableAzureConsumptionUsage(_ context.Context) *plugin.Table { return &plugin.Table{ Name: "azure_consumption_usage", - Description: "Azure Consuption Usage", + Description: "Azure Consumption Usage", List: &plugin.ListConfig{ - Hydrate: listConsuptionUsage, + Hydrate: listConsumptionUsage, KeyColumns: plugin.KeyColumnSlice{ { Name: "filter", @@ -141,10 +141,10 @@ type UsageDetails struct { //// LIST FUNCTION -func listConsuptionUsage(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { +func listConsumptionUsage(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { session, err := GetNewSession(ctx, d, "MANAGEMENT") if err != nil { - plugin.Logger(ctx).Error("azure_consumption_usage.listConsuptionUsage", "sessin_error", err) + plugin.Logger(ctx).Error("azure_consumption_usage.listConsumptionUsage", "sessin_error", err) return nil, err } @@ -184,7 +184,7 @@ func listConsuptionUsage(ctx context.Context, d *plugin.QueryData, _ *plugin.Hyd } result, err := consumptionClient.List(ctx, scope, expand, filter, skiptoken, nil, metric) if err != nil { - plugin.Logger(ctx).Error("azure_consumption_usage.listConsuptionUsage", "api_error", err) + plugin.Logger(ctx).Error("azure_consumption_usage.listConsumptionUsage", "api_error", err) return nil, err } @@ -205,7 +205,7 @@ func listConsuptionUsage(ctx context.Context, d *plugin.QueryData, _ *plugin.Hyd for result.NotDone() { err = result.NextWithContext(ctx) if err != nil { - plugin.Logger(ctx).Error("azure_consumption_usage.listConsuptionUsage", "paging_error", err) + plugin.Logger(ctx).Error("azure_consumption_usage.listConsumptionUsage", "paging_error", err) return nil, err } diff --git a/docs/tables/azure_consumption_usage.md b/docs/tables/azure_consumption_usage.md index 69463b0b..430a8687 100644 --- a/docs/tables/azure_consumption_usage.md +++ b/docs/tables/azure_consumption_usage.md @@ -3,7 +3,7 @@ title: "Steampipe Table: azure_consumption_usage - Query Azure Cost Management u description: "Allows users to query Azure Cost Management usage details for the defined scope." --- -# Table: azure_consumption_usage - Query Azure Container Groups using SQL +# Table: azure_consumption_usage - Query Azure Consumption Usage using SQL Azure Consumption Usage provide the ability to explore cost and usage data via multidimensional analysis, where creating customized filters and expressions allow you to answer consumption-related questions for your Azure resources. @@ -102,7 +102,7 @@ select legacy_usage_detail ->> 'IsAzureCreditEligible' as is_azure_credit_eligible, legacy_usage_detail ->> 'ResourceID' as resource_id from - azure_consumption_usage; + azure_consumption_usage where kind = 'legacy' and metric = 'actualcost'; @@ -125,7 +125,8 @@ select json_extract(legacy_usage_detail, '$.ChargeType') as charge_type, json_extract(legacy_usage_detail, '$.IsAzureCreditEligible') as is_azure_credit_eligible, json_extract(legacy_usage_detail, '$.ResourceID') as resource_id -from azure_consumption_usage +from + azure_consumption_usage where kind = 'legacy' and metric = 'actualcost'; From 7d52cb3d3576133cb51397c3426ef21c71db3a0c Mon Sep 17 00:00:00 2001 From: ParthaI <47887552+ParthaI@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:57:53 +0530 Subject: [PATCH 4/4] resolved the lint error --- azure/plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/plugin.go b/azure/plugin.go index 5aea2152..3e7ff576 100644 --- a/azure/plugin.go +++ b/azure/plugin.go @@ -67,7 +67,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "azure_compute_virtual_machine_scale_set": tableAzureComputeVirtualMachineScaleSet(ctx), "azure_compute_virtual_machine_scale_set_network_interface": tableAzureComputeVirtualMachineScaleSetNetworkInterface(ctx), "azure_compute_virtual_machine_scale_set_vm": tableAzureComputeVirtualMachineScaleSetVm(ctx), - "azure_consumption_usage": tableAzureConsuptionUsage(ctx), + "azure_consumption_usage": tableAzureConsumptionUsage(ctx), "azure_container_group": tableAzureContainerGroup(ctx), "azure_container_registry": tableAzureContainerRegistry(ctx), "azure_cosmosdb_account": tableAzureCosmosDBAccount(ctx),