Skip to content

Commit

Permalink
add support for Business Metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
skyscrapr committed Jul 20, 2023
1 parent 6269b94 commit cbff92b
Show file tree
Hide file tree
Showing 8 changed files with 375 additions and 28 deletions.
1 change: 1 addition & 0 deletions cloudability/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func Provider() *schema.Provider {
"cloudability_master_account": resourceMasterAccount(),
"cloudability_linked_account": resourceLinkedAccount(),
"cloudability_business_mapping": resourceBusinessMapping(),
"cloudability_business_metric": resourceBusinessMetric(),
"cloudability_view": resourceView(),
},
ConfigureFunc: providerConfigure,
Expand Down
16 changes: 8 additions & 8 deletions cloudability/resource_business_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ func resourceBusinessMappingCreate(d *schema.ResourceData, meta interface{}) err
DefaultValue: d.Get("default_value").(string),
Statements: inflateStatements(d.Get("statement").([]interface{})),
}
businessMapping, err := client.BusinessMappings().NewBusinessMapping(&payload)
businessMapping, err := client.BusinessMappings().NewBusinessDimension(&payload)
if err != nil {
return err
}
ctx := context.TODO()
tflog.Info(ctx, fmt.Sprintf("New business mapping created with index: %d", businessMapping.Index))
tflog.Info(ctx, fmt.Sprintf("New business dimension created with index: %d", businessMapping.Index))
d.SetId(strconv.Itoa(businessMapping.Index))
time.Sleep(2 * time.Second)
return resourceBusinessMappingRead(d, meta)
Expand All @@ -99,8 +99,8 @@ func resourceBusinessMappingRead(d *schema.ResourceData, meta interface{}) error
return err
}
ctx := context.TODO()
tflog.Info(ctx, fmt.Sprintf("Reading business mapping with index: %d", index))
businessMapping, err := client.BusinessMappings().GetBusinessMapping(index)
tflog.Info(ctx, fmt.Sprintf("Reading business dimension with index: %d", index))
businessMapping, err := client.BusinessMappings().GetBusinessDimension(index)
if err != nil {
return err
}
Expand Down Expand Up @@ -131,12 +131,12 @@ func resourceBusinessMappingUpdate(d *schema.ResourceData, meta interface{}) err
DefaultValue: d.Get("default_value").(string),
Statements: inflateStatements(d.Get("statement").([]interface{})),
}
err = client.BusinessMappings().UpdateBusinessMapping(&payload)
err = client.BusinessMappings().UpdateBusinessDimension(&payload)
if err != nil {
return err
}
ctx := context.TODO()
tflog.Info(ctx, fmt.Sprintf("Updating business mapping with index: %d", id))
tflog.Info(ctx, fmt.Sprintf("Updating business dimension with index: %d", id))
return resourceBusinessMappingRead(d, meta)
}

Expand All @@ -149,14 +149,14 @@ func resourceBusinessMappingDelete(d *schema.ResourceData, meta interface{}) err
}
ctx := context.TODO()
tflog.Info(ctx, fmt.Sprintf("Deleting business mapping with index: %d", id))
err = client.BusinessMappings().DeleteBusinessMapping(id)
err = client.BusinessMappings().DeleteBusinessDimension(id)
if err != nil {
// Ignore 404 errors (No resource found)
var apiError cloudability.APIError
jsonErr := json.Unmarshal([]byte(err.Error()), &apiError)
if jsonErr == nil && apiError.Error.Status == 404 {
ctx := context.TODO()
tflog.Info(ctx, "resourceBusinessMappingDelete Resource not found. Ignoring")
tflog.Info(ctx, "resourceBusinessDimensionDelete Resource not found. Ignoring")
return nil
}
}
Expand Down
185 changes: 185 additions & 0 deletions cloudability/resource_business_metric.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package cloudability

import (
"context"
"encoding/json"
"fmt"
"strconv"

"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/skyscrapr/cloudability-sdk-go/cloudability"
)

