From f3c22f70926d3e937362ed83af1c12d40c7aad3a Mon Sep 17 00:00:00 2001 From: Luisa Rojas Date: Wed, 11 Sep 2024 00:47:32 -0400 Subject: [PATCH] Add support for VPN server resource (#186) * Add starter files * Add cost components and function calls * Update metrics names * Add default and example usage * Add unit tests --- infracost-usage-example.yml | 7 ++ internal/providers/terraform/ibm/ibm.go | 1 + .../providers/terraform/ibm/is_vpn_server.go | 29 ++++++ .../terraform/ibm/is_vpn_server_test.go | 16 +++ internal/providers/terraform/ibm/registry.go | 3 + .../is_vpn_server_test.golden | 25 +++++ .../is_vpn_server_test/is_vpn_server_test.tf | 53 ++++++++++ .../is_vpn_server_test.usage.yml | 5 + internal/resources/ibm/is_vpn_server.go | 97 +++++++++++++++++++ 9 files changed, 236 insertions(+) create mode 100644 internal/providers/terraform/ibm/is_vpn_server.go create mode 100644 internal/providers/terraform/ibm/is_vpn_server_test.go create mode 100644 internal/providers/terraform/ibm/testdata/is_vpn_server_test/is_vpn_server_test.golden create mode 100644 internal/providers/terraform/ibm/testdata/is_vpn_server_test/is_vpn_server_test.tf create mode 100644 internal/providers/terraform/ibm/testdata/is_vpn_server_test/is_vpn_server_test.usage.yml create mode 100644 internal/resources/ibm/is_vpn_server.go diff --git a/infracost-usage-example.yml b/infracost-usage-example.yml index 47527af1bdf..d3292721dd3 100644 --- a/infracost-usage-example.yml +++ b/infracost-usage-example.yml @@ -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: # @@ -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 diff --git a/internal/providers/terraform/ibm/ibm.go b/internal/providers/terraform/ibm/ibm.go index 683f3e77f39..37c55a6819b 100644 --- a/internal/providers/terraform/ibm/ibm.go +++ b/internal/providers/terraform/ibm/ibm.go @@ -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) { diff --git a/internal/providers/terraform/ibm/is_vpn_server.go b/internal/providers/terraform/ibm/is_vpn_server.go new file mode 100644 index 00000000000..3eb16de0d94 --- /dev/null +++ b/internal/providers/terraform/ibm/is_vpn_server.go @@ -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() +} diff --git a/internal/providers/terraform/ibm/is_vpn_server_test.go b/internal/providers/terraform/ibm/is_vpn_server_test.go new file mode 100644 index 00000000000..5364a1acd0f --- /dev/null +++ b/internal/providers/terraform/ibm/is_vpn_server_test.go @@ -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") +} diff --git a/internal/providers/terraform/ibm/registry.go b/internal/providers/terraform/ibm/registry.go index 3b69d0d7e85..c9fd0fe1379 100644 --- a/internal/providers/terraform/ibm/registry.go +++ b/internal/providers/terraform/ibm/registry.go @@ -20,6 +20,7 @@ var ResourceRegistry []*schema.RegistryItem = []*schema.RegistryItem{ getIsPublicGatewayRegistryItem(), getIbmPiVolumeRegistryItem(), getDatabaseRegistryItem(), + getIsVpnServerRegistryItem(), } // FreeResources grouped alphabetically @@ -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{ diff --git a/internal/providers/terraform/ibm/testdata/is_vpn_server_test/is_vpn_server_test.golden b/internal/providers/terraform/ibm/testdata/is_vpn_server_test/is_vpn_server_test.golden new file mode 100644 index 00000000000..cfcd94e904e --- /dev/null +++ b/internal/providers/terraform/ibm/testdata/is_vpn_server_test/is_vpn_server_test.golden @@ -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 \ No newline at end of file diff --git a/internal/providers/terraform/ibm/testdata/is_vpn_server_test/is_vpn_server_test.tf b/internal/providers/terraform/ibm/testdata/is_vpn_server_test/is_vpn_server_test.tf new file mode 100644 index 00000000000..915f5502a92 --- /dev/null +++ b/internal/providers/terraform/ibm/testdata/is_vpn_server_test/is_vpn_server_test.tf @@ -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" + } +} diff --git a/internal/providers/terraform/ibm/testdata/is_vpn_server_test/is_vpn_server_test.usage.yml b/internal/providers/terraform/ibm/testdata/is_vpn_server_test/is_vpn_server_test.usage.yml new file mode 100644 index 00000000000..5e6df8bf919 --- /dev/null +++ b/internal/providers/terraform/ibm/testdata/is_vpn_server_test/is_vpn_server_test.usage.yml @@ -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 diff --git a/internal/resources/ibm/is_vpn_server.go b/internal/resources/ibm/is_vpn_server.go new file mode 100644 index 00000000000..edb1d43e86e --- /dev/null +++ b/internal/resources/ibm/is_vpn_server.go @@ -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, + } +}