From a15f182626d0ee5ca50cdd76d0ae2714a492ca37 Mon Sep 17 00:00:00 2001 From: Jason Harley Date: Sat, 31 Aug 2024 13:55:08 -0400 Subject: [PATCH] fix(r/trigger): handle dynamic evaluation schedule --- internal/models/notification_recipients.go | 8 +- internal/models/triggers.go | 41 +++-- internal/provider/notification_recipients.go | 1 - internal/provider/trigger_resource.go | 166 +++++++++++++------ internal/provider/trigger_resource_test.go | 57 +++++++ 5 files changed, 203 insertions(+), 70 deletions(-) diff --git a/internal/models/notification_recipients.go b/internal/models/notification_recipients.go index 7ce83fa7..e6ef1ff0 100644 --- a/internal/models/notification_recipients.go +++ b/internal/models/notification_recipients.go @@ -12,10 +12,6 @@ type NotificationRecipientModel struct { Details types.List `tfsdk:"notification_details"` // NotificationRecipientDetailsModel } -type NotificationRecipientDetailsModel struct { - PDSeverity types.String `tfsdk:"pagerduty_severity"` -} - var NotificationRecipientAttrType = map[string]attr.Type{ "id": types.StringType, "type": types.StringType, @@ -25,6 +21,10 @@ var NotificationRecipientAttrType = map[string]attr.Type{ }}, } +type NotificationRecipientDetailsModel struct { + PDSeverity types.String `tfsdk:"pagerduty_severity"` +} + var NotificationRecipientDetailsAttrType = map[string]attr.Type{ "pagerduty_severity": types.StringType, } diff --git a/internal/models/triggers.go b/internal/models/triggers.go index 5fb1874c..dcd5b955 100644 --- a/internal/models/triggers.go +++ b/internal/models/triggers.go @@ -1,20 +1,23 @@ package models -import "github.com/hashicorp/terraform-plugin-framework/types" +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" +) type TriggerResourceModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Dataset types.String `tfsdk:"dataset"` - Description types.String `tfsdk:"description"` - Disabled types.Bool `tfsdk:"disabled"` - QueryID types.String `tfsdk:"query_id"` - QueryJson types.String `tfsdk:"query_json"` - AlertType types.String `tfsdk:"alert_type"` - Frequency types.Int64 `tfsdk:"frequency"` - Threshold []TriggerThresholdModel `tfsdk:"threshold"` - Recipients types.Set `tfsdk:"recipient"` // NotificationRecipientModel - EvaluationSchedule []TriggerEvaluationScheduleModel `tfsdk:"evaluation_schedule"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Dataset types.String `tfsdk:"dataset"` + Description types.String `tfsdk:"description"` + Disabled types.Bool `tfsdk:"disabled"` + QueryID types.String `tfsdk:"query_id"` + QueryJson types.String `tfsdk:"query_json"` + AlertType types.String `tfsdk:"alert_type"` + Frequency types.Int64 `tfsdk:"frequency"` + Threshold types.List `tfsdk:"threshold"` // TriggerThresholdModel + Recipients types.Set `tfsdk:"recipient"` // NotificationRecipientModel + EvaluationSchedule types.List `tfsdk:"evaluation_schedule"` // TriggerEvaluationScheduleModel } type TriggerThresholdModel struct { @@ -23,8 +26,20 @@ type TriggerThresholdModel struct { ExceededLimit types.Int64 `tfsdk:"exceeded_limit"` } +var TriggerThresholdAttrType = map[string]attr.Type{ + "op": types.StringType, + "value": types.Float64Type, + "exceeded_limit": types.Int64Type, +} + type TriggerEvaluationScheduleModel struct { DaysOfWeek []types.String `tfsdk:"days_of_week"` StartTime types.String `tfsdk:"start_time"` EndTime types.String `tfsdk:"end_time"` } + +var TriggerEvaluationScheduleAttrType = map[string]attr.Type{ + "days_of_week": types.ListType{ElemType: types.StringType}, + "start_time": types.StringType, + "end_time": types.StringType, +} diff --git a/internal/provider/notification_recipients.go b/internal/provider/notification_recipients.go index 90ad7ff4..e48f69a4 100644 --- a/internal/provider/notification_recipients.go +++ b/internal/provider/notification_recipients.go @@ -241,7 +241,6 @@ func notificationRecipientDetailsToList(ctx context.Context, details *client.Not diags.Append(d...) result, d = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: models.NotificationRecipientDetailsAttrType}, []attr.Value{objVal}) diags.Append(d...) - } else { result = types.ListNull(types.ObjectType{AttrTypes: models.NotificationRecipientDetailsAttrType}) } diff --git a/internal/provider/trigger_resource.go b/internal/provider/trigger_resource.go index 124ec645..f7e76aab 100644 --- a/internal/provider/trigger_resource.go +++ b/internal/provider/trigger_resource.go @@ -10,6 +10,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -241,10 +243,13 @@ func (r *triggerResource) Create(ctx context.Context, req resource.CreateRequest Description: plan.Description.ValueString(), Disabled: plan.Disabled.ValueBool(), AlertType: client.TriggerAlertType(plan.AlertType.ValueString()), - Threshold: expandTriggerThreshold(plan.Threshold), + Threshold: expandTriggerThreshold(ctx, plan.Threshold, &resp.Diagnostics), Frequency: int(plan.Frequency.ValueInt64()), Recipients: expandNotificationRecipients(ctx, plan.Recipients, &resp.Diagnostics), - EvaluationSchedule: expandTriggerEvaluationSchedule(plan.EvaluationSchedule), + EvaluationSchedule: expandTriggerEvaluationSchedule(ctx, plan.EvaluationSchedule, &resp.Diagnostics), + } + if resp.Diagnostics.HasError() { + return } specifiedByID := !plan.QueryID.IsNull() @@ -265,7 +270,7 @@ func (r *triggerResource) Create(ctx context.Context, req resource.CreateRequest newTrigger.Query = &q } - if plan.EvaluationSchedule != nil { + if !plan.EvaluationSchedule.IsNull() { newTrigger.EvaluationScheduleType = client.TriggerEvaluationScheduleWindow } @@ -281,9 +286,9 @@ func (r *triggerResource) Create(ctx context.Context, req resource.CreateRequest state.Description = types.StringValue(trigger.Description) state.Disabled = types.BoolValue(trigger.Disabled) state.AlertType = types.StringValue(string(trigger.AlertType)) - state.Threshold = flattenTriggerThreshold(trigger.Threshold) + state.Threshold = flattenTriggerThreshold(ctx, trigger.Threshold, &resp.Diagnostics) state.Frequency = types.Int64Value(int64(trigger.Frequency)) - state.EvaluationSchedule = flattenTriggerEvaluationSchedule(trigger) + state.EvaluationSchedule = flattenTriggerEvaluationSchedule(ctx, trigger.EvaluationSchedule, &resp.Diagnostics) // we created them as authored so to avoid matching type-target or ID we can just use the same value state.Recipients = config.Recipients @@ -343,9 +348,9 @@ func (r *triggerResource) Read(ctx context.Context, req resource.ReadRequest, re state.Description = types.StringValue(trigger.Description) state.Disabled = types.BoolValue(trigger.Disabled) state.AlertType = types.StringValue(string(trigger.AlertType)) - state.Threshold = flattenTriggerThreshold(trigger.Threshold) + state.Threshold = flattenTriggerThreshold(ctx, trigger.Threshold, &resp.Diagnostics) state.Frequency = types.Int64Value(int64(trigger.Frequency)) - state.EvaluationSchedule = flattenTriggerEvaluationSchedule(trigger) + state.EvaluationSchedule = flattenTriggerEvaluationSchedule(ctx, trigger.EvaluationSchedule, &resp.Diagnostics) state.Recipients = reconcileReadNotificationRecipientState(ctx, trigger.Recipients, state.Recipients, &resp.Diagnostics) specifiedByID := !state.QueryID.IsNull() @@ -385,9 +390,12 @@ func (r *triggerResource) Update(ctx context.Context, req resource.UpdateRequest Disabled: plan.Disabled.ValueBool(), AlertType: client.TriggerAlertType(plan.AlertType.ValueString()), Frequency: int(plan.Frequency.ValueInt64()), - Threshold: expandTriggerThreshold(plan.Threshold), + Threshold: expandTriggerThreshold(ctx, plan.Threshold, &resp.Diagnostics), Recipients: expandNotificationRecipients(ctx, plan.Recipients, &resp.Diagnostics), - EvaluationSchedule: expandTriggerEvaluationSchedule(plan.EvaluationSchedule), + EvaluationSchedule: expandTriggerEvaluationSchedule(ctx, plan.EvaluationSchedule, &resp.Diagnostics), + } + if resp.Diagnostics.HasError() { + return } specifiedByID := !plan.QueryID.IsNull() @@ -432,8 +440,8 @@ func (r *triggerResource) Update(ctx context.Context, req resource.UpdateRequest state.Disabled = types.BoolValue(trigger.Disabled) state.AlertType = types.StringValue(string(trigger.AlertType)) state.Frequency = types.Int64Value(int64(trigger.Frequency)) - state.Threshold = flattenTriggerThreshold(trigger.Threshold) - state.EvaluationSchedule = flattenTriggerEvaluationSchedule(trigger) + state.Threshold = flattenTriggerThreshold(ctx, trigger.Threshold, &resp.Diagnostics) + state.EvaluationSchedule = flattenTriggerEvaluationSchedule(ctx, trigger.EvaluationSchedule, &resp.Diagnostics) // we created them as authored so to avoid matching type-target or ID we can just use the same value state.Recipients = config.Recipients @@ -497,11 +505,13 @@ func (r *triggerResource) ImportState(ctx context.Context, req resource.ImportSt } resp.Diagnostics.Append(resp.State.Set(ctx, &models.TriggerResourceModel{ - ID: types.StringValue(id), - Dataset: types.StringValue(dataset), - QueryID: types.StringNull(), - QueryJson: types.StringUnknown(), // favor QueryJSON on import - Recipients: types.SetUnknown(types.ObjectType{AttrTypes: models.NotificationRecipientAttrType}), + ID: types.StringValue(id), + Dataset: types.StringValue(dataset), + QueryID: types.StringNull(), + QueryJson: types.StringUnknown(), // favor QueryJSON on import + Recipients: types.SetUnknown(types.ObjectType{AttrTypes: models.NotificationRecipientAttrType}), + Threshold: types.ListUnknown(types.ObjectType{AttrTypes: models.TriggerThresholdAttrType}), + EvaluationSchedule: types.ListUnknown(types.ObjectType{AttrTypes: models.TriggerEvaluationScheduleAttrType}), })...) } @@ -600,8 +610,18 @@ func (r *triggerResource) ValidateConfig(ctx context.Context, req resource.Valid } } -func expandTriggerThreshold(t []models.TriggerThresholdModel) *client.TriggerThreshold { - if len(t) != 1 { +func expandTriggerThreshold( + ctx context.Context, + l types.List, + diags *diag.Diagnostics, +) *client.TriggerThreshold { + if l.IsNull() || l.IsUnknown() { + return nil + } + + var t []models.TriggerThresholdModel + diags.Append(l.ElementsAs(ctx, &t, false)...) + if diags.HasError() { return nil } @@ -612,48 +632,90 @@ func expandTriggerThreshold(t []models.TriggerThresholdModel) *client.TriggerThr } } -func flattenTriggerThreshold(t *client.TriggerThreshold) []models.TriggerThresholdModel { - return []models.TriggerThresholdModel{{ - Op: types.StringValue(string(t.Op)), - Value: types.Float64Value(t.Value), - ExceededLimit: types.Int64Value(int64(t.ExceededLimit)), - }} +func flattenTriggerThreshold( + ctx context.Context, + t *client.TriggerThreshold, + diags *diag.Diagnostics, +) types.List { + if t == nil { + return types.ListNull(types.ObjectType{AttrTypes: models.TriggerThresholdAttrType}) + } + + thresholdObj, d := types.ObjectValue(models.TriggerThresholdAttrType, map[string]attr.Value{ + "op": types.StringValue(string(t.Op)), + "value": types.Float64Value(t.Value), + "exceeded_limit": types.Int64Value(int64(t.ExceededLimit)), + }) + diags.Append(d...) + + result, d := types.ListValueFrom( + ctx, + types.ObjectType{AttrTypes: models.TriggerThresholdAttrType}, + []attr.Value{thresholdObj}, + ) + diags.Append(d...) + + return result } -func expandTriggerEvaluationSchedule(s []models.TriggerEvaluationScheduleModel) *client.TriggerEvaluationSchedule { - if s != nil { - days := make([]string, len(s[0].DaysOfWeek)) - for i, d := range s[0].DaysOfWeek { - days[i] = d.ValueString() - } +func expandTriggerEvaluationSchedule( + ctx context.Context, + l types.List, + diags *diag.Diagnostics, +) *client.TriggerEvaluationSchedule { + if l.IsNull() || l.IsUnknown() { + return nil + } - return &client.TriggerEvaluationSchedule{ - Window: client.TriggerEvaluationWindow{ - StartTime: s[0].StartTime.ValueString(), - EndTime: s[0].EndTime.ValueString(), - DaysOfWeek: days, - }, - } + var s []models.TriggerEvaluationScheduleModel + diags.Append(l.ElementsAs(ctx, &s, false)...) + if diags.HasError() { + return nil + } + + days := make([]string, len(s[0].DaysOfWeek)) + for i, d := range s[0].DaysOfWeek { + days[i] = d.ValueString() } - return nil + return &client.TriggerEvaluationSchedule{ + Window: client.TriggerEvaluationWindow{ + StartTime: s[0].StartTime.ValueString(), + EndTime: s[0].EndTime.ValueString(), + DaysOfWeek: days, + }, + } } -func flattenTriggerEvaluationSchedule(t *client.Trigger) []models.TriggerEvaluationScheduleModel { - if t.EvaluationScheduleType == client.TriggerEvaluationScheduleWindow { - days := make([]basetypes.StringValue, len(t.EvaluationSchedule.Window.DaysOfWeek)) - for i, d := range t.EvaluationSchedule.Window.DaysOfWeek { - days[i] = types.StringValue(d) - } +func flattenTriggerEvaluationSchedule( + ctx context.Context, + w *client.TriggerEvaluationSchedule, + diags *diag.Diagnostics, +) types.List { + if w == nil { + return types.ListNull(types.ObjectType{AttrTypes: models.TriggerEvaluationScheduleAttrType}) + } - return []models.TriggerEvaluationScheduleModel{ - { - StartTime: types.StringValue(t.EvaluationSchedule.Window.StartTime), - EndTime: types.StringValue(t.EvaluationSchedule.Window.EndTime), - DaysOfWeek: days, - }, - } + days := make([]basetypes.StringValue, len(w.Window.DaysOfWeek)) + for i, d := range w.Window.DaysOfWeek { + days[i] = types.StringValue(d) } + daysObj, d := types.ListValueFrom(ctx, types.StringType, days) + diags.Append(d...) + + scheduleObj, d := types.ObjectValue(models.TriggerEvaluationScheduleAttrType, map[string]attr.Value{ + "days_of_week": daysObj, + "start_time": types.StringValue(w.Window.StartTime), + "end_time": types.StringValue(w.Window.EndTime), + }) + diags.Append(d...) + + result, d := types.ListValueFrom( + ctx, + types.ObjectType{AttrTypes: models.TriggerEvaluationScheduleAttrType}, + []attr.Value{scheduleObj}, + ) + diags.Append(d...) - return nil + return result } diff --git a/internal/provider/trigger_resource_test.go b/internal/provider/trigger_resource_test.go index 97dcb915..d1822750 100644 --- a/internal/provider/trigger_resource_test.go +++ b/internal/provider/trigger_resource_test.go @@ -406,6 +406,63 @@ resource "honeycombio_trigger" "test" { }, }, }) + + t.Run("handles dynamic evaluation schedule block", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: testAccPreCheck(t), + ProtoV5ProviderFactories: testAccProtoV5MuxServerFactory, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` +locals { + business_hour = { + start_time = "14:00" + end_time = "22:00" + days_of_week = [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + ] + } +} + +variable "enable_evaluation_schedule_business_hour" { + type = bool + default = true +} + +data "honeycombio_query_specification" "test" {} + +resource "honeycombio_trigger" "test" { + name = "test" + dataset = "%s" + + query_json = data.honeycombio_query_specification.test.json + + frequency = 1800 + + threshold { + exceeded_limit = 1 + op = ">" + value = "0" + } + + dynamic "evaluation_schedule" { + for_each = var.enable_evaluation_schedule_business_hour ? [true] : [] + + content { + start_time = local.business_hour.start_time + end_time = local.business_hour.end_time + days_of_week = local.business_hour.days_of_week + } + } +}`, testAccDataset()), + }, + }, + }) + }) } func TestAcc_TriggerResourcePagerDutyUnsetSeverity(t *testing.T) {