func resourceBusinessMetric() *schema.Resource {
return &schema.Resource{
Create: resourceBusinessMetricCreate,
Read: resourceBusinessMetricRead,
// Update: resourceBusinessMetricUpdate,
Delete: resourceBusinessMetricDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"index": {
Type: schema.TypeInt,
Computed: true,
},
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"number_format": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: func(val any, key string) (warns []string, errs []error) {
switch val.(string) {
case
"currency",
"number":
return
}
errs = append(errs, fmt.Errorf("invalid value for number_format. Must ne 'currency' or 'number'"))
return
},
Default: "number",
ForceNew: true,
},
"default_value": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"default_value_expression"},
},
"default_value_expression": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"default_value"},
},
"pre_match_expression": {
Type: schema.TypeString,
Optional: true,
Default: "",
ForceNew: true,
},
"statement": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"match_expression": {
Type: schema.TypeString,
Required: true,
},
"value_expression": {
Type: schema.TypeString,
Required: true,
},
},
},
},
"updated_at": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourceBusinessMetricCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudability.Client)
payload := cloudability.BusinessMapping{
Kind: "BUSINESS_METRIC",
Name: d.Get("name").(string),
NumberFormat: d.Get("number_format").(string),
PreMatchExpression: d.Get("pre_match_expression").(string),
DefaultValueExpression: d.Get("default_value_expression").(string),
DefaultValue: d.Get("default_value").(string),
Statements: inflateStatements(d.Get("statement").([]interface{})),
}
businessMapping, err := client.BusinessMappings().NewBusinessMetric(&payload)
if err != nil {
return err
}
ctx := context.TODO()
tflog.Info(ctx, fmt.Sprintf("New business metric created with index: %d", businessMapping.Index))
d.SetId(strconv.Itoa(businessMapping.Index))
return resourceBusinessMetricRead(d, meta)
}

func resourceBusinessMetricRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudability.Client)
index, err := strconv.Atoi(d.Id())
if err != nil {
return err
}
ctx := context.TODO()
tflog.Info(ctx, fmt.Sprintf("Reading business metric with index: %d", index))
businessMapping, err := client.BusinessMappings().GetBusinessMetric(index)
if err != nil {
return err
}

if businessMapping != nil {
d.Set("index", businessMapping.Index)
d.Set("name", businessMapping.Name)
d.Set("number_format", businessMapping.NumberFormat)
d.Set("default_value", businessMapping.DefaultValue)
// d.Set("default_value_expression", businessMapping.DefaultValueExpression)
d.Set("pre_match_expression", businessMapping.PreMatchExpression)
d.Set("statement", flattenStatements(businessMapping.Statements))
d.Set("updated_at", businessMapping.UpdatedAt)
d.SetId(strconv.Itoa(businessMapping.Index))
}
return nil
}

// func resourceBusinessMetricUpdate(d *schema.ResourceData, meta interface{}) error {
// client := meta.(*cloudability.Client)
// id, err := strconv.Atoi(d.Id())
// if err != nil {
// return nil
// }
// payload := cloudability.BusinessMapping{
// Kind: "BUSINESS_METRIC",
// Index: d.Get("index").(int),
// Name: d.Get("name").(string),
// NumberFormat: d.Get("number_format").(string),
// PreMatchExpression: d.Get("pre_match_expression").(string),
// DefaultValueExpression: d.Get("default_value_expression").(string),
// DefaultValue: d.Get("default_value").(string),
// Statements: inflateStatements(d.Get("statement").([]interface{})),
// }
// err = client.BusinessMappings().UpdateBusinessMetric(&payload)
// if err != nil {
// return err
// }
// ctx := context.TODO()
// tflog.Info(ctx, fmt.Sprintf("Updating business metric with index: %d", id))
// return resourceBusinessMetricRead(d, meta)
// }

func resourceBusinessMetricDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudability.Client)
id, err := strconv.Atoi(d.Id())
if err != nil {
return err
}
ctx := context.TODO()
tflog.Info(ctx, fmt.Sprintf("Deleting business metric with index: %d", id))
err = client.BusinessMappings().DeleteBusinessMetric(id)
if err != nil {
// Ignore 404 errors (No resource found)
var apiError cloudability.APIError
jsonErr := json.Unmarshal([]byte(err.Error()), &apiError)
if jsonErr == nil && apiError.Error.Status == 404 {
ctx := context.TODO()
tflog.Info(ctx, "resourceBusinessMetricDelete Resource not found. Ignoring")
return nil
}
}
return err
}
133 changes: 133 additions & 0 deletions cloudability/resource_business_metric_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package cloudability

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func TestAccBusinessMetricResource(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: testAccBusinessMetricResourceConfig("A"),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("cloudability_business_metric.test", "id"),
resource.TestCheckResourceAttr("cloudability_business_metric.test", "name", "Cost (Surcharge) A"),
// resource.TestCheckResourceAttr("cloudability_business_metric.test", "default_value", "METRIC['unblended_cost']"),
// resource.TestCheckResourceAttr("cloudability_business_metric.test", "default_value_expression", "METRIC['unblended_cost']"),
),
},
// ImportState testing
{
ResourceName: "cloudability_business_metric.test",
ImportState: true,
ImportStateVerify: true,
// This is not normally necessary, but is here because this
// example code does not have an actual upstream service.
// Once the Read method is able to refresh information from
// the upstream service, this can be removed.
ImportStateVerifyIgnore: []string{"default_value_expression", "default_value"},
},
// Update and Read testing
{
Config: testAccBusinessMetricResourceConfig("B"),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("cloudability_business_metric.test", "name", "Cost (Surcharge) B"),
),
},
// Delete testing automatically occurs in TestCase
},
})
}

