Skip to content

Commit

Permalink
Merge pull request #1448 from asanuy/feat/service-level-resource
Browse files Browse the repository at this point in the history
feat(servicelevel): Add service level resource
  • Loading branch information
sanderblue authored Oct 1, 2021
2 parents 6ca4df1 + 9a6875d commit 81d3d80
Show file tree
Hide file tree
Showing 6 changed files with 760 additions and 3 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/newrelic/go-agent/v3 v3.15.0
github.com/newrelic/go-insights v1.0.3
github.com/newrelic/newrelic-client-go v0.63.0
github.com/newrelic/newrelic-client-go v0.63.4
github.com/stretchr/testify v1.7.0
golang.org/x/tools v0.0.0-20201028111035-eafbe7b904eb // indirect
google.golang.org/api v0.34.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,8 @@ github.com/newrelic/go-agent/v3 v3.15.0 h1:XKF81YOkkO5cCEtQmguamOVMVmeWnv7X3+mkR
github.com/newrelic/go-agent/v3 v3.15.0/go.mod h1:1A1dssWBwzB7UemzRU6ZVaGDsI+cEn5/bNxI0wiYlIc=
github.com/newrelic/go-insights v1.0.3 h1:zSNp1CEZnXktzSIEsbHJk8v6ZihdPFP2WsO/fzau3OQ=
github.com/newrelic/go-insights v1.0.3/go.mod h1:A20BoT8TNkqPGX2nS/Z2fYmKl3Cqa3iKZd4whzedCY4=
github.com/newrelic/newrelic-client-go v0.63.0 h1:FqM9GPLYeSah9m3iU4oe5Obm8Ty/EN+G4XaNlcrqU8o=
github.com/newrelic/newrelic-client-go v0.63.0/go.mod h1:VXjhsfui0rvhM9cVwnKwlidF8NbXlHZvh63ZKi6fImA=
github.com/newrelic/newrelic-client-go v0.63.4 h1:PU/5n6Zw8JrYPUmagxggdGdzNBhdkEh0IkWlXw6CLfc=
github.com/newrelic/newrelic-client-go v0.63.4/go.mod h1:VXjhsfui0rvhM9cVwnKwlidF8NbXlHZvh63ZKi6fImA=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758=
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
Expand Down
1 change: 1 addition & 0 deletions newrelic/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func Provider() *schema.Provider {
"newrelic_one_dashboard": resourceNewRelicOneDashboard(),
"newrelic_one_dashboard_raw": resourceNewRelicOneDashboardRaw(),
"newrelic_plugins_alert_condition": resourceNewRelicPluginsAlertCondition(),
"newrelic_service_level": resourceNewRelicServiceLevel(),
"newrelic_synthetics_alert_condition": resourceNewRelicSyntheticsAlertCondition(),
"newrelic_synthetics_monitor": resourceNewRelicSyntheticsMonitor(),
"newrelic_synthetics_monitor_script": resourceNewRelicSyntheticsMonitorScript(),
Expand Down
302 changes: 302 additions & 0 deletions newrelic/resource_newrelic_service_level.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
package newrelic

import (
"context"
"fmt"
"log"
"strconv"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/newrelic/newrelic-client-go/pkg/common"
"github.com/newrelic/newrelic-client-go/pkg/errors"
)

func resourceNewRelicServiceLevel() *schema.Resource {
return &schema.Resource{
CreateContext: resourceNewRelicServiceLevelCreate,
ReadContext: resourceNewRelicServiceLevelRead,
UpdateContext: resourceNewRelicServiceLevelUpdate,
DeleteContext: resourceNewRelicServiceLevelDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"guid": {
Type: schema.TypeString,
Required: true,
Description: "",
ValidateFunc: validation.StringIsNotWhiteSpace,
},
"name": {
Type: schema.TypeString,
Required: true,
Description: "",
ValidateFunc: validation.StringIsNotWhiteSpace,
},
"description": {
Type: schema.TypeString,
Optional: true,
Description: "",
},
"events": {
Type: schema.TypeList,
Required: true,
Description: "",
MinItems: 1,
MaxItems: 1,
Elem: eventsSchema(),
},
"objective": {
Type: schema.TypeSet,
Optional: true,
Description: "",
Elem: objectiveSchema(),
},
"sli_id": {
Type: schema.TypeString,
Computed: true,
Description: "",
},
},
}
}

func eventsSchema() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"account_id": {
Type: schema.TypeInt,
Required: true,
Description: "",
ValidateFunc: validation.IntAtLeast(1),
},
"valid_events": {
Type: schema.TypeList,
Required: true,
Description: "",
MinItems: 1,
MaxItems: 1,
Elem: eventsQuerySchema(),
},
"good_events": {
Type: schema.TypeList,
Optional: true,
Description: "",
MinItems: 0,
MaxItems: 1,
Elem: eventsQuerySchema(),
},
"bad_events": {
Type: schema.TypeList,
Optional: true,
Description: "",
MinItems: 0,
MaxItems: 1,
Elem: eventsQuerySchema(),
},
},
}
}

