Skip to content

Commit

Permalink
feat(key_transaction): add new resource to manage key transactions (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
vagrawal-newrelic authored Sep 25, 2024
1 parent 6a1d90c commit c237c0b
Show file tree
Hide file tree
Showing 6 changed files with 365 additions and 3 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/newrelic/go-agent/v3 v3.30.0
github.com/newrelic/go-insights v1.0.3
github.com/newrelic/newrelic-client-go/v2 v2.46.0
github.com/newrelic/newrelic-client-go/v2 v2.47.0
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,8 @@ github.com/newrelic/go-agent/v3 v3.30.0 h1:ZXHCT/Cot4iIPwcegCZURuRQOsfmGA6wilW+S
github.com/newrelic/go-agent/v3 v3.30.0/go.mod h1:9utrgxlSryNqRrTvII2XBL+0lpofXbqXApvVWPpbzUg=
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/v2 v2.46.0 h1:J1dKQFRKfQJQQFbP4EQGRs6JsYL20gXJxRmTjXLuB9E=
github.com/newrelic/newrelic-client-go/v2 v2.46.0/go.mod h1:pDFY24/6iIMEbPIdowTRrRn9YYwkXc3j+B+XpTb4oF4=
github.com/newrelic/newrelic-client-go/v2 v2.47.0 h1:4+Q4ynp1lHm2t8OopQ6lmac+dJD0E3AvbHQAuSW7+ws=
github.com/newrelic/newrelic-client-go/v2 v2.47.0/go.mod h1:pDFY24/6iIMEbPIdowTRrRn9YYwkXc3j+B+XpTb4oF4=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
Expand Down
1 change: 1 addition & 0 deletions newrelic/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ func Provider() *schema.Provider {
"newrelic_group": resourceNewRelicGroup(),
"newrelic_infra_alert_condition": resourceNewRelicInfraAlertCondition(),
"newrelic_insights_event": resourceNewRelicInsightsEvent(),
"newrelic_key_transaction": resourceNewRelicKeyTransaction(),
"newrelic_log_parsing_rule": resourceNewRelicLogParsingRule(),
"newrelic_monitor_downtime": resourceNewRelicMonitorDowntime(),
"newrelic_notification_channel": resourceNewRelicNotificationChannel(),
Expand Down
207 changes: 207 additions & 0 deletions newrelic/resource_newrelic_key_transaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package newrelic

import (
"context"
"fmt"
"log"

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

func resourceNewRelicKeyTransaction() *schema.Resource {
return &schema.Resource{
CreateContext: resourceNewRelicKeyTransactionCreate,
ReadContext: resourceNewRelicKeyTransactionRead,
UpdateContext: resourceNewRelicKeyTransactionUpdate,
DeleteContext: resourceNewRelicKeyTransactionDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"apdex_index": {
Type: schema.TypeFloat,
Description: "The acceptable amount of the time spent in the backend before customers get frustrated (Apdex target)",
Required: true,
},
"application_guid": {
Type: schema.TypeString,
Description: "The GUID of the application.",
Required: true,
ForceNew: true,
},
"browser_apdex_target": {
Type: schema.TypeFloat,
Description: "The acceptable amount of time for rendering a page in a browser before customers get frustrated (browser Apdex target).",
Required: true,
},
"metric_name": {
Type: schema.TypeString,
Description: "The name of the metric underlying this key transaction",
Required: true,
ForceNew: true,
},
"name": {
Type: schema.TypeString,
Description: "The name of the key transaction.",
Required: true,
},
"domain": {
Type: schema.TypeString,
Description: "Domain of the entity.",
Required: false,
Computed: true,
},
"type": {
Type: schema.TypeString,
Description: "Type of the entity.",
Required: false,
Computed: true,
},
},
}
}

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

apdexIndex,
applicationGUID,
browserApdexTarget,
metricName,
name := resourceNewRelicKeyTransactionFetchValuesFromConfig(d)

log.Printf("[INFO] Creating New Relic Key Transaction %s", name)
createKeyTransactionResult, err := client.KeyTransaction.KeyTransactionCreate(
apdexIndex,
keytransaction.EntityGUID(applicationGUID),
browserApdexTarget,
metricName,
name,
)

if err != nil {
return diag.FromErr(err)
}
if createKeyTransactionResult == nil {
return diag.FromErr(fmt.Errorf("something went wrong while creating the key transaction"))
}

resourceNewRelicKeyTransactionSetValuesToState(d, *createKeyTransactionResult, keytransaction.KeyTransactionUpdateResult{})

return nil
}

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

guid := d.Id()

resp, err := client.Entities.GetEntityWithContext(ctx, common.EntityGUID(guid))
if err != nil {
return diag.FromErr(err)
}

if resp == nil {
d.SetId("")
return diag.FromErr(fmt.Errorf("no key transaction found with given guid %s", guid))
}

switch (*resp).(type) {
case *entities.KeyTransactionEntity:
entity := (*resp).(*entities.KeyTransactionEntity)
_ = d.Set("apdex_index", entity.ApdexTarget)
_ = d.Set("application_guid", entity.Application.GUID)
_ = d.Set("browser_apdex_target", entity.BrowserApdexTarget)
_ = d.Set("metric_name", entity.MetricName)
_ = d.Set("name", entity.Name)
default:
return diag.FromErr(fmt.Errorf("entity with GUID %s was not a key transaction", guid))
}
return nil
}

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