func testAccBusinessMetricResourceConfig(name string) string {
return fmt.Sprintf(`
resource "cloudability_business_metric" "test" {
name = "Cost (Surcharge) %s"
number_format = "number"
default_value_expression = "METRIC['unblended_cost']"
statement {
match_expression = "DIMENSION['vendor'] == 'Amazon' || DIMENSION['vendor'] == 'Azure'"
value_expression = "METRIC['unblended_cost'] * 1.15"
}
lifecycle {
ignore_changes = [
default_value_expression,
default_value
]
}
// name = "Cost (Storage Backup)"
// // "kind": "BUSINESS_METRIC"
// number_format = "currency"
// pre_match_expression = "(DIMENSION['vendor'] == 'amazon' && DIMENSION['usage_family'] == 'storage'"
// default_value_expression = "METRIC['unblended_cost']"
// statement {
// match_expression = "DIMENSION['invoice_date'] == '2019-09-01' && DIMENSION['transaction_type'] == 'usage' && DIMENSION['usage_type'] CONTAINS 'snapshot'"
// value_expression = "METRIC['unblended_cost'] * 1.10"
// }
// statement {
// match_expression = "DIMENSION['invoice_date'] == '2019-10-01' && DIMENSION['transaction_type'] == 'usage' && DIMENSION['usage_type'] CONTAINS 'snapshot'"
// value_expression = "METRIC['unblended_cost'] * 1.10"
// }
// statement {
// match_expression = "DIMENSION['invoice_date'] == '2019-11-01' && DIMENSION['transaction_type'] == 'usage' && DIMENSION['usage_type'] CONTAINS 'snapshot'"
// value_expression = "METRIC['unblended_cost'] * 1.10"
// }
}
`, name)
}

// func testAccBusinessMetricMultipleConfig() string {
// return `
// resource "cloudability_business_mapping" "test1" {
// name = "test1"
// default_value = "Unknown1"
// kind = "BUSINESS_DIMENSION"
// statement {
// match_expression = "DIMENSION['vendor'] == 'vendor1_1'"
// value_expression = "'Vendor1_1'"
// }
// statement {
// match_expression = "DIMENSION['vendor'] == 'vendor1_2'"
// value_expression = "'Vendor1_2'"
// }
// }

// resource "cloudability_business_mapping" "test2" {
// name = "test2"
// default_value = "Unknown2"
// kind = "BUSINESS_DIMENSION"
// statement {
// match_expression = "DIMENSION['vendor'] == 'vendor2_1'"
// value_expression = "'Vendor2_1'"
// }
// statement {
// match_expression = "DIMENSION['vendor'] == 'vendor2_2'"
// value_expression = "'Vendor2_2'"
// }
// depends_on = [cloudability_business_mapping.test1]
// }

// resource "cloudability_business_mapping" "test3" {
// name = "test3"
// default_value = "Unknown3"
// kind = "BUSINESS_DIMENSION"
// statement {
// match_expression = "DIMENSION['vendor'] == 'vendor3_1'"
// value_expression = "'Vendor3_1'"
// }
// statement {
// match_expression = "DIMENSION['vendor'] == 'vendor3_2'"
// value_expression = "'Vendor3_2'"
// }
// depends_on = [cloudability_business_mapping.test2]
// }
// `
// }
1 change: 1 addition & 0 deletions docs/resources/business_mapping.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Business Mapping Resource

Business Mapping is an activity that allows you to map your cloud cost and usage to custom dimensions and custom metrics that are important to report on within your organization.
This resource maps to BUSINESS_DIMENSION Business Mapping.

## Example Usage

Expand Down
Loading

0 comments on commit cbff92b

Please sign in to comment.