Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ibm): add databases-for-postgresql support #154

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions internal/providers/terraform/ibm/database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package ibm

import (
"github.com/infracost/infracost/internal/resources/ibm"
"github.com/infracost/infracost/internal/schema"
)

func getDatabaseRegistryItem() *schema.RegistryItem {
return &schema.RegistryItem{
Name: "ibm_database",
RFunc: newDatabase,
}
}

func newDatabase(d *schema.ResourceData, u *schema.UsageData) *schema.Resource {
plan := d.Get("plan").String()
location := d.Get("location").String()
service := d.Get("service").String()
name := d.Get("name").String()

r := &ibm.Database{
Name: name,
Address: d.Address,
Service: service,
Plan: plan,
Location: location,
Group: d.RawValues,
}
r.PopulateUsage(u)

configuration := make(map[string]any)
configuration["service"] = service
configuration["plan"] = plan
configuration["location"] = location

SetCatalogMetadata(d, service, configuration)

return r.BuildResource()
}
16 changes: 16 additions & 0 deletions internal/providers/terraform/ibm/database_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ibm_test

import (
"testing"

"github.com/infracost/infracost/internal/providers/terraform/tftest"
)

func TestDatabase(t *testing.T) {
t.Parallel()
if testing.Short() {
t.Skip("skipping test in short mode")
}

tftest.GoldenFileResourceTests(t, "database_test")
}
1 change: 1 addition & 0 deletions internal/providers/terraform/ibm/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var ResourceRegistry []*schema.RegistryItem = []*schema.RegistryItem{
getIsLbRegistryItem(),
getIsPublicGatewayRegistryItem(),
getIbmPiVolumeRegistryItem(),
getDatabaseRegistryItem(),
}

// FreeResources grouped alphabetically
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

Name Monthly Qty Unit Monthly Cost

ibm_database.test_db1
├─ RAM 24 GB-RAM $129.36
├─ Disk 256 GB-DISK $161.28
└─ Core 6 Virtual Processor Core $193.80

ibm_database.test_db2
├─ RAM 60 GB-RAM $323.40
└─ Disk 20 GB-DISK $12.60

OVERALL TOTAL $820.44
──────────────────────────────────
2 cloud resources were detected:
∙ 2 were estimated
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

terraform {
required_providers {
ibm = {
source = "IBM-Cloud/ibm"
version = "1.61.0"
}
}
}

provider "ibm" {
region = "us-south"
}

resource "ibm_database" "test_db1" {
name = "demo-postgres"
service = "databases-for-postgresql"
plan = "standard"
location = "eu-gb"

group {
group_id = "member"
memory {
allocation_mb = 12288
}
disk {
allocation_mb = 131072
}
cpu {
allocation_count = 3
}
}
}

