Skip to content

Commit

Permalink
Add support for VPN server resource (#186)
Browse files Browse the repository at this point in the history
* Add starter files

* Add cost components and function calls

* Update metrics names

* Add default and example usage

* Add unit tests
  • Loading branch information
luisarojas authored Sep 11, 2024
1 parent 5d757dc commit f3c22f7
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 0 deletions.
7 changes: 7 additions & 0 deletions infracost-usage-example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ resource_type_default_usage:
elasticsearch_database_disk_mb: 131072
elasticsearch_database_core: 3
elasticsearch_database_members: 4
ibm_is_vpc_server:
is.vpn-server_CONNECTION_HOURS: 730
is.vpn-server_INSTANCE_HOURS: 730

resource_usage:
#
Expand Down Expand Up @@ -1392,3 +1395,7 @@ resource_usage:
elasticsearch_database_disk_mb: 131072 # Allocated disk in MB per-member
elasticsearch_database_core: 3 # Allocated dedicated CPU per-member
elasticsearch_database_members: 4 # Allocated number of members in the cluster

ibm_is_vpc_server:
is.vpn-server_CONNECTION_HOURS: 730
is.vpn-server_INSTANCE_HOURS: 730
1 change: 1 addition & 0 deletions internal/providers/terraform/ibm/ibm.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ var globalCatalogServiceId = map[string]catalogMetadata{
"compliance": {"compliance", []string{}, nil, "https://cloud.ibm.com/catalog/services/security-and-compliance-center"},
"dns-svcs": {"b4ed8a30-936f-11e9-b289-1d079699cbe5", []string{}, nil, "https://cloud.ibm.com/catalog/services/dns-services"},
"messagehub": {"6a7f4e38-f218-48ef-9dd2-df408747568e", []string{}, nil, "https://cloud.ibm.com/eventstreams-provisioning/6a7f4e38-f218-48ef-9dd2-df408747568e/create"},
"ibm_is_vpn_server": {"is.vpn-server", []string{}, nil, "https://cloud.ibm.com/vpc-ext/provision/vpnserver"},
}

func SetCatalogMetadata(d *schema.ResourceData, resourceType string, config map[string]any) {
Expand Down
29 changes: 29 additions & 0 deletions internal/providers/terraform/ibm/is_vpn_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package ibm

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

func getIsVpnServerRegistryItem() *schema.RegistryItem {
return &schema.RegistryItem{
Name: "ibm_is_vpn_server",
RFunc: newIsVpnServer,
}
}

func newIsVpnServer(d *schema.ResourceData, u *schema.UsageData) *schema.Resource {
region := d.Get("region").String()
r := &ibm.IsVpnServer{
Address: d.Address,
Region: region,
}
r.PopulateUsage(u)

configuration := make(map[string]any)
configuration["region"] = region

SetCatalogMetadata(d, d.Type, configuration)

return r.BuildResource()
}
16 changes: 16 additions & 0 deletions internal/providers/terraform/ibm/is_vpn_server_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 TestIsVpnServer(t *testing.T) {
t.Parallel()
if testing.Short() {
t.Skip("skipping test in short mode")
}

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

// FreeResources grouped alphabetically
Expand Down Expand Up @@ -172,6 +173,8 @@ var FreeResources = []string{
"ibm_sm_service_credentials_secret",
"ibm_sm_username_password_secret",
"ibm_tg_connection",
"ibm_is_vpn_server_client",
"ibm_is_vpn_server_route",
}

var UsageOnlyResources = []string{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Name Monthly Qty Unit Monthly Cost

ibm_is_vpc.vpc
├─ VPC instance 1 Instance $0.00
├─ VPC egress free allowance (first 5GB) Monthly cost depends on usage: $0.00 per GB
└─ VPC egress us-south (first 9995 GB) Monthly cost depends on usage: $0.090915 per GB
└─ VPC egress us-south (next 40000 GB) Monthly cost depends on usage: $0.086735 per GB
└─ VPC egress us-south (next 100000 GB) Monthly cost depends on usage: $0.07315 per GB
└─ VPC egress us-south (over 149995 GB) Monthly cost depends on usage: $0.05225 per GB

ibm_is_vpn_server.vpn_server
├─ VPN connection hours us-south 730 Hours $7.63
└─ VPN instance hours us-south 730 Hours $91.54

ibm_is_vpn_server.vpn_server_without_usage
├─ VPN connection hours us-south Monthly cost depends on usage: $0.01045 per Hours
└─ VPN instance hours us-south Monthly cost depends on usage: $0.13 per Hours

OVERALL TOTAL $99.17
──────────────────────────────────
5 cloud resources were detected:
∙ 3 were estimated, 1 of which usage-based costs, see https://infracost.io/usage-file
∙ 2 were free:
∙ 2 x ibm_is_subnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

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

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

resource "ibm_is_vpc" "vpc" {
name = "vpc"
}

resource "ibm_is_subnet" "subnet_1" {
name = "subnet-1"
vpc = ibm_is_vpc.vpc.id
zone = "us-south-1"
ipv4_cidr_block = "10.240.0.0/24"
}

resource "ibm_is_subnet" "subnet_2" {
name = "subnet-2"
vpc = ibm_is_vpc.vpc.id
zone = "us-south-2"
ipv4_cidr_block = "10.240.0.0/24"
}

resource "ibm_is_vpn_server" "vpn_server" {
name = "vpn-server"
certificate_crn = ""
client_ip_pool = "10.0.0.0/20"
subnets = [ibm_is_subnet.subnet_1.id]
client_authentication {
method = "username"
identity_provider = "iam"
}
}

resource "ibm_is_vpn_server" "vpn_server_without_usage" {
name = "vpn-server-no-usage"
certificate_crn = ""
client_ip_pool = "10.0.0.0/20"
subnets = [ibm_is_subnet.subnet_2.id]
client_authentication {
method = "username"
identity_provider = "iam"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version: 0.1
resource_usage:
ibm_is_vpn_server.vpn_server:
is.vpn-server_CONNECTION_HOURS: 730
is.vpn-server_INSTANCE_HOURS: 730
97 changes: 97 additions & 0 deletions internal/resources/ibm/is_vpn_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package ibm

import (
"fmt"

"github.com/infracost/infracost/internal/resources"
"github.com/infracost/infracost/internal/schema"
"github.com/shopspring/decimal"
)

// IsVpnServer struct represents a VPN server for IBM Cloud VPC
//
// Catalog information: https://cloud.ibm.com/vpc-ext/provision/vpnserver
// Resource information: https://cloud.ibm.com/docs/vpc?topic=vpc-vpn-overview#client-to-site-vpn-server
// Pricing information: https://www.ibm.com/cloud/vpc/pricing
type IsVpnServer struct {
Address string
Region string
MonthlyConnectionHours *float64 `infracost_usage:"is.vpn-server_CONNECTION_HOURS"`
MonthlyInstanceHours *float64 `infracost_usage:"is.vpn-server_INSTANCE_HOURS"`
}

// IsVpnServerUsageSchema defines a list which represents the usage schema of IsVpnServer.
var IsVpnServerUsageSchema = []*schema.UsageItem{
{Key: "is.vpn-server_CONNECTION_HOURS", DefaultValue: 0, ValueType: schema.Float64},
{Key: "is.vpn-server_INSTANCE_HOURS", DefaultValue: 0, ValueType: schema.Float64},
}

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

func (r *IsVpnServer) connectionHoursCostComponent() *schema.CostComponent {
var quantity *decimal.Decimal
if r.MonthlyConnectionHours != nil {
quantity = decimalPtr(decimal.NewFromFloat(*r.MonthlyConnectionHours))
}
return &schema.CostComponent{
Name: fmt.Sprintf("VPN connection hours %s", r.Region),
Unit: "Hours",
UnitMultiplier: decimal.NewFromInt(1),
MonthlyQuantity: quantity,
ProductFilter: &schema.ProductFilter{
VendorName: strPtr("ibm"),
Region: strPtr(r.Region),
Service: strPtr("is.vpn-server"),
AttributeFilters: []*schema.AttributeFilter{
{Key: "planName", ValueRegex: strPtr(fmt.Sprintf("/%s/i", "gen2-vpn-server"))},
},
},
PriceFilter: &schema.PriceFilter{
Unit: strPtr("CONNECTION_HOURS"),
},
}
}

func (r *IsVpnServer) instanceHoursCostComponent() *schema.CostComponent {
var quantity *decimal.Decimal
if r.MonthlyInstanceHours != nil {
quantity = decimalPtr(decimal.NewFromFloat(*r.MonthlyInstanceHours))
}
return &schema.CostComponent{
Name: fmt.Sprintf("VPN instance hours %s", r.Region),
Unit: "Hours",
UnitMultiplier: decimal.NewFromInt(1),
MonthlyQuantity: quantity,
ProductFilter: &schema.ProductFilter{
VendorName: strPtr("ibm"),
Region: strPtr(r.Region),
Service: strPtr("is.vpn-server"),
AttributeFilters: []*schema.AttributeFilter{
{Key: "planName", ValueRegex: strPtr(fmt.Sprintf("/%s/i", "gen2-vpn-server"))},
},
},
PriceFilter: &schema.PriceFilter{
Unit: strPtr("INSTANCE_HOURS"),
},
}
}

// BuildResource builds a schema.Resource from a valid IsVpnServer struct.
// This method is called after the resource is initialised by an IaC provider.
// See providers folder for more information.
func (r *IsVpnServer) BuildResource() *schema.Resource {
costComponents := []*schema.CostComponent{
r.connectionHoursCostComponent(),
r.instanceHoursCostComponent(),
}

return &schema.Resource{
Name: r.Address,
UsageSchema: IsVpnServerUsageSchema,
CostComponents: costComponents,
}
}

0 comments on commit f3c22f7

Please sign in to comment.