Repository: honeycombio/terraform-provider-honeycombio
Pull request #351 (2023-09-05 18:45:12)

Add support for Trigger Threshold Exceeded Limit
Triggers now support configuring the number of times the threshold is met before an alert is sent! 🎉 


Patch:
diff --git a/client/trigger.go b/client/trigger.go
index 2e97150..98dd5da 100644
--- a/client/trigger.go
+++ b/client/trigger.go
@@ -79,8 +79,9 @@ type Trigger struct {
 
 // TriggerThreshold represents the threshold of a trigger.
 type TriggerThreshold struct {
-	Op    TriggerThresholdOp `json:"op"`
-	Value float64            `json:"value"`
+	Op            TriggerThresholdOp `json:"op"`
+	Value         float64            `json:"value"`
+	ExceededLimit int                `json:"exceeded_limit,omitempty"`
 }
 
 // TriggerThresholdOp the operator of the trigger threshold.
diff --git a/docs/resources/trigger.md b/docs/resources/trigger.md
index a95890e..9fdba82 100644
--- a/docs/resources/trigger.md
+++ b/docs/resources/trigger.md
@@ -101,15 +101,16 @@ resource "honeycombio_trigger" "example" {
   frequency = 600 // in seconds, 10 minutes
 
   threshold {
-    op    = ">"
-    value = 1000
+    op             = ">"
+    value          = 1000
+    exceeded_limit = 3
   }
 
   recipient {
     id = data.honeycombio_recipient.pd-prod.id
 
     notification_details {
-      pagerduty_severity = "info"
+      pagerduty_severity = "warning"
     }
   }
 
@@ -153,6 +154,7 @@ Each trigger configuration must contain exactly one `threshold` block, which acc
 
 * `op` - (Required) The operator to apply, allowed threshold operators are `>`, `>=`, `<`, and `<=`.
 * `value` - (Required) The value to be used with the operator.
+* `exceeded_limit` - (Optional) The number of times the threshold is met before an alert is sent, must be between 1 and 5. Defaults to `1`.
 
 Each trigger configuration may provide an `evaluation_schedule` block, which accepts the following arguments:
 
diff --git a/internal/helper/modifiers/trigger_threshold.go b/internal/helper/modifiers/trigger_threshold.go
new file mode 100644
index 0000000..882ca27
--- /dev/null
+++ b/internal/helper/modifiers/trigger_threshold.go
@@ -0,0 +1,36 @@
+package modifiers
+
+import (
+	"context"
+
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+type defaultTriggerThresholdExceededLimitModifier struct{}
+
+var _ planmodifier.Int64 = &defaultTriggerThresholdExceededLimitModifier{}
+
+func (m defaultTriggerThresholdExceededLimitModifier) Description(_ context.Context) string {
+	return "Handles the default value for a Trigger's Threshold Exceeded Limits."
+}
+
+func (m defaultTriggerThresholdExceededLimitModifier) MarkdownDescription(ctx context.Context) string {
+	return m.Description(ctx)
+}
+
+func (m defaultTriggerThresholdExceededLimitModifier) PlanModifyInt64(ctx context.Context, req planmodifier.Int64Request, resp *planmodifier.Int64Response) {
+	// Do nothing on resource destroy.
+	if req.Plan.Raw.IsNull() {
+		return
+	}
+
+	if req.ConfigValue.IsNull() {
+		// default value is 1
+		resp.PlanValue = types.Int64Value(1)
+	}
+}
+
+func DefaultTriggerThresholdExceededLimit() planmodifier.Int64 {
+	return defaultTriggerThresholdExceededLimitModifier{}
+}
diff --git a/internal/models/triggers.go b/internal/models/triggers.go
index 9e8bc81..7c052be 100644
--- a/internal/models/triggers.go
+++ b/internal/models/triggers.go
@@ -17,8 +17,9 @@ type TriggerResourceModel struct {
 }
 
 type TriggerThresholdModel struct {
-	Op    types.String  `tfsdk:"op"`
-	Value types.Float64 `tfsdk:"value"`
+	Op            types.String  `tfsdk:"op"`
+	Value         types.Float64 `tfsdk:"value"`
+	ExceededLimit types.Int64   `tfsdk:"exceeded_limit"`
 }
 
 type TriggerEvaluationScheduleModel struct {
diff --git a/internal/provider/trigger_resource.go b/internal/provider/trigger_resource.go
index b496082..e1f712a 100644
--- a/internal/provider/trigger_resource.go
+++ b/internal/provider/trigger_resource.go
@@ -15,6 +15,7 @@ import (
 	"github.com/hashicorp/terraform-plugin-framework/resource/schema"
 	"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
 	"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
 	"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
 	"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
 	"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
@@ -24,6 +25,7 @@ import (
 
 	"github.com/honeycombio/terraform-provider-honeycombio/client"
 	"github.com/honeycombio/terraform-provider-honeycombio/internal/helper"
+	"github.com/honeycombio/terraform-provider-honeycombio/internal/helper/modifiers"
 	"github.com/honeycombio/terraform-provider-honeycombio/internal/helper/validation"
 	"github.com/honeycombio/terraform-provider-honeycombio/internal/models"
 )
@@ -144,6 +146,17 @@ func (r *triggerResource) Schema(_ context.Context, _ resource.SchemaRequest, re
 							Required:    true,
 							Description: "The value to be used with the operator.",
 						},
+						"exceeded_limit": schema.Int64Attribute{
+							Optional:    true,
+							Computed:    true,
+							Description: "The number of times the threshold is met before an alert is sent. Defaults to 1.",
+							Validators:  []validator.Int64{int64validator.Between(1, 5)},
+							PlanModifiers: []planmodifier.Int64{
+								int64planmodifier.UseStateForUnknown(),
+								// Nested blocks currently do not support default values, so we need to set it via a planmodifier
+								modifiers.DefaultTriggerThresholdExceededLimit(),
+							},
+						},
 					},
 				},
 			},
@@ -402,15 +415,17 @@ func expandTriggerThreshold(t []models.TriggerThresholdModel) *client.TriggerThr
 	}
 
 	return &client.TriggerThreshold{
-		Op:    client.TriggerThresholdOp(t[0].Op.ValueString()),
-		Value: t[0].Value.ValueFloat64(),
+		Op:            client.TriggerThresholdOp(t[0].Op.ValueString()),
+		Value:         t[0].Value.ValueFloat64(),
+		ExceededLimit: int(t[0].ExceededLimit.ValueInt64()),
 	}
 }
 
 func flattenTriggerThreshold(t *client.TriggerThreshold) []models.TriggerThresholdModel {
 	return []models.TriggerThresholdModel{{
-		Op:    types.StringValue(string(t.Op)),
-		Value: types.Float64Value(t.Value),
+		Op:            types.StringValue(string(t.Op)),
+		Value:         types.Float64Value(t.Value),
+		ExceededLimit: types.Int64Value(int64(t.ExceededLimit)),
 	}}
 }
 