apdexIndex,
_,
browserApdexTarget,
_,
name := resourceNewRelicKeyTransactionFetchValuesFromConfig(d)

guid := keytransaction.EntityGUID(d.Id())
log.Printf("[INFO] Updating New Relic Key Transaction %s", name)

updateKeyTransactionResult, err := client.KeyTransaction.KeyTransactionUpdate(apdexIndex, browserApdexTarget, guid, name)

if err != nil {

return diag.FromErr(err)
}
if updateKeyTransactionResult == nil {
return diag.FromErr(fmt.Errorf("something went wrong while updating the key transaction"))
}

resourceNewRelicKeyTransactionSetValuesToState(d, keytransaction.KeyTransactionCreateResult{}, *updateKeyTransactionResult)
return nil
}

func resourceNewRelicKeyTransactionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*ProviderConfig).NewClient
keyTransactionGUID := keytransaction.EntityGUID(d.Id())

log.Printf("[INFO] Deleting New Relic Key Transaction %s", d.Id())
_, err := client.KeyTransaction.KeyTransactionDelete(keyTransactionGUID)

if err != nil {
return diag.FromErr(err)
}
return nil
}

func resourceNewRelicKeyTransactionFetchValuesFromConfig(d *schema.ResourceData) (
apdexIndex float64,
applicationGUID string,
browserApdexTarget float64,
metricName string,
name string,
) {
apdexIndex = d.Get("apdex_index").(float64)
applicationGUID = d.Get("application_guid").(string)
browserApdexTarget = d.Get("browser_apdex_target").(float64)
metricName = d.Get("metric_name").(string)
name = d.Get("name").(string)

return apdexIndex, applicationGUID, browserApdexTarget, metricName, name
}

func resourceNewRelicKeyTransactionSetValuesToState(
d *schema.ResourceData,
createKeyTransactionResult keytransaction.KeyTransactionCreateResult,
updateKeyTransactionResult keytransaction.KeyTransactionUpdateResult,
) {
if createKeyTransactionResult.GUID != "" && updateKeyTransactionResult.Name == "" {
d.SetId(string(createKeyTransactionResult.GUID))
_ = d.Set("apdex_index", createKeyTransactionResult.ApdexTarget)
_ = d.Set("application_guid", createKeyTransactionResult.Application.GUID)
_ = d.Set("browser_apdex_target", createKeyTransactionResult.BrowserApdexTarget)
_ = d.Set("metric_name", createKeyTransactionResult.MetricName)
_ = d.Set("name", createKeyTransactionResult.Name)
entity := createKeyTransactionResult.Application.Entity.(*keytransaction.ApmApplicationEntityOutline)
_ = d.Set("domain", entity.Domain)
_ = d.Set("type", entity.Type)
} else if createKeyTransactionResult.GUID == "" && updateKeyTransactionResult.Name != "" {
_ = d.Set("apdex_index", updateKeyTransactionResult.ApdexTarget)
_ = d.Set("application_guid", updateKeyTransactionResult.Application.GUID)
_ = d.Set("browser_apdex_target", updateKeyTransactionResult.BrowserApdexTarget)
_ = d.Set("name", updateKeyTransactionResult.Name)
entity := updateKeyTransactionResult.Application.Entity.(*keytransaction.ApmApplicationEntityOutline)
_ = d.Set("domain", entity.Domain)
_ = d.Set("type", entity.Type)
}
}
103 changes: 103 additions & 0 deletions newrelic/resource_newrelic_key_transaction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//go:build integration
// +build integration

package newrelic

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/newrelic/newrelic-client-go/v2/pkg/common"
"github.com/newrelic/newrelic-client-go/v2/pkg/entities"
)

