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

Add support for VPN server resource #186

Merged
merged 6 commits into from
Sep 11, 2024
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
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,
}
}
Loading