Test patch:
diff --git a/client/trigger_test.go b/client/trigger_test.go
index 04662b5..169558d 100644
--- a/client/trigger_test.go
+++ b/client/trigger_test.go
@@ -76,6 +76,8 @@ func TestTriggers(t *testing.T) {
 		data.AlertType = TriggerAlertTypeOnChange
 		// set the default evaluation window type
 		data.EvaluationScheduleType = TriggerEvaluationScheduleFrequency
+		// set the default threshold exceeded limit
+		data.Threshold.ExceededLimit = 1
 
 		assert.Equal(t, data, trigger)
 	})
@@ -108,6 +110,8 @@ func TestTriggers(t *testing.T) {
 				EndTime:    "21:00",
 			},
 		}
+		// update the threshold exceeded limit to 3
+		trigger.Threshold.ExceededLimit = 3
 
 		result, err := c.Triggers.Update(ctx, dataset, trigger)
 
diff --git a/internal/provider/trigger_resource_test.go b/internal/provider/trigger_resource_test.go
index fef1de5..b0a20d3 100644
--- a/internal/provider/trigger_resource_test.go
+++ b/internal/provider/trigger_resource_test.go
@@ -26,6 +26,7 @@ func TestAcc_TriggerResource(t *testing.T) {
 					resource.TestCheckResourceAttr("honeycombio_trigger.test", "name", "Test trigger from terraform-provider-honeycombio"),
 					resource.TestCheckResourceAttr("honeycombio_trigger.test", "frequency", "600"),
 					resource.TestCheckResourceAttr("honeycombio_trigger.test", "recipient.#", "2"),
+					resource.TestCheckResourceAttr("honeycombio_trigger.test", "threshold.0.exceeded_limit", "1"),
 					resource.TestCheckResourceAttrPair("honeycombio_trigger.test", "query_id", "honeycombio_query.test", "id"),
 				),
 			},
@@ -115,12 +116,18 @@ func TestAcc_TriggerResourceUpdateRecipientByID(t *testing.T) {
 		ProtoV5ProviderFactories: testAccProtoV5MuxServerFactory,
 		Steps: []resource.TestStep{
 			{
-				Config: testAccConfigTriggerRecipientByID(dataset, testRecipients[0].ID),
-				Check:  resource.TestCheckResourceAttr("honeycombio_trigger.test", "recipient.0.id", testRecipients[0].ID),
+				Config: testAccConfigTriggerRecipientByID(dataset, testRecipients[0].ID, 3),
+				Check: resource.ComposeAggregateTestCheckFunc(
+					resource.TestCheckResourceAttr("honeycombio_trigger.test", "recipient.0.id", testRecipients[0].ID),
+					resource.TestCheckResourceAttr("honeycombio_trigger.test", "threshold.0.exceeded_limit", "3"),
+				),
 			},
 			{
-				Config: testAccConfigTriggerRecipientByID(dataset, testRecipients[1].ID),
-				Check:  resource.TestCheckResourceAttr("honeycombio_trigger.test", "recipient.0.id", testRecipients[1].ID),
+				Config: testAccConfigTriggerRecipientByID(dataset, testRecipients[1].ID, 5),
+				Check: resource.ComposeAggregateTestCheckFunc(
+					resource.TestCheckResourceAttr("honeycombio_trigger.test", "recipient.0.id", testRecipients[1].ID),
+					resource.TestCheckResourceAttr("honeycombio_trigger.test", "threshold.0.exceeded_limit", "5"),
+				),
 			},
 		},
 	})
@@ -453,7 +460,7 @@ resource "honeycombio_trigger" "test" {
 }`, dataset, pdseverity)
 }
 
-func testAccConfigTriggerRecipientByID(dataset, recipientID string) string {
+func testAccConfigTriggerRecipientByID(dataset, recipientID string, exceededLimit int) string {
 	return fmt.Sprintf(`
 data "honeycombio_query_specification" "test" {
   calculation {
@@ -473,15 +480,17 @@ resource "honeycombio_trigger" "test" {
   name    = "Test trigger with changing recipient id"
   dataset = "%[1]s"
   query_id = honeycombio_query.test.id
+
   threshold {
-    op    = ">"
-    value = 100
+    op             = ">"
+    value          = 100
+    exceeded_limit = %[3]d
   }
 
   recipient {
     id = "%[2]s"
   }
-}`, dataset, recipientID)
+}`, dataset, recipientID, exceededLimit)
 }
 
 func testAccEnsureTriggerExists(t *testing.T, name string) resource.TestCheckFunc {