Skip to content

Commit c237c0b

Browse files
feat(key_transaction): add new resource to manage key transactions (#2748)
1 parent 6a1d90c commit c237c0b

File tree

6 files changed

+365
-3
lines changed

6 files changed

+365
-3
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/mitchellh/go-homedir v1.1.0
88
github.com/newrelic/go-agent/v3 v3.30.0
99
github.com/newrelic/go-insights v1.0.3
10-
github.com/newrelic/newrelic-client-go/v2 v2.46.0
10+
github.com/newrelic/newrelic-client-go/v2 v2.47.0
1111
github.com/stretchr/testify v1.9.0
1212
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
1313
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,8 @@ github.com/newrelic/go-agent/v3 v3.30.0 h1:ZXHCT/Cot4iIPwcegCZURuRQOsfmGA6wilW+S
270270
github.com/newrelic/go-agent/v3 v3.30.0/go.mod h1:9utrgxlSryNqRrTvII2XBL+0lpofXbqXApvVWPpbzUg=
271271
github.com/newrelic/go-insights v1.0.3 h1:zSNp1CEZnXktzSIEsbHJk8v6ZihdPFP2WsO/fzau3OQ=
272272
github.com/newrelic/go-insights v1.0.3/go.mod h1:A20BoT8TNkqPGX2nS/Z2fYmKl3Cqa3iKZd4whzedCY4=
273-
github.com/newrelic/newrelic-client-go/v2 v2.46.0 h1:J1dKQFRKfQJQQFbP4EQGRs6JsYL20gXJxRmTjXLuB9E=
274-
github.com/newrelic/newrelic-client-go/v2 v2.46.0/go.mod h1:pDFY24/6iIMEbPIdowTRrRn9YYwkXc3j+B+XpTb4oF4=
273+
github.com/newrelic/newrelic-client-go/v2 v2.47.0 h1:4+Q4ynp1lHm2t8OopQ6lmac+dJD0E3AvbHQAuSW7+ws=
274+
github.com/newrelic/newrelic-client-go/v2 v2.47.0/go.mod h1:pDFY24/6iIMEbPIdowTRrRn9YYwkXc3j+B+XpTb4oF4=
275275
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
276276
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
277277
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=

newrelic/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ func Provider() *schema.Provider {
162162
"newrelic_group": resourceNewRelicGroup(),
163163
"newrelic_infra_alert_condition": resourceNewRelicInfraAlertCondition(),
164164
"newrelic_insights_event": resourceNewRelicInsightsEvent(),
165+
"newrelic_key_transaction": resourceNewRelicKeyTransaction(),
165166
"newrelic_log_parsing_rule": resourceNewRelicLogParsingRule(),
166167
"newrelic_monitor_downtime": resourceNewRelicMonitorDowntime(),
167168
"newrelic_notification_channel": resourceNewRelicNotificationChannel(),
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
package newrelic
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
"github.com/newrelic/newrelic-client-go/v2/pkg/common"
11+
"github.com/newrelic/newrelic-client-go/v2/pkg/entities"
12+
"github.com/newrelic/newrelic-client-go/v2/pkg/keytransaction"
13+
)
14+
15+
func resourceNewRelicKeyTransaction() *schema.Resource {
16+
return &schema.Resource{
17+
CreateContext: resourceNewRelicKeyTransactionCreate,
18+
ReadContext: resourceNewRelicKeyTransactionRead,
19+
UpdateContext: resourceNewRelicKeyTransactionUpdate,
20+
DeleteContext: resourceNewRelicKeyTransactionDelete,
21+
Importer: &schema.ResourceImporter{
22+
StateContext: schema.ImportStatePassthroughContext,
23+
},
24+
Schema: map[string]*schema.Schema{
25+
"apdex_index": {
26+
Type: schema.TypeFloat,
27+
Description: "The acceptable amount of the time spent in the backend before customers get frustrated (Apdex target)",
28+
Required: true,
29+
},
30+
"application_guid": {
31+
Type: schema.TypeString,
32+
Description: "The GUID of the application.",
33+
Required: true,
34+
ForceNew: true,
35+
},
36+
"browser_apdex_target": {
37+
Type: schema.TypeFloat,
38+
Description: "The acceptable amount of time for rendering a page in a browser before customers get frustrated (browser Apdex target).",
39+
Required: true,
40+
},
41+
"metric_name": {
42+
Type: schema.TypeString,
43+
Description: "The name of the metric underlying this key transaction",
44+
Required: true,
45+
ForceNew: true,
46+
},
47+
"name": {
48+
Type: schema.TypeString,
49+
Description: "The name of the key transaction.",
50+
Required: true,
51+
},
52+
"domain": {
53+
Type: schema.TypeString,
54+
Description: "Domain of the entity.",
55+
Required: false,
56+
Computed: true,
57+
},
58+
"type": {
59+
Type: schema.TypeString,
60+
Description: "Type of the entity.",
61+
Required: false,
62+
Computed: true,
63+
},
64+
},
65+
}
66+
}
67+
68+
func resourceNewRelicKeyTransactionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
69+
client := meta.(*ProviderConfig).NewClient
70+
71+
apdexIndex,
72+
applicationGUID,
73+
browserApdexTarget,
74+
metricName,
75+
name := resourceNewRelicKeyTransactionFetchValuesFromConfig(d)
76+
77+
log.Printf("[INFO] Creating New Relic Key Transaction %s", name)
78+
createKeyTransactionResult, err := client.KeyTransaction.KeyTransactionCreate(
79+
apdexIndex,
80+
keytransaction.EntityGUID(applicationGUID),
81+
browserApdexTarget,
82+
metricName,
83+
name,
84+
)
85+
86+
if err != nil {
87+
return diag.FromErr(err)
88+
}
89+
if createKeyTransactionResult == nil {
90+
return diag.FromErr(fmt.Errorf("something went wrong while creating the key transaction"))
91+
}
92+
93+
resourceNewRelicKeyTransactionSetValuesToState(d, *createKeyTransactionResult, keytransaction.KeyTransactionUpdateResult{})
94+
95+
return nil
96+
}
97+
98+
func resourceNewRelicKeyTransactionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
99+
providerConfig := meta.(*ProviderConfig)
100+
client := providerConfig.NewClient
101+
102+
guid := d.Id()
103+
104+
resp, err := client.Entities.GetEntityWithContext(ctx, common.EntityGUID(guid))
105+
if err != nil {
106+
return diag.FromErr(err)
107+
}
108+
109+
if resp == nil {
110+
d.SetId("")
111+
return diag.FromErr(fmt.Errorf("no key transaction found with given guid %s", guid))
112+
}
113+
114+
switch (*resp).(type) {
115+
case *entities.KeyTransactionEntity:
116+
entity := (*resp).(*entities.KeyTransactionEntity)
117+
_ = d.Set("apdex_index", entity.ApdexTarget)
118+
_ = d.Set("application_guid", entity.Application.GUID)
119+
_ = d.Set("browser_apdex_target", entity.BrowserApdexTarget)
120+
_ = d.Set("metric_name", entity.MetricName)
121+
_ = d.Set("name", entity.Name)
122+
default:
123+
return diag.FromErr(fmt.Errorf("entity with GUID %s was not a key transaction", guid))
124+
}
125+
return nil
126+
}
127+
128+
func resourceNewRelicKeyTransactionUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
129+
client := meta.(*ProviderConfig).NewClient
130+
131+
apdexIndex,
132+
_,
133+
browserApdexTarget,
134+
_,
135+
name := resourceNewRelicKeyTransactionFetchValuesFromConfig(d)
136+
137+
guid := keytransaction.EntityGUID(d.Id())
138+
log.Printf("[INFO] Updating New Relic Key Transaction %s", name)
139+
140+
updateKeyTransactionResult, err := client.KeyTransaction.KeyTransactionUpdate(apdexIndex, browserApdexTarget, guid, name)
141+
142+
if err != nil {
143+
144+
return diag.FromErr(err)
145+
}
146+
if updateKeyTransactionResult == nil {
147+
return diag.FromErr(fmt.Errorf("something went wrong while updating the key transaction"))
148+
}
149+
150+
resourceNewRelicKeyTransactionSetValuesToState(d, keytransaction.KeyTransactionCreateResult{}, *updateKeyTransactionResult)
151+
return nil
152+
}
153+
154+
func resourceNewRelicKeyTransactionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
155+
client := meta.(*ProviderConfig).NewClient
156+
keyTransactionGUID := keytransaction.EntityGUID(d.Id())
157+
158+
log.Printf("[INFO] Deleting New Relic Key Transaction %s", d.Id())
159+
_, err := client.KeyTransaction.KeyTransactionDelete(keyTransactionGUID)
160+
161+
if err != nil {
162+
return diag.FromErr(err)
163+
}
164+
return nil
165+
}
166+
167+
func resourceNewRelicKeyTransactionFetchValuesFromConfig(d *schema.ResourceData) (
168+
apdexIndex float64,
169+
applicationGUID string,
170+
browserApdexTarget float64,
171+
metricName string,
172+
name string,
173+
) {
174+
apdexIndex = d.Get("apdex_index").(float64)
175+
applicationGUID = d.Get("application_guid").(string)
176+
browserApdexTarget = d.Get("browser_apdex_target").(float64)
177+
metricName = d.Get("metric_name").(string)
178+
name = d.Get("name").(string)
179+
180+
return apdexIndex, applicationGUID, browserApdexTarget, metricName, name
181+
}
182+
183+
func resourceNewRelicKeyTransactionSetValuesToState(
184+
d *schema.ResourceData,
185+
createKeyTransactionResult keytransaction.KeyTransactionCreateResult,
186+
updateKeyTransactionResult keytransaction.KeyTransactionUpdateResult,
187+
) {
188+
if createKeyTransactionResult.GUID != "" && updateKeyTransactionResult.Name == "" {
189+
d.SetId(string(createKeyTransactionResult.GUID))
190+
_ = d.Set("apdex_index", createKeyTransactionResult.ApdexTarget)
191+
_ = d.Set("application_guid", createKeyTransactionResult.Application.GUID)
192+
_ = d.Set("browser_apdex_target", createKeyTransactionResult.BrowserApdexTarget)
193+
_ = d.Set("metric_name", createKeyTransactionResult.MetricName)
194+
_ = d.Set("name", createKeyTransactionResult.Name)
195+
entity := createKeyTransactionResult.Application.Entity.(*keytransaction.ApmApplicationEntityOutline)
196+
_ = d.Set("domain", entity.Domain)
197+
_ = d.Set("type", entity.Type)
198+
} else if createKeyTransactionResult.GUID == "" && updateKeyTransactionResult.Name != "" {
199+
_ = d.Set("apdex_index", updateKeyTransactionResult.ApdexTarget)
200+
_ = d.Set("application_guid", updateKeyTransactionResult.Application.GUID)
201+
_ = d.Set("browser_apdex_target", updateKeyTransactionResult.BrowserApdexTarget)
202+
_ = d.Set("name", updateKeyTransactionResult.Name)
203+
entity := updateKeyTransactionResult.Application.Entity.(*keytransaction.ApmApplicationEntityOutline)
204+
_ = d.Set("domain", entity.Domain)
205+
_ = d.Set("type", entity.Type)
206+
}
207+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//go:build integration
2+
// +build integration
3+
4+
package newrelic
5+
6+
import (
7+
"fmt"
8+
"regexp"
9+
"testing"
10+
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
13+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
14+
"github.com/newrelic/newrelic-client-go/v2/pkg/common"
15+
"github.com/newrelic/newrelic-client-go/v2/pkg/entities"
16+
)
17+
18+
func TestAccNewRelicKeyTransaction_Basic(t *testing.T) {
19+
randomName := fmt.Sprintf("tf-test-%s", acctest.RandString(5))
20+
resourceName := "newrelic_key_transaction.foo"
21+
resource.ParallelTest(t, resource.TestCase{
22+
Providers: testAccProviders,
23+
Steps: []resource.TestStep{
24+
// Create
25+
{
26+
Config: testAccNewRelicKeyTransactionBasicConfiguration(fmt.Sprintf("%s", randomName)),
27+
Check: resource.ComposeTestCheckFunc(
28+
testAccNewRelicCheckKeyTransactionExists(resourceName),
29+
),
30+
},
31+
// Update
32+
{
33+
Config: testAccNewRelicKeyTransactionBasicConfiguration(fmt.Sprintf("%s-updated", randomName)),
34+
Check: resource.ComposeTestCheckFunc(
35+
testAccNewRelicCheckKeyTransactionExists(resourceName),
36+
),
37+
},
38+
// Import
39+
{
40+
ResourceName: resourceName,
41+
ImportState: true,
42+
ImportStateVerify: true,
43+
ImportStateVerifyIgnore: []string{
44+
"domain",
45+
"type",
46+
},
47+
},
48+
},
49+
})
50+
}
51+
52+
func TestAccNewRelicKeyTransaction_DuplicateNameError(t *testing.T) {
53+
resource.ParallelTest(t, resource.TestCase{
54+
Providers: testAccProviders,
55+
Steps: []resource.TestStep{
56+
// create a key transaction with a name that already exists in the UI
57+
// this is expected to throw an error, as only one key transaction may be created per metric name
58+
{
59+
Config: testAccNewRelicKeyTransactionBasicConfiguration(fmt.Sprintf("%s", "terraform_acceptance_test_key_transaction_donot_delete")),
60+
ExpectError: regexp.MustCompile("\\s*"),
61+
},
62+
},
63+
})
64+
}
65+
66+
func testAccNewRelicKeyTransactionBasicConfiguration(name string) string {
67+
return fmt.Sprintf(`
68+
resource "newrelic_key_transaction" "foo" {
69+
apdex_index = 0.5
70+
application_guid = "MzgwNjUyNnxBUE18QVBQTElDQVRJT058NTUzNDQ4MjAy"
71+
browser_apdex_target = 0.5
72+
metric_name = "test"
73+
name = "%[1]s"
74+
}
75+
`, name)
76+
}
77+
78+
func testAccNewRelicCheckKeyTransactionExists(resourceName string) resource.TestCheckFunc {
79+
return func(s *terraform.State) error {
80+
rs, ok := s.RootModule().Resources[resourceName]
81+
82+
if !ok {
83+
return fmt.Errorf("not found: %s", resourceName)
84+
}
85+
if rs.Primary.ID == "" {
86+
return fmt.Errorf("no key transaction ID is set")
87+
}
88+
89+
client := testAccProvider.Meta().(*ProviderConfig).NewClient
90+
91+
found, err := client.Entities.GetEntity(common.EntityGUID(rs.Primary.ID))
92+
if err != nil {
93+
return fmt.Errorf(err.Error())
94+
}
95+
96+
x := (*found).(*entities.KeyTransactionEntity)
97+
if x.GUID != common.EntityGUID(rs.Primary.ID) {
98+
return fmt.Errorf("key transaction not found")
99+
}
100+
101+
return nil
102+
}
103+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
layout: "newrelic"
3+
page_title: "New Relic: newrelic_key_transaction"
4+
sidebar_current: "docs-newrelic-resource-key-transaction"
5+
description: |-
6+
Create a new New Relic Key Transaction.
7+
---
8+
9+
# Resource: newrelic\_key\_transaction
10+
11+
Use this resource to create a new Key Transaction in New Relic.
12+
13+
-> **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.
14+
15+
## Example Usage
16+
17+
```hcl
18+
resource "newrelic_key_transaction" "foo" {
19+
application_guid = "MzgfNjUyNnxBUE19QVBQTElDQVHJT068NTUfNDT4MjUy"
20+
apdex_index = 0.5
21+
browser_apdex_target = 0.5
22+
metric_name = "WebTransaction/Function/__main__:foo_bar"
23+
name = "Sample Key Transaction"
24+
}
25+
```
26+
## Argument Reference
27+
28+
The following arguments are supported by this resource.
29+
30+
* `application_guid` - (Required) The GUID of the APM Application comprising transactions, of which one would be made a key transaction.
31+
* `metric_name` - (Required) - The name of the underlying metric monitored by the key transaction to be created.
32+
* `name` - (Required) - The name of the key transaction.
33+
* `apdex_index` - (Required) A decimal value, measuring user satisfaction with response times, ranging from 0 (frustrated) to 1 (satisfied).
34+
* `browser_apdex_target` - (Required) A decimal value representing the response time threshold for satisfactory experience (e.g., 0.5 seconds).
35+
36+
-> **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.
37+
38+
## Attributes Reference
39+
40+
In addition to all arguments above, the following attributes are exported by this resource.
41+
42+
* `id` - The GUID of the created key transaction.
43+
* `domain` - The domain of the entity monitored by the key transaction.
44+
* `type` - The type of the entity monitored by the key transaction.
45+
46+
## Import
47+
A Key Transaction in New Relic may be imported into Terraform using its GUID specified in the `<id>` field, in the following command.
48+
49+
```bash
50+
$ terraform import newrelic_key_transaction.foo <id>
51+
```

0 commit comments

Comments
 (0)