resource "ibm_database" "test_db2" {
name = "demo-postgres2"
service = "databases-for-postgresql"
plan = "standard"
location = "eu-gb"

group {
group_id = "member"
memory {
allocation_mb = 15360
}
members {
allocation_count = 4
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: 0.1
resource_usage:
ibm_database.test_db1:
database_ram_mb: 12288
database_disk_mb: 131072
database_core: 3
ibm_database.test_db2:
database_ram_mb: 15360
database_members: 4
190 changes: 190 additions & 0 deletions internal/resources/ibm/database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package ibm

import (
"github.com/infracost/infracost/internal/resources"
"github.com/infracost/infracost/internal/schema"
"github.com/shopspring/decimal"
"github.com/tidwall/gjson"
)

// Database struct represents a database instance
//
// This terraform resource is opaque and can handle multiple databases, provided with the right parameters
type Database struct {
Name string
Address string
Service string
Plan string
Location string
Group gjson.Result

// Databases For PostgreSQL
// Catalog Link: https://cloud.ibm.com/catalog/services/databases-for-postgresql
// Pricing Link: https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-pricing
RAM *int64 `infracost_usage:"database_ram_mb"`
Disk *int64 `infracost_usage:"database_disk_mb"`
Core *int64 `infracost_usage:"database_core"`
Members *int64 `infracost_usage:"database_members"`
}

type DatabaseCostComponentsFunc func(*Database) []*schema.CostComponent

// PopulateUsage parses the u schema.UsageData into the Database.
// It uses the `infracost_usage` struct tags to populate data into the Database.
func (r *Database) PopulateUsage(u *schema.UsageData) {
resources.PopulateArgsWithUsage(r, u)
}

// DatabaseUsageSchema defines a list which represents the usage schema of Database.
var DatabaseUsageSchema = []*schema.UsageItem{
{Key: "database_ram_mb", DefaultValue: 0, ValueType: schema.Int64},
{Key: "database_disk_mb", DefaultValue: 0, ValueType: schema.Int64},
{Key: "database_core", DefaultValue: 0, ValueType: schema.Int64},
{Key: "database_members", DefaultValue: 0, ValueType: schema.Int64},
}

var DatabaseCostMap map[string]DatabaseCostComponentsFunc = map[string]DatabaseCostComponentsFunc{
"databases-for-postgresql": GetPostgresCostComponents,
// "databases-for-etcd":
// "databases-for-redis":
// "databases-for-elasticsearch":
// "messages-for-rabbitmq":
// "databases-for-mongodb":
// "databases-for-mysql":
// "databases-for-cassandra":
// "databases-for-enterprisedb"
}

func ConvertMBtoGB(d decimal.Decimal) decimal.Decimal {
return d.Div(decimal.NewFromInt(1024))
}

func PostgresRAMCostComponent(r *Database) *schema.CostComponent {
var R decimal.Decimal
if r.RAM != nil {
R = ConvertMBtoGB(decimal.NewFromInt(*r.RAM))
} else { // set the default
R = decimal.NewFromInt(1)
}
var m decimal.Decimal
if r.Members != nil {
m = decimal.NewFromInt(*r.Members)
} else { // set the default
m = decimal.NewFromInt(2)
}

cost := R.Mul(m)

costComponent := schema.CostComponent{
Name: "RAM",
Unit: "GB-RAM",
MonthlyQuantity: &cost,
UnitMultiplier: decimal.NewFromInt(1),
ProductFilter: &schema.ProductFilter{
VendorName: strPtr("ibm"),
Region: strPtr(r.Location),
Service: strPtr("databases-for-postgresql"),
ProductFamily: strPtr("service"),
},
PriceFilter: &schema.PriceFilter{
Unit: strPtr("GIGABYTE_MONTHS_RAM"),
},
}
return &costComponent
}

func PostgresDiskCostComponent(r *Database) *schema.CostComponent {
var d decimal.Decimal
if r.Disk != nil {
d = ConvertMBtoGB(decimal.NewFromInt(*r.Disk))
} else { // set the default
d = decimal.NewFromInt(5)
}
var m decimal.Decimal
if r.Members != nil {
m = decimal.NewFromInt(*r.Members)
} else { // set the default
m = decimal.NewFromInt(2)
}

cost := d.Mul(m)

costComponent := schema.CostComponent{
Name: "Disk",
Unit: "GB-DISK",
MonthlyQuantity: &cost,
UnitMultiplier: decimal.NewFromInt(1),
ProductFilter: &schema.ProductFilter{
VendorName: strPtr("ibm"),
Region: strPtr(r.Location),
Service: strPtr("databases-for-postgresql"),
ProductFamily: strPtr("service"),
},
PriceFilter: &schema.PriceFilter{
Unit: strPtr("GIGABYTE_MONTHS_DISK"),
},
}
return &costComponent
}

func PostgresCoreCostComponent(r *Database) *schema.CostComponent {
var c decimal.Decimal
if r.Core != nil {
c = decimal.NewFromInt(*r.Core)
} else { // set the default
c = decimal.NewFromInt(0)
}
var m decimal.Decimal
if r.Members != nil {
m = decimal.NewFromInt(*r.Members)
} else { // set the default
m = decimal.NewFromInt(2)
}

cost := c.Mul(m)

costComponent := schema.CostComponent{
Name: "Core",
Unit: "Virtual Processor Core",
MonthlyQuantity: &cost,
UnitMultiplier: decimal.NewFromInt(1),
ProductFilter: &schema.ProductFilter{
VendorName: strPtr("ibm"),
Region: strPtr(r.Location),
Service: strPtr("databases-for-postgresql"),
ProductFamily: strPtr("service"),
},
PriceFilter: &schema.PriceFilter{
Unit: strPtr("VIRTUAL_PROCESSOR_CORES"),
},
}
return &costComponent
}

func GetPostgresCostComponents(r *Database) []*schema.CostComponent {
return []*schema.CostComponent{
PostgresRAMCostComponent(r),
PostgresDiskCostComponent(r),
PostgresCoreCostComponent(r),
}
}

// BuildResource builds a schema.Resource from a valid Database struct.
// This method is called after the resource is initialised by an IaC provider.
// See providers folder for more information.
func (r *Database) BuildResource() *schema.Resource {
costComponentsFunc, ok := DatabaseCostMap[r.Service]

if !ok {
return &schema.Resource{
Name: r.Address,
UsageSchema: DatabaseUsageSchema,
}
}

return &schema.Resource{
Name: r.Address,
UsageSchema: DatabaseUsageSchema,
CostComponents: costComponentsFunc(r),
}
}
Loading