Skip to content

Commit

Permalink
feat(ibm): databases-for-postgresql
Browse files Browse the repository at this point in the history
Added support for databases-for-postgresql
  • Loading branch information
andreainnocenti committed Jan 19, 2024
1 parent c738113 commit dc75564
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 0 deletions.
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),
}
}

0 comments on commit dc75564

Please sign in to comment.