From fd9249207ce03ea6e06cad74c1e54734003e5d28 Mon Sep 17 00:00:00 2001 From: tdannenmuller <114987187+tdannenmuller@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:57:09 +0100 Subject: [PATCH 1/4] add Invoices table (#122) --- docs/tables/scaleway_invoices.md | 132 ++++++++++++++ scaleway/plugin.go | 1 + scaleway/table_scaleway_invoices.go | 259 ++++++++++++++++++++++++++++ 3 files changed, 392 insertions(+) create mode 100644 docs/tables/scaleway_invoices.md create mode 100644 scaleway/table_scaleway_invoices.go diff --git a/docs/tables/scaleway_invoices.md b/docs/tables/scaleway_invoices.md new file mode 100644 index 0000000..aefc9e4 --- /dev/null +++ b/docs/tables/scaleway_invoices.md @@ -0,0 +1,132 @@ +--- +title: "Steampipe Table: scaleway_invoice - Query Scaleway Invoices using SQL" +description: "Allows users to query Scaleway Invoices, providing detailed information about billing and usage for Scaleway services." +--- + +# Table: scaleway_invoice - Query Scaleway Invoices using SQL + +Scaleway Invoices are detailed records of charges for the use of Scaleway's cloud services. These invoices provide a comprehensive breakdown of costs associated with various resources and services used within a Scaleway account. + +## Table Usage Guide + +The `scaleway_invoice` table provides insights into billing information within Scaleway. As a finance manager or cloud administrator, explore invoice-specific details through this table, including total amounts, billing periods, and associated organizations. Utilize it to track expenses, verify charges, and manage cloud spending across different projects and timeframes. + +## Examples + +### Basic info +Explore the basic details of your Scaleway invoices, including their unique identifiers, associated organizations, and billing periods. This can help in tracking and managing your cloud expenses effectively. + +```sql +SELECT + id, + organization_id, + billing_period, + total_taxed_amount, + state, + currency +FROM + scaleway_invoices; +``` + +### Get total billed amount for each organization +Calculate the total amount billed to each organization. This provides an overview of cloud spending across different entities within your Scaleway account. + +```sql +SELECT + organization_id, + SUM(total_taxed_amount) as total_billed, + currency +FROM + scaleway_invoices +GROUP BY + organization_id, + currency; +``` + +### Find invoices with high discount amounts +Identify invoices with significant discounts. This can help in understanding which billing periods or services are providing the most cost savings. + +```sql +SELECT + id, + billing_period, + total_discount_amount, + total_taxed_amount, + currency +FROM + scaleway_invoices +WHERE + total_discount_amount > 1000 +ORDER BY + total_discount_amount DESC; +``` + +### List invoices for a specific date range +Retrieve invoices within a specific time frame. This is useful for periodic financial reviews or audits. + +```sql +SELECT + id, + billing_period, + total_taxed_amount, + issued_date, + currency +FROM + scaleway_invoices +WHERE + issued_date BETWEEN '2023-01-01' AND '2023-12-31' +ORDER BY + issued_date; +``` + +### Get the average invoice amount by month +Calculate the average invoice amount for each month. This helps in understanding monthly spending patterns and budgeting for cloud services. + +```sql +SELECT + DATE_TRUNC('month', issued_date) AS month, + AVG(total_taxed_amount) AS average_invoice_amount, + currency +FROM + scaleway_invoices +GROUP BY + DATE_TRUNC('month', issued_date), + currency +ORDER BY + month; +``` + +### Compare total taxed and untaxed amounts +Analyze the difference between taxed and untaxed amounts for each invoice to understand the tax impact on your cloud spending. + +```sql +SELECT + id, + total_untaxed_amount, + total_taxed_amount, + total_taxed_amount - total_untaxed_amount AS tax_amount, + currency +FROM + scaleway_invoices +ORDER BY + tax_amount DESC; +``` + +### Examine discounts and their impact +Investigate how discounts affect your invoices by comparing the undiscounted amount to the final taxed amount. + +```sql +SELECT + id, + total_undiscount_amount, + total_discount_amount, + total_taxed_amount, + total_discount_amount / total_undiscount_amount * 100 AS discount_percentage, + currency +FROM + scaleway_invoices +WHERE + total_discount_amount > 0 +ORDER BY + discount_percentage DESC; +``` diff --git a/scaleway/plugin.go b/scaleway/plugin.go index e370cc7..ab0f122 100644 --- a/scaleway/plugin.go +++ b/scaleway/plugin.go @@ -31,6 +31,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "scaleway_instance_server": tableScalewayInstanceServer(ctx), "scaleway_instance_snapshot": tableScalewayInstanceSnapshot(ctx), "scaleway_instance_volume": tableScalewayInstanceVolume(ctx), + "scaleway_invoices": tableScalewayInvoices(ctx), "scaleway_kubernetes_cluster": tableScalewayKubernetesCluster(ctx), "scaleway_kubernetes_node": tableScalewayKubernetesNode(ctx), "scaleway_kubernetes_pool": tableScalewayKubernetesPool(ctx), diff --git a/scaleway/table_scaleway_invoices.go b/scaleway/table_scaleway_invoices.go new file mode 100644 index 0000000..51f4a43 --- /dev/null +++ b/scaleway/table_scaleway_invoices.go @@ -0,0 +1,259 @@ +package scaleway + +import ( + "context" + "fmt" + "math" + "time" + + billing "github.com/scaleway/scaleway-sdk-go/api/billing/v2beta1" + + "github.com/scaleway/scaleway-sdk-go/scw" + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" +) + +func tableScalewayInvoices(ctx context.Context) *plugin.Table { + plugin.Logger(ctx).Debug("Initializing Scaleway invoices table") + return &plugin.Table{ + Name: "scaleway_invoices", + Description: "invoices in your Scaleway account.", + List: &plugin.ListConfig{ + Hydrate: listScalewayInvoices, + KeyColumns: []*plugin.KeyColumn{ + {Name: "organization_id", Require: plugin.Optional}, + }, + }, + Columns: []*plugin.Column{ + { + Name: "id", + Type: proto.ColumnType_STRING, + Description: "The unique identifier of the invoices.", + Transform: transform.FromField("ID"), + }, + { + Name: "organization_id", + Type: proto.ColumnType_STRING, + Description: "The organization ID associated with the invoices.", + Transform: transform.FromField("OrganizationID"), + }, + { + Name: "organization_name", + Type: proto.ColumnType_STRING, + Description: "The organization name associated with the invoices.", + }, + { + Name: "start_date", + Type: proto.ColumnType_TIMESTAMP, + Description: "The start date of the billing period.", + }, + { + Name: "stop_date", + Type: proto.ColumnType_TIMESTAMP, + Description: "The end date of the billing period.", + }, + { + Name: "billing_period", + Type: proto.ColumnType_TIMESTAMP, + Description: "The billing period for the invoices.", + }, + { + Name: "issued_date", + Type: proto.ColumnType_TIMESTAMP, + Description: "The date when the invoices was issued.", + }, + { + Name: "due_date", + Type: proto.ColumnType_TIMESTAMP, + Description: "The due date for the invoices payment.", + }, + { + Name: "total_untaxed_amount", + Type: proto.ColumnType_DOUBLE, + Description: "The total untaxed amount of the invoices.", + Transform: transform.FromField("TotalUntaxed").Transform(extractAmount), + }, + { + Name: "total_taxed_amount", + Type: proto.ColumnType_DOUBLE, + Description: "The total taxed amount of the invoices.", + Transform: transform.FromField("TotalTaxed").Transform(extractAmount), + }, + { + Name: "total_discount_amount", + Type: proto.ColumnType_DOUBLE, + Description: "The total discount amount of the invoice (always positive).", + Transform: transform.FromField("TotalDiscount").Transform(extractAmount), + }, + { + Name: "total_undiscount_amount", + Type: proto.ColumnType_DOUBLE, + Description: "The total undiscounted amount of the invoices.", + Transform: transform.FromField("TotalUndiscount").Transform(extractAmount), + }, + { + Name: "currency", + Type: proto.ColumnType_STRING, + Description: "The currency used for all monetary values in the invoices.", + Transform: transform.FromField("TotalTaxed").Transform(extractCurrency), + }, + { + Name: "type", + Type: proto.ColumnType_STRING, + Description: "The type of the invoices.", + }, + { + Name: "state", + Type: proto.ColumnType_STRING, + Description: "The current state of the invoices.", + }, + { + Name: "number", + Type: proto.ColumnType_INT, + Description: "The invoices number.", + }, + { + Name: "seller_name", + Type: proto.ColumnType_STRING, + Description: "The name of the seller.", + }, + }, + } +} + +func extractAmount(ctx context.Context, d *transform.TransformData) (interface{}, error) { + plugin.Logger(ctx).Debug("extractAmount", "field", d.ColumnName, "raw_value", d.Value) + + if d.Value == nil { + plugin.Logger(ctx).Warn("extractAmount: nil value", "field", d.ColumnName) + return nil, nil + } + + money, ok := d.Value.(*scw.Money) + if !ok || money == nil { + plugin.Logger(ctx).Warn("extractAmount: unexpected type or nil", "type", fmt.Sprintf("%T", d.Value)) + return nil, nil + } + + amount := float64(money.Units) + float64(money.Nanos)/1e9 + if d.ColumnName == "total_discount_amount" { + return math.Abs(amount), nil + } + return amount, nil +} + +func extractCurrency(ctx context.Context, d *transform.TransformData) (interface{}, error) { + plugin.Logger(ctx).Debug("extractCurrency", "field", d.ColumnName, "raw_value", d.Value) + + if d.Value == nil { + plugin.Logger(ctx).Warn("extractCurrency: nil value", "field", d.ColumnName) + return nil, nil + } + + switch v := d.Value.(type) { + case *scw.Money: + if v == nil { + return nil, nil + } + return v.CurrencyCode, nil + default: + plugin.Logger(ctx).Warn("extractCurrency: unexpected type", "type", fmt.Sprintf("%T", d.Value)) + return nil, nil + } +} + +type invoicesItem struct { + ID string `json:"id"` + OrganizationID string `json:"organization_id"` + OrganizationName string `json:"organization_name"` + StartDate *time.Time `json:"start_date"` + StopDate *time.Time `json:"stop_date"` + IssuedDate *time.Time `json:"issued_date"` + DueDate *time.Time `json:"due_date"` + TotalUntaxed *scw.Money `json:"total_untaxed"` + TotalTaxed *scw.Money `json:"total_taxed"` + TotalDiscount *scw.Money `json:"total_discount"` + TotalUndiscount *scw.Money `json:"total_undiscount"` + TotalTax *scw.Money `json:"total_tax"` + Type billing.InvoiceType `json:"type"` + Number int32 `json:"number"` + State string `json:"state"` + BillingPeriod *time.Time `json:"billing_period"` + SellerName string `json:"seller_name"` +} + +func listScalewayInvoices(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Get client configuration + client, err := getSessionConfig(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("scaleway_invoices.listScalewayInvoices", "connection_error", err) + return nil, err + } + + // Check if the client is properly configured + if client == nil { + return nil, fmt.Errorf("scaleway client is not properly configured") + } + + billingAPI := billing.NewAPI(client) + + // Prepare the request + req := &billing.ListInvoicesRequest{} + + // Get the organization_id from the config + scalewayConfig := GetConfig(d.Connection) + var organizationID string + if scalewayConfig.OrganizationID != nil { + organizationID = *scalewayConfig.OrganizationID + } + + // Check if organization_id is specified in the query + if d.EqualsQualString("organization_id") != "" { + organizationID = d.EqualsQualString("organization_id") + } + + // Log a warning if organization_id is not provided by either config or query + if organizationID == "" { + plugin.Logger(ctx).Warn("scaleway_invoices.listScalewayInvoices", "warning", "No organization_id provided in config or query") + } + + // Set the organization_id in the request if it's available + if organizationID != "" { + req.OrganizationID = &organizationID + } + + // Make the API request to list invoices + resp, err := billingAPI.ListInvoices(req) + if err != nil { + plugin.Logger(ctx).Error("scaleway_invoices.listScalewayInvoices", "api_error", err) + return nil, err + } + + for _, invoices := range resp.Invoices { + plugin.Logger(ctx).Debug("raw invoices data", "invoices", fmt.Sprintf("%+v", invoices)) + + item := invoicesItem{ + ID: invoices.ID, + OrganizationID: invoices.OrganizationID, + OrganizationName: invoices.OrganizationName, + StartDate: invoices.StartDate, + StopDate: invoices.StopDate, + IssuedDate: invoices.IssuedDate, + DueDate: invoices.DueDate, + TotalUntaxed: invoices.TotalUntaxed, + TotalTaxed: invoices.TotalTaxed, + TotalDiscount: invoices.TotalDiscount, + TotalUndiscount: invoices.TotalUndiscount, + TotalTax: invoices.TotalTax, + Type: invoices.Type, + Number: invoices.Number, + State: invoices.State, + BillingPeriod: invoices.BillingPeriod, + SellerName: invoices.SellerName, + } + d.StreamListItem(ctx, item) + } + + return nil, nil +} From 641858de75c5f67e4593616e6c4549868dd0a02a Mon Sep 17 00:00:00 2001 From: ParthaI Date: Mon, 25 Nov 2024 18:04:07 +0530 Subject: [PATCH 2/4] Updated the table with GetConfig, column arrangement, log statement and cleanup --- docs/tables/scaleway_billing_invoice.md | 229 +++++++++++++++ docs/tables/scaleway_invoices.md | 132 --------- scaleway/plugin.go | 2 +- ...s.go => table_scaleway_billing_invoice.go} | 269 ++++++++++-------- 4 files changed, 386 insertions(+), 246 deletions(-) create mode 100644 docs/tables/scaleway_billing_invoice.md delete mode 100644 docs/tables/scaleway_invoices.md rename scaleway/{table_scaleway_invoices.go => table_scaleway_billing_invoice.go} (61%) diff --git a/docs/tables/scaleway_billing_invoice.md b/docs/tables/scaleway_billing_invoice.md new file mode 100644 index 0000000..47ca48e --- /dev/null +++ b/docs/tables/scaleway_billing_invoice.md @@ -0,0 +1,229 @@ +--- +title: Steampipe Table: scaleway_billing_invoice - Query Scaleway Invoices using SQL +description: Enables users to query Scaleway invoices, offering comprehensive billing and usage details for Scaleway cloud services. +--- + +# Table: scaleway_billing_invoice - Query Scaleway invoices using SQL + +Scaleway invoices provide detailed records of charges for using Scaleway's cloud services. These invoices include a complete breakdown of costs for various resources and services within a Scaleway account. + +## Table Usage Guide + +The `scaleway_billing_invoice` table offers insights into billing information in Scaleway. It allows finance managers or cloud administrators to query invoice-specific details such as total amounts, billing periods, and associated organizations. Use this table to track expenses, verify charges, and manage cloud spending across different projects and timeframes. + +### Examples + +#### Explore basic details of Scaleway invoices +Retrieve invoice identifiers, associated organizations, and billing periods to track cloud expenses effectively. + +```sql+postgres +select + id, + organization_id, + billing_period, + total_taxed_amount, + state, + currency +from + scaleway_billing_invoice; +``` + +```sql+sqlite +select + id, + organization_id, + billing_period, + total_taxed_amount, + state, + currency +from + scaleway_billing_invoice; +``` + +#### Get total billed amount for each organization +Calculate the total amount billed for each organization to analyze spending across different entities. + +```sql+postgres +select + organization_id, + sum(total_taxed_amount) as total_billed, + currency +from + scaleway_billing_invoice +group by + organization_id, + currency; +``` + +```sql+sqlite +select + organization_id, + sum(total_taxed_amount) as total_billed, + currency +from + scaleway_billing_invoice +group by + organization_id, + currency; +``` + +#### Find invoices with high discount amounts +Identify invoices with substantial discounts to understand cost-saving opportunities. + +```sql+postgres +select + id, + billing_period, + total_discount_amount, + total_taxed_amount, + currency +from + scaleway_billing_invoice +where + total_discount_amount > 1000 +order by + total_discount_amount desc; +``` + +```sql+sqlite +select + id, + billing_period, + total_discount_amount, + total_taxed_amount, + currency +from + scaleway_billing_invoice +where + total_discount_amount > 1000 +order by + total_discount_amount desc; +``` + +#### List invoices within a specific date range +Retrieve invoices for a defined period to assist with financial reviews or audits. + +```sql+postgres +select + id, + billing_period, + total_taxed_amount, + issued_date, + currency +from + scaleway_billing_invoice +where + issued_date between '2023-01-01' and '2023-12-31' +order by + issued_date; +``` + +```sql+sqlite +select + id, + billing_period, + total_taxed_amount, + issued_date, + currency +from + scaleway_billing_invoice +where + issued_date between '2023-01-01' and '2023-12-31' +order by + issued_date; +``` + +#### Get the average invoice amount by month +Analyze monthly spending patterns by calculating the average invoice amount. + +```sql+postgres +select + date_trunc('month', issued_date) as month, + avg(total_taxed_amount) as average_invoice_amount, + currency +from + scaleway_billing_invoice +group by + date_trunc('month', issued_date), + currency +order by + month; +``` + +```sql+sqlite +select + strftime('%Y-%m', issued_date) as month, + avg(total_taxed_amount) as average_invoice_amount, + currency +from + scaleway_billing_invoice +group by + strftime('%Y-%m', issued_date), + currency +order by + month; +``` + +#### Compare total taxed and untaxed amounts +Analyze the tax impact by comparing taxed and untaxed amounts. + +```sql+postgres +select + id, + total_untaxed_amount, + total_taxed_amount, + total_taxed_amount - total_untaxed_amount as tax_amount, + currency +from + scaleway_billing_invoice +order by + tax_amount desc; +``` + +```sql+sqlite +select + id, + total_untaxed_amount, + total_taxed_amount, + total_taxed_amount - total_untaxed_amount as tax_amount, + currency +from + scaleway_billing_invoice +order by + tax_amount desc; +``` + +#### Examine discounts and their impact +Evaluate the effect of discounts on invoices by comparing undiscounted and final taxed amounts. + +```sql+postgres +select + id, + total_undiscount_amount, + total_discount_amount, + total_taxed_amount, + total_discount_amount / total_undiscount_amount * 100 as discount_percentage, + currency +from + scaleway_billing_invoice +where + total_discount_amount > 0 +order by + discount_percentage desc; +``` + +```sql+sqlite +select + id, + total_undiscount_amount, + total_discount_amount, + total_taxed_amount, + total_discount_amount / total_undiscount_amount * 100 as discount_percentage, + currency +from + scaleway_billing_invoice +where + total_discount_amount > 0 +order by + discount_percentage desc; +``` \ No newline at end of file diff --git a/docs/tables/scaleway_invoices.md b/docs/tables/scaleway_invoices.md deleted file mode 100644 index aefc9e4..0000000 --- a/docs/tables/scaleway_invoices.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -title: "Steampipe Table: scaleway_invoice - Query Scaleway Invoices using SQL" -description: "Allows users to query Scaleway Invoices, providing detailed information about billing and usage for Scaleway services." ---- - -# Table: scaleway_invoice - Query Scaleway Invoices using SQL - -Scaleway Invoices are detailed records of charges for the use of Scaleway's cloud services. These invoices provide a comprehensive breakdown of costs associated with various resources and services used within a Scaleway account. - -## Table Usage Guide - -The `scaleway_invoice` table provides insights into billing information within Scaleway. As a finance manager or cloud administrator, explore invoice-specific details through this table, including total amounts, billing periods, and associated organizations. Utilize it to track expenses, verify charges, and manage cloud spending across different projects and timeframes. - -## Examples - -### Basic info -Explore the basic details of your Scaleway invoices, including their unique identifiers, associated organizations, and billing periods. This can help in tracking and managing your cloud expenses effectively. - -```sql -SELECT - id, - organization_id, - billing_period, - total_taxed_amount, - state, - currency -FROM - scaleway_invoices; -``` - -### Get total billed amount for each organization -Calculate the total amount billed to each organization. This provides an overview of cloud spending across different entities within your Scaleway account. - -```sql -SELECT - organization_id, - SUM(total_taxed_amount) as total_billed, - currency -FROM - scaleway_invoices -GROUP BY - organization_id, - currency; -``` - -### Find invoices with high discount amounts -Identify invoices with significant discounts. This can help in understanding which billing periods or services are providing the most cost savings. - -```sql -SELECT - id, - billing_period, - total_discount_amount, - total_taxed_amount, - currency -FROM - scaleway_invoices -WHERE - total_discount_amount > 1000 -ORDER BY - total_discount_amount DESC; -``` - -### List invoices for a specific date range -Retrieve invoices within a specific time frame. This is useful for periodic financial reviews or audits. - -```sql -SELECT - id, - billing_period, - total_taxed_amount, - issued_date, - currency -FROM - scaleway_invoices -WHERE - issued_date BETWEEN '2023-01-01' AND '2023-12-31' -ORDER BY - issued_date; -``` - -### Get the average invoice amount by month -Calculate the average invoice amount for each month. This helps in understanding monthly spending patterns and budgeting for cloud services. - -```sql -SELECT - DATE_TRUNC('month', issued_date) AS month, - AVG(total_taxed_amount) AS average_invoice_amount, - currency -FROM - scaleway_invoices -GROUP BY - DATE_TRUNC('month', issued_date), - currency -ORDER BY - month; -``` - -### Compare total taxed and untaxed amounts -Analyze the difference between taxed and untaxed amounts for each invoice to understand the tax impact on your cloud spending. - -```sql -SELECT - id, - total_untaxed_amount, - total_taxed_amount, - total_taxed_amount - total_untaxed_amount AS tax_amount, - currency -FROM - scaleway_invoices -ORDER BY - tax_amount DESC; -``` - -### Examine discounts and their impact -Investigate how discounts affect your invoices by comparing the undiscounted amount to the final taxed amount. - -```sql -SELECT - id, - total_undiscount_amount, - total_discount_amount, - total_taxed_amount, - total_discount_amount / total_undiscount_amount * 100 AS discount_percentage, - currency -FROM - scaleway_invoices -WHERE - total_discount_amount > 0 -ORDER BY - discount_percentage DESC; -``` diff --git a/scaleway/plugin.go b/scaleway/plugin.go index ab0f122..2a13c98 100644 --- a/scaleway/plugin.go +++ b/scaleway/plugin.go @@ -23,6 +23,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "scaleway_account_ssh_key": tableScalewayAccountSSHKey(ctx), "scaleway_baremetal_server": tableScalewayBaremetalServer(ctx), "scaleway_billing_consumption": tableScalewayBillingConsumption(ctx), + "scaleway_billing_invoice": tableScalewayBillingInvoice(ctx), "scaleway_iam_api_key": tableScalewayIamAPIKey(ctx), "scaleway_iam_user": tableScalewayIamUser(ctx), "scaleway_instance_image": tableScalewayInstanceImage(ctx), @@ -31,7 +32,6 @@ func Plugin(ctx context.Context) *plugin.Plugin { "scaleway_instance_server": tableScalewayInstanceServer(ctx), "scaleway_instance_snapshot": tableScalewayInstanceSnapshot(ctx), "scaleway_instance_volume": tableScalewayInstanceVolume(ctx), - "scaleway_invoices": tableScalewayInvoices(ctx), "scaleway_kubernetes_cluster": tableScalewayKubernetesCluster(ctx), "scaleway_kubernetes_node": tableScalewayKubernetesNode(ctx), "scaleway_kubernetes_pool": tableScalewayKubernetesPool(ctx), diff --git a/scaleway/table_scaleway_invoices.go b/scaleway/table_scaleway_billing_invoice.go similarity index 61% rename from scaleway/table_scaleway_invoices.go rename to scaleway/table_scaleway_billing_invoice.go index 51f4a43..f372242 100644 --- a/scaleway/table_scaleway_invoices.go +++ b/scaleway/table_scaleway_billing_invoice.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math" - "time" billing "github.com/scaleway/scaleway-sdk-go/api/billing/v2beta1" @@ -14,17 +13,26 @@ import ( "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" ) -func tableScalewayInvoices(ctx context.Context) *plugin.Table { +func tableScalewayBillingInvoice(ctx context.Context) *plugin.Table { plugin.Logger(ctx).Debug("Initializing Scaleway invoices table") return &plugin.Table{ - Name: "scaleway_invoices", - Description: "invoices in your Scaleway account.", + Name: "scaleway_billing_invoice", + Description: "Scaleway Billing Invoice", List: &plugin.ListConfig{ Hydrate: listScalewayInvoices, KeyColumns: []*plugin.KeyColumn{ {Name: "organization_id", Require: plugin.Optional}, + {Name: "type", Require: plugin.Optional}, + {Name: "billing_period", Require: plugin.Optional, Operators: []string{">=", "<="}}, }, }, + Get: &plugin.GetConfig{ + Hydrate: getScalewayInvoice, + KeyColumns: plugin.SingleColumn("id"), + // When an incorrect Invoice ID is provided, no "not found" error is returned. + // Instead, a timeout error is encountered, as shown below. + // Error: rpc error: code = DeadlineExceeded desc = scaleway-sdk-go: error executing request: Get "https://api.scaleway.com/account/v3/projects/5575456ffhgfh": dial tcp: lookup api.scaleway.com: i/o timeout. + }, Columns: []*plugin.Column{ { Name: "id", @@ -38,6 +46,26 @@ func tableScalewayInvoices(ctx context.Context) *plugin.Table { Description: "The organization ID associated with the invoices.", Transform: transform.FromField("OrganizationID"), }, + { + Name: "type", + Type: proto.ColumnType_STRING, + Description: "The type of the invoices.", + }, + { + Name: "state", + Type: proto.ColumnType_STRING, + Description: "The current state of the invoices.", + }, + { + Name: "number", + Type: proto.ColumnType_INT, + Description: "The invoices number.", + }, + { + Name: "seller_name", + Type: proto.ColumnType_STRING, + Description: "The name of the seller.", + }, { Name: "organization_name", Type: proto.ColumnType_STRING, @@ -98,162 +126,177 @@ func tableScalewayInvoices(ctx context.Context) *plugin.Table { Description: "The currency used for all monetary values in the invoices.", Transform: transform.FromField("TotalTaxed").Transform(extractCurrency), }, + + // Scaleway standard columns { - Name: "type", - Type: proto.ColumnType_STRING, - Description: "The type of the invoices.", - }, - { - Name: "state", + Name: "organization", + Description: "The ID of the organization where the server resides.", Type: proto.ColumnType_STRING, - Description: "The current state of the invoices.", - }, - { - Name: "number", - Type: proto.ColumnType_INT, - Description: "The invoices number.", + Transform: transform.FromField("OrganizationID"), }, + + // Steampipe standard columns { - Name: "seller_name", + Name: "title", + Description: "Title of the resource.", Type: proto.ColumnType_STRING, - Description: "The name of the seller.", + Transform: transform.FromField("Number"), }, }, } } -func extractAmount(ctx context.Context, d *transform.TransformData) (interface{}, error) { - plugin.Logger(ctx).Debug("extractAmount", "field", d.ColumnName, "raw_value", d.Value) +//// LIST FUNCTION - if d.Value == nil { - plugin.Logger(ctx).Warn("extractAmount: nil value", "field", d.ColumnName) - return nil, nil +func listScalewayInvoices(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Get client configuration + client, err := getSessionConfig(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("scaleway_billing_invoice.listScalewayInvoices", "connection_error", err) + return nil, err } - money, ok := d.Value.(*scw.Money) - if !ok || money == nil { - plugin.Logger(ctx).Warn("extractAmount: unexpected type or nil", "type", fmt.Sprintf("%T", d.Value)) - return nil, nil + billingAPI := billing.NewAPI(client) + + // Prepare the request + req := &billing.ListInvoicesRequest{} + + // Get the organization_id from the config + scalewayConfig := GetConfig(d.Connection) + var organizationID string + if scalewayConfig.OrganizationID != nil { + organizationID = *scalewayConfig.OrganizationID } - amount := float64(money.Units) + float64(money.Nanos)/1e9 - if d.ColumnName == "total_discount_amount" { - return math.Abs(amount), nil + // Check if organization_id is specified in the query parameter + if d.EqualsQualString("organization_id") != "" { + organizationID = d.EqualsQualString("organization_id") } - return amount, nil -} -func extractCurrency(ctx context.Context, d *transform.TransformData) (interface{}, error) { - plugin.Logger(ctx).Debug("extractCurrency", "field", d.ColumnName, "raw_value", d.Value) + // Set the organization_id in the request if it's available + if organizationID != "" { + req.OrganizationID = &organizationID + } - if d.Value == nil { - plugin.Logger(ctx).Warn("extractCurrency: nil value", "field", d.ColumnName) - return nil, nil + if d.EqualsQualString("type") != "" { + req.InvoiceType = billing.InvoiceType(d.EqualsQualString("type")) } - switch v := d.Value.(type) { - case *scw.Money: - if v == nil { - return nil, nil + quals := d.Quals + + if quals["billing_period"] != nil { + for _, q := range quals["billing_period"].Quals { + billingPeriod := q.Value.GetTimestampValue().AsTime() + switch q.Operator { + case ">=": + req.BillingPeriodStartAfter = &billingPeriod + case "<=": + req.BillingPeriodStartBefore = &billingPeriod + } } - return v.CurrencyCode, nil - default: - plugin.Logger(ctx).Warn("extractCurrency: unexpected type", "type", fmt.Sprintf("%T", d.Value)) - return nil, nil } -} -type invoicesItem struct { - ID string `json:"id"` - OrganizationID string `json:"organization_id"` - OrganizationName string `json:"organization_name"` - StartDate *time.Time `json:"start_date"` - StopDate *time.Time `json:"stop_date"` - IssuedDate *time.Time `json:"issued_date"` - DueDate *time.Time `json:"due_date"` - TotalUntaxed *scw.Money `json:"total_untaxed"` - TotalTaxed *scw.Money `json:"total_taxed"` - TotalDiscount *scw.Money `json:"total_discount"` - TotalUndiscount *scw.Money `json:"total_undiscount"` - TotalTax *scw.Money `json:"total_tax"` - Type billing.InvoiceType `json:"type"` - Number int32 `json:"number"` - State string `json:"state"` - BillingPeriod *time.Time `json:"billing_period"` - SellerName string `json:"seller_name"` + var count int + + for { + // Make the API request to list invoices + resp, err := billingAPI.ListInvoices(req) + if err != nil { + plugin.Logger(ctx).Error("scaleway_billing_invoice.listScalewayInvoices", "api_error", err) + return nil, err + } + + for _, invoice := range resp.Invoices { + + d.StreamListItem(ctx, invoice) + + // Increase the resource count by 1 + count++ + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + + if resp.TotalCount == uint64(count) { + break + } + req.Page = scw.Int32Ptr(*req.Page + 1) + } + + return nil, nil } -func listScalewayInvoices(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { +//// GYDRATE FUNCTION + +func getScalewayInvoice(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { // Get client configuration client, err := getSessionConfig(ctx, d) if err != nil { - plugin.Logger(ctx).Error("scaleway_invoices.listScalewayInvoices", "connection_error", err) + plugin.Logger(ctx).Error("scaleway_billing_invoice.getScalewayInvoice", "connection_error", err) return nil, err } - // Check if the client is properly configured - if client == nil { - return nil, fmt.Errorf("scaleway client is not properly configured") + // Empty check + if d.EqualsQualString("id") == "" { + return nil, nil } billingAPI := billing.NewAPI(client) // Prepare the request - req := &billing.ListInvoicesRequest{} + req := &billing.GetInvoiceRequest{ + InvoiceID: d.EqualsQualString("id"), + } - // Get the organization_id from the config - scalewayConfig := GetConfig(d.Connection) - var organizationID string - if scalewayConfig.OrganizationID != nil { - organizationID = *scalewayConfig.OrganizationID + resp, err := billingAPI.GetInvoice(req) + if err != nil { + plugin.Logger(ctx).Error("scaleway_billing_invoice.getScalewayInvoice", "api_error", err) + return nil, err } - // Check if organization_id is specified in the query - if d.EqualsQualString("organization_id") != "" { - organizationID = d.EqualsQualString("organization_id") + if resp != nil { + return resp, nil } - // Log a warning if organization_id is not provided by either config or query - if organizationID == "" { - plugin.Logger(ctx).Warn("scaleway_invoices.listScalewayInvoices", "warning", "No organization_id provided in config or query") + return nil, nil +} + +//// TRANSFORM FUNCTIONS + +func extractAmount(_ context.Context, d *transform.TransformData) (interface{}, error) { + + if d.Value == nil { + return nil, nil } - // Set the organization_id in the request if it's available - if organizationID != "" { - req.OrganizationID = &organizationID + money, ok := d.Value.(*scw.Money) + if !ok || money == nil { + return nil, nil } - // Make the API request to list invoices - resp, err := billingAPI.ListInvoices(req) - if err != nil { - plugin.Logger(ctx).Error("scaleway_invoices.listScalewayInvoices", "api_error", err) - return nil, err + amount := float64(money.Units) + float64(money.Nanos)/1e9 + if d.ColumnName == "total_discount_amount" { + return math.Abs(amount), nil } + return amount, nil +} - for _, invoices := range resp.Invoices { - plugin.Logger(ctx).Debug("raw invoices data", "invoices", fmt.Sprintf("%+v", invoices)) - - item := invoicesItem{ - ID: invoices.ID, - OrganizationID: invoices.OrganizationID, - OrganizationName: invoices.OrganizationName, - StartDate: invoices.StartDate, - StopDate: invoices.StopDate, - IssuedDate: invoices.IssuedDate, - DueDate: invoices.DueDate, - TotalUntaxed: invoices.TotalUntaxed, - TotalTaxed: invoices.TotalTaxed, - TotalDiscount: invoices.TotalDiscount, - TotalUndiscount: invoices.TotalUndiscount, - TotalTax: invoices.TotalTax, - Type: invoices.Type, - Number: invoices.Number, - State: invoices.State, - BillingPeriod: invoices.BillingPeriod, - SellerName: invoices.SellerName, - } - d.StreamListItem(ctx, item) +func extractCurrency(ctx context.Context, d *transform.TransformData) (interface{}, error) { + + if d.Value == nil { + return nil, nil } - return nil, nil + switch v := d.Value.(type) { + case *scw.Money: + if v == nil { + return nil, nil + } + return v.CurrencyCode, nil + default: + plugin.Logger(ctx).Warn("extractCurrency: unexpected type", "type", fmt.Sprintf("%T", d.Value)) + return nil, nil + } } From de839e563026211530ce2979b6f259ac8b440bac Mon Sep 17 00:00:00 2001 From: Ved misra <47312748+misraved@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:23:28 +0530 Subject: [PATCH 3/4] Update scaleway_billing_invoice.md --- docs/tables/scaleway_billing_invoice.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/tables/scaleway_billing_invoice.md b/docs/tables/scaleway_billing_invoice.md index 47ca48e..932f141 100644 --- a/docs/tables/scaleway_billing_invoice.md +++ b/docs/tables/scaleway_billing_invoice.md @@ -11,9 +11,9 @@ Scaleway invoices provide detailed records of charges for using Scaleway's cloud The `scaleway_billing_invoice` table offers insights into billing information in Scaleway. It allows finance managers or cloud administrators to query invoice-specific details such as total amounts, billing periods, and associated organizations. Use this table to track expenses, verify charges, and manage cloud spending across different projects and timeframes. -### Examples +## Examples -#### Explore basic details of Scaleway invoices +### Explore basic details of Scaleway invoices Retrieve invoice identifiers, associated organizations, and billing periods to track cloud expenses effectively. ```sql+postgres @@ -40,7 +40,7 @@ from scaleway_billing_invoice; ``` -#### Get total billed amount for each organization +### Get total billed amount for each organization Calculate the total amount billed for each organization to analyze spending across different entities. ```sql+postgres @@ -67,7 +67,7 @@ group by currency; ``` -#### Find invoices with high discount amounts +### Find invoices with high discount amounts Identify invoices with substantial discounts to understand cost-saving opportunities. ```sql+postgres @@ -100,7 +100,7 @@ order by total_discount_amount desc; ``` -#### List invoices within a specific date range +### List invoices within a specific date range Retrieve invoices for a defined period to assist with financial reviews or audits. ```sql+postgres @@ -133,7 +133,7 @@ order by issued_date; ``` -#### Get the average invoice amount by month +### Get the average invoice amount by month Analyze monthly spending patterns by calculating the average invoice amount. ```sql+postgres @@ -164,7 +164,7 @@ order by month; ``` -#### Compare total taxed and untaxed amounts +### Compare total taxed and untaxed amounts Analyze the tax impact by comparing taxed and untaxed amounts. ```sql+postgres @@ -193,7 +193,7 @@ order by tax_amount desc; ``` -#### Examine discounts and their impact +### Examine discounts and their impact Evaluate the effect of discounts on invoices by comparing undiscounted and final taxed amounts. ```sql+postgres @@ -226,4 +226,4 @@ where total_discount_amount > 0 order by discount_percentage desc; -``` \ No newline at end of file +``` From 35eb5a0898cb8ae16fbbfccde78027c0149c384d Mon Sep 17 00:00:00 2001 From: Ved misra <47312748+misraved@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:26:04 +0530 Subject: [PATCH 4/4] Update table_scaleway_billing_invoice.go --- scaleway/table_scaleway_billing_invoice.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scaleway/table_scaleway_billing_invoice.go b/scaleway/table_scaleway_billing_invoice.go index f372242..06f3ac0 100644 --- a/scaleway/table_scaleway_billing_invoice.go +++ b/scaleway/table_scaleway_billing_invoice.go @@ -89,12 +89,12 @@ func tableScalewayBillingInvoice(ctx context.Context) *plugin.Table { { Name: "issued_date", Type: proto.ColumnType_TIMESTAMP, - Description: "The date when the invoices was issued.", + Description: "The date when the invoices were issued.", }, { Name: "due_date", Type: proto.ColumnType_TIMESTAMP, - Description: "The due date for the invoices payment.", + Description: "The due date for the invoice payment.", }, { Name: "total_untaxed_amount", @@ -213,7 +213,7 @@ func listScalewayInvoices(ctx context.Context, d *plugin.QueryData, _ *plugin.Hy // Increase the resource count by 1 count++ - // Context can be cancelled due to manual cancellation or the limit has been hit + // Context can be canceled due to manual cancellation or the limit has been hit if d.RowsRemaining(ctx) == 0 { return nil, nil } @@ -228,7 +228,7 @@ func listScalewayInvoices(ctx context.Context, d *plugin.QueryData, _ *plugin.Hy return nil, nil } -//// GYDRATE FUNCTION +//// HYDRATE FUNCTION func getScalewayInvoice(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { // Get client configuration