func eventsQuerySchema() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"from": {
Type: schema.TypeString,
Required: true,
Description: "",
ValidateFunc: validation.StringIsNotWhiteSpace,
},
"where": {
Type: schema.TypeString,
Optional: true,
Description: "",
ValidateFunc: validation.StringIsNotWhiteSpace,
},
},
}
}

func objectiveSchema() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Optional: true,
Description: "",
},
"description": {
Type: schema.TypeString,
Optional: true,
Description: "",
},
"target": {
Type: schema.TypeFloat,
Required: true,
Description: "",
},
"time_window": {
Type: schema.TypeList,
Required: true,
Description: "",
MinItems: 1,
MaxItems: 1,
Elem: timeWindowSchema(),
},
},
}
}

func timeWindowSchema() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"rolling": {
Type: schema.TypeList,
Required: true,
Description: "",
MinItems: 1,
MaxItems: 1,
Elem: rollingTimeWindowSchema(),
},
},
}
}

func rollingTimeWindowSchema() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"count": {
Type: schema.TypeInt,
Required: true,
Description: "",
ValidateFunc: intInSlice([]int{1, 7, 14, 28, 30}),
},
"unit": {
Type: schema.TypeString,
Required: true,
Description: "",
ValidateFunc: validation.StringInSlice([]string{"DAY"}, false),
},
},
}
}

func resourceNewRelicServiceLevelCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*ProviderConfig).NewClient
guid := d.Get("guid").(string)
createInput := expandServiceLevelCreateInput(d)

if createInput.Events.GoodEvents == nil && createInput.Events.BadEvents == nil {
return diag.Errorf("err: Defining a new SLI requires a good or bad events query.")
}
if createInput.Events.GoodEvents != nil && createInput.Events.BadEvents != nil {
return diag.Errorf("err: Only a good or bad events query can be defined for an SLI.")
}

log.Printf("[INFO] Creating New Relic One Service Level %s", createInput.Name)

created, err := client.ServiceLevel.ServiceLevelCreateWithContext(ctx, common.EntityGUID(guid), createInput)
if err != nil {
return diag.FromErr(err)
}

identifier := serviceLevelIdentifier{
AccountID: createInput.Events.AccountID,
ID: created.ID,
GUID: guid,
}

d.SetId(identifier.String())
_ = d.Set("sli_id", created.ID)

return diag.FromErr(nil)
}

func resourceNewRelicServiceLevelRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*ProviderConfig).NewClient

identifier, err := parseIdentifier(d.Id())
if err != nil {
return diag.FromErr(err)
}

indicators, err := client.ServiceLevel.GetIndicatorsWithContext(ctx, common.EntityGUID(identifier.GUID))
if err != nil {
if _, ok := err.(*errors.NotFound); ok {
return diag.Errorf("err: SLI with id=%s not found.", d.Id())
}
return diag.FromErr(err)
}

for _, indicator := range *indicators {
if indicator.ID == identifier.ID {
return diag.FromErr(flattenServiceLevelIndicator(indicator, identifier, d))
}
}

return diag.Errorf("err: SLI with id=%s not found.", d.Id())
}

func resourceNewRelicServiceLevelUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*ProviderConfig).NewClient
updateInput := expandServiceLevelUpdateInput(d)

log.Printf("[INFO] Updating New Relic One Service Level %s", d.Id())

identifier, err := parseIdentifier(d.Id())
if err != nil {
return diag.FromErr(err)
}

_, err = client.ServiceLevel.ServiceLevelUpdateWithContext(ctx, identifier.ID, updateInput)
if err != nil {
return diag.FromErr(err)
}

return resourceNewRelicServiceLevelRead(ctx, d, meta)
}

func resourceNewRelicServiceLevelDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*ProviderConfig).NewClient

log.Printf("[INFO] Deleting New Relic One Service Level %s", d.Id())

identifier, err := parseIdentifier(d.Id())
if err != nil {
return diag.FromErr(err)
}

if _, err := client.ServiceLevel.ServiceLevelDeleteWithContext(ctx, identifier.ID); err != nil {
return diag.FromErr(err)
}

return nil
}

type serviceLevelIdentifier struct {
AccountID int
ID string
GUID string
}

func (identifier *serviceLevelIdentifier) String() string {
return fmt.Sprintf("%d:%s:%s", identifier.AccountID, identifier.ID, identifier.GUID)
}

func parseIdentifier(ids string) (*serviceLevelIdentifier, error) {
split := strings.Split(ids, ":")

accountID, err := strconv.ParseInt(split[0], 10, 32)
if err != nil {
return nil, err
}

return &serviceLevelIdentifier{
AccountID: int(accountID),
ID: split[1],
GUID: split[2],
}, nil
}
Loading

0 comments on commit 81d3d80

Please sign in to comment.