func TestAccNewRelicKeyTransaction_Basic(t *testing.T) {
randomName := fmt.Sprintf("tf-test-%s", acctest.RandString(5))
resourceName := "newrelic_key_transaction.foo"
resource.ParallelTest(t, resource.TestCase{
Providers: testAccProviders,
Steps: []resource.TestStep{
// Create
{
Config: testAccNewRelicKeyTransactionBasicConfiguration(fmt.Sprintf("%s", randomName)),
Check: resource.ComposeTestCheckFunc(
testAccNewRelicCheckKeyTransactionExists(resourceName),
),
},
// Update
{
Config: testAccNewRelicKeyTransactionBasicConfiguration(fmt.Sprintf("%s-updated", randomName)),
Check: resource.ComposeTestCheckFunc(
testAccNewRelicCheckKeyTransactionExists(resourceName),
),
},
// Import
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
"domain",
"type",
},
},
},
})
}

func TestAccNewRelicKeyTransaction_DuplicateNameError(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
Providers: testAccProviders,
Steps: []resource.TestStep{
// create a key transaction with a name that already exists in the UI
// this is expected to throw an error, as only one key transaction may be created per metric name
{
Config: testAccNewRelicKeyTransactionBasicConfiguration(fmt.Sprintf("%s", "terraform_acceptance_test_key_transaction_donot_delete")),
ExpectError: regexp.MustCompile("\\s*"),
},
},
})
}

func testAccNewRelicKeyTransactionBasicConfiguration(name string) string {
return fmt.Sprintf(`
resource "newrelic_key_transaction" "foo" {
apdex_index = 0.5
application_guid = "MzgwNjUyNnxBUE18QVBQTElDQVRJT058NTUzNDQ4MjAy"
browser_apdex_target = 0.5
metric_name = "test"
name = "%[1]s"
}
`, name)
}

func testAccNewRelicCheckKeyTransactionExists(resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]

if !ok {
return fmt.Errorf("not found: %s", resourceName)
}
if rs.Primary.ID == "" {
return fmt.Errorf("no key transaction ID is set")
}

client := testAccProvider.Meta().(*ProviderConfig).NewClient

found, err := client.Entities.GetEntity(common.EntityGUID(rs.Primary.ID))
if err != nil {
return fmt.Errorf(err.Error())
}

x := (*found).(*entities.KeyTransactionEntity)
if x.GUID != common.EntityGUID(rs.Primary.ID) {
return fmt.Errorf("key transaction not found")
}

return nil
}
}
51 changes: 51 additions & 0 deletions website/docs/r/key_transaction.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
layout: "newrelic"
page_title: "New Relic: newrelic_key_transaction"
sidebar_current: "docs-newrelic-resource-key-transaction"
description: |-
Create a new New Relic Key Transaction.
---

# Resource: newrelic\_key\_transaction

Use this resource to create a new Key Transaction in New Relic.

-> **NOTE:** For more information on Key Transactions, head over to [this page](https://docs.newrelic.com/docs/apm/transactions/key-transactions/introduction-key-transactions/) in New Relic's docs.

## Example Usage

```hcl
resource "newrelic_key_transaction" "foo" {
application_guid = "MzgfNjUyNnxBUE19QVBQTElDQVHJT068NTUfNDT4MjUy"
apdex_index = 0.5
browser_apdex_target = 0.5
metric_name = "WebTransaction/Function/__main__:foo_bar"
name = "Sample Key Transaction"
}
```
## Argument Reference

The following arguments are supported by this resource.

* `application_guid` - (Required) The GUID of the APM Application comprising transactions, of which one would be made a key transaction.
* `metric_name` - (Required) - The name of the underlying metric monitored by the key transaction to be created.
* `name` - (Required) - The name of the key transaction.
* `apdex_index` - (Required) A decimal value, measuring user satisfaction with response times, ranging from 0 (frustrated) to 1 (satisfied).
* `browser_apdex_target` - (Required) A decimal value representing the response time threshold for satisfactory experience (e.g., 0.5 seconds).

-> **NOTE:** It may be noted that the `metric_name` and `application_guid` of a Key Transaction _cannot_ be updated in a key transaction that has already been created; since this is not supported. As a consequence, altering the values of `application_guid` and/or `metric_name` of a `newrelic_key_transaction` resource created (to try updating these values) would result in `terraform plan` prompting a forced destruction and re-creation of the resource.

## Attributes Reference

In addition to all arguments above, the following attributes are exported by this resource.

* `id` - The GUID of the created key transaction.
* `domain` - The domain of the entity monitored by the key transaction.
* `type` - The type of the entity monitored by the key transaction.

## Import
A Key Transaction in New Relic may be imported into Terraform using its GUID specified in the `<id>` field, in the following command.

```bash
$ terraform import newrelic_key_transaction.foo <id>
```

0 comments on commit c237c0b

Please sign in to comment.