diff --git a/internal/mackerel/alert_group_setting.go b/internal/mackerel/alert_group_setting.go index ad7cd245..a41290cf 100644 --- a/internal/mackerel/alert_group_setting.go +++ b/internal/mackerel/alert_group_setting.go @@ -42,7 +42,7 @@ func (ag *AlertGroupSettingModel) Read(ctx context.Context, client *Client) erro return err } - ag.merge(newAg) + *ag = newAg return nil } @@ -66,9 +66,9 @@ func newAlertGroupSetting(ag mackerel.AlertGroupSetting) AlertGroupSettingModel ID: types.StringValue(ag.ID), Name: types.StringValue(ag.Name), Memo: types.StringValue(ag.Memo), - ServiceScopes: ag.ServiceScopes, + ServiceScopes: nilAsEmptySlice(ag.ServiceScopes), RoleScopes: normalizeScopes(ag.RoleScopes), - MonitorScopes: ag.MonitorScopes, + MonitorScopes: nilAsEmptySlice(ag.MonitorScopes), NotificationInterval: types.Int64Value(int64(ag.NotificationInterval)), } } @@ -84,17 +84,3 @@ func (ag AlertGroupSettingModel) mackerelAlertGroupSetting() mackerel.AlertGroup NotificationInterval: uint64(ag.NotificationInterval.ValueInt64()), } } - -func (ag *AlertGroupSettingModel) merge(newAg AlertGroupSettingModel) { - // Distinct null and [] by preserving old state - if len(ag.ServiceScopes) == 0 && len(newAg.ServiceScopes) == 0 { - newAg.ServiceScopes = ag.ServiceScopes - } - if len(ag.RoleScopes) == 0 && len(newAg.RoleScopes) == 0 { - newAg.RoleScopes = ag.RoleScopes - } - if len(ag.MonitorScopes) == 0 && len(newAg.MonitorScopes) == 0 { - newAg.MonitorScopes = ag.MonitorScopes - } - *ag = newAg -} diff --git a/internal/mackerel/alert_group_setting_test.go b/internal/mackerel/alert_group_setting_test.go index 187f5b47..f89f1144 100644 --- a/internal/mackerel/alert_group_setting_test.go +++ b/internal/mackerel/alert_group_setting_test.go @@ -74,42 +74,3 @@ func Test_AlertGroupSetting_conv(t *testing.T) { }) } } - -func Test_AlertGroupSetting_merge(t *testing.T) { - t.Parallel() - - // lhs <- rhs = wants - cases := map[string]struct { - lhs AlertGroupSettingModel - rhs AlertGroupSettingModel - wants AlertGroupSettingModel - }{ - "nil preserving": { - lhs: AlertGroupSettingModel{ - Name: types.StringValue("before"), - }, - rhs: AlertGroupSettingModel{ - Name: types.StringValue("after"), - ServiceScopes: []string{}, - RoleScopes: []string{}, - MonitorScopes: []string{}, - }, - wants: AlertGroupSettingModel{ - Name: types.StringValue("after"), - }, - }, - } - - for name, tt := range cases { - t.Run(name, func(t *testing.T) { - t.Parallel() - - m := tt.lhs - m.merge(tt.rhs) - if diff := cmp.Diff(m, tt.wants); diff != "" { - t.Error(diff) - } - - }) - } -} diff --git a/internal/mackerel/aws_integration.go b/internal/mackerel/aws_integration.go index a23cbd0f..187fc5d4 100644 --- a/internal/mackerel/aws_integration.go +++ b/internal/mackerel/aws_integration.go @@ -116,7 +116,7 @@ func (m *AWSIntegrationModel) Read(_ context.Context, client *Client) error { return err } - m.merge(*integration) + *m = *integration return nil } @@ -157,7 +157,7 @@ func newAWSIntegrationModel(aws mackerel.AWSIntegration) (*AWSIntegrationModel, continue } if len(awsService.IncludedMetrics) != 0 { - return nil, fmt.Errorf("%s: IncludedMetrics is not supported.", name) + return nil, fmt.Errorf("%s: IncludedMetrics is not supported", name) } svcs[name] = AWSIntegrationService{ @@ -240,45 +240,6 @@ func (m *AWSIntegrationModel) updateParam() *mackerel.UpdateAWSIntegrationParam return (*mackerel.UpdateAWSIntegrationParam)(m.createParam()) } -func (m *AWSIntegrationModel) merge(newModel AWSIntegrationModel) { - oldServices := make(map[string]AWSIntegrationService) - m.each(func(name string, service *AWSIntegrationService) *AWSIntegrationService { - if service != nil { - oldServices[name] = *service - } - return service - }) - - newModel.SecretKey = m.SecretKey - newModel.each(func(name string, service *AWSIntegrationService) *AWSIntegrationService { - oldService, ok := oldServices[name] - if !ok { - return service - } - - // If new == nil && old == zero, use old one. - if service == nil { - if !oldService.Enable.ValueBool() && - len(oldService.ExcludedMetrics) == 0 && - oldService.Role.IsNull() && - !oldService.RetireAutomatically.ValueBool() { - return &oldService - } else { - return nil - } - } - - if service.Role.ValueString() == "" && oldService.Role.ValueString() == "" { - service.Role = oldService.Role - } - if len(service.ExcludedMetrics) == 0 && len(oldService.ExcludedMetrics) == 0 { - service.ExcludedMetrics = oldService.ExcludedMetrics - } - return service - }) - *m = newModel -} - type awsServiceEachFunc func(name string, service *AWSIntegrationService) *AWSIntegrationService // Iterates and updates over services by name diff --git a/internal/mackerel/downtime.go b/internal/mackerel/downtime.go index c42f28b3..cddf6f73 100644 --- a/internal/mackerel/downtime.go +++ b/internal/mackerel/downtime.go @@ -70,7 +70,7 @@ func (d *DowntimeModel) Read(_ context.Context, client *Client) error { if err != nil { return err } - d.merge(*newModel) + *d = *newModel return nil } @@ -95,12 +95,12 @@ func newDowntime(d mackerel.Downtime) *DowntimeModel { Memo: types.StringValue(d.Memo), Start: types.Int64Value(d.Start), Duration: types.Int64Value(d.Duration), - ServiceScopes: d.ServiceScopes, - ServiceExcludeScopes: d.ServiceExcludeScopes, - RoleScopes: d.RoleScopes, - RoleExcludeScopes: d.RoleExcludeScopes, - MonitorScopes: d.MonitorScopes, - MonitorExcludeScopes: d.MonitorExcludeScopes, + ServiceScopes: nilAsEmptySlice(d.ServiceScopes), + ServiceExcludeScopes: nilAsEmptySlice(d.ServiceExcludeScopes), + RoleScopes: nilAsEmptySlice(d.RoleScopes), + RoleExcludeScopes: nilAsEmptySlice(d.RoleExcludeScopes), + MonitorScopes: nilAsEmptySlice(d.MonitorScopes), + MonitorExcludeScopes: nilAsEmptySlice(d.MonitorExcludeScopes), } if d.Recurrence != nil { recurrence := DowntimeRecurrence{ @@ -178,29 +178,9 @@ func (d *DowntimeModel) mackerelDowntime() *mackerel.Downtime { return mackerelDowntime } -func (d *DowntimeModel) merge(newModel DowntimeModel) { - // preserve nil - if len(d.Recurrence) == 1 && len(d.Recurrence[0].Weekdays) == 0 && - len(newModel.Recurrence) == 1 && len(newModel.Recurrence[0].Weekdays) == 0 { - newModel.Recurrence[0].Weekdays = d.Recurrence[0].Weekdays - } - if len(d.ServiceScopes) == 0 && len(newModel.ServiceScopes) == 0 { - newModel.ServiceScopes = d.ServiceScopes - } - if len(d.ServiceExcludeScopes) == 0 && len(newModel.ServiceExcludeScopes) == 0 { - newModel.ServiceExcludeScopes = d.ServiceExcludeScopes - } - if len(d.RoleScopes) == 0 && len(newModel.RoleScopes) == 0 { - newModel.RoleScopes = d.RoleScopes - } - if len(d.RoleExcludeScopes) == 0 && len(newModel.RoleExcludeScopes) == 0 { - newModel.RoleExcludeScopes = d.RoleExcludeScopes - } - if len(d.MonitorScopes) == 0 && len(newModel.MonitorScopes) == 0 { - newModel.MonitorScopes = d.MonitorScopes - } - if len(d.MonitorExcludeScopes) == 0 && len(newModel.MonitorExcludeScopes) == 0 { - newModel.MonitorExcludeScopes = d.MonitorExcludeScopes +func nilAsEmptySlice[V any](slice []V) []V { + if slice == nil { + return []V{} } - *d = newModel + return slice } diff --git a/internal/mackerel/downtime_test.go b/internal/mackerel/downtime_test.go index 9792a392..8fe6123b 100644 --- a/internal/mackerel/downtime_test.go +++ b/internal/mackerel/downtime_test.go @@ -57,11 +57,17 @@ func Test_Downtime_ReadDowntime(t *testing.T) { inID: "5ghjb6vgDFN", wants: DowntimeModel{ - ID: types.StringValue("5ghjb6vgDFN"), - Name: types.StringValue("basic"), - Memo: types.StringValue(""), - Start: types.Int64Value(1735707600), - Duration: types.Int64Value(3600), + ID: types.StringValue("5ghjb6vgDFN"), + Name: types.StringValue("basic"), + Memo: types.StringValue(""), + Start: types.Int64Value(1735707600), + Duration: types.Int64Value(3600), + ServiceScopes: []string{}, + ServiceExcludeScopes: []string{}, + RoleScopes: []string{}, + RoleExcludeScopes: []string{}, + MonitorScopes: []string{}, + MonitorExcludeScopes: []string{}, }, }, "no downtime": { @@ -106,17 +112,29 @@ func Test_Downtime_conv(t *testing.T) { }{ "basic": { api: mackerel.Downtime{ - ID: "5ghjb6vgDFN", - Name: "basic", - Start: 1735707600, - Duration: 3600, + ID: "5ghjb6vgDFN", + Name: "basic", + Start: 1735707600, + Duration: 3600, + ServiceScopes: []string{}, + ServiceExcludeScopes: []string{}, + RoleScopes: []string{}, + RoleExcludeScopes: []string{}, + MonitorScopes: []string{}, + MonitorExcludeScopes: []string{}, }, model: DowntimeModel{ - ID: types.StringValue("5ghjb6vgDFN"), - Name: types.StringValue("basic"), - Memo: types.StringValue(""), - Start: types.Int64Value(1735707600), - Duration: types.Int64Value(3600), + ID: types.StringValue("5ghjb6vgDFN"), + Name: types.StringValue("basic"), + Memo: types.StringValue(""), + Start: types.Int64Value(1735707600), + Duration: types.Int64Value(3600), + ServiceScopes: []string{}, + ServiceExcludeScopes: []string{}, + RoleScopes: []string{}, + RoleExcludeScopes: []string{}, + MonitorScopes: []string{}, + MonitorExcludeScopes: []string{}, }, }, "full": { @@ -183,85 +201,3 @@ func Test_Downtime_conv(t *testing.T) { }) } } - -func Test_Downtime_merge(t *testing.T) { - t.Parallel() - - // in <- inNew == wants - cases := map[string]struct { - in DowntimeModel - inNew DowntimeModel - wants DowntimeModel - }{ - "basic": { - in: DowntimeModel{ - ID: types.StringValue("5ghjb6vgDFN"), - Name: types.StringValue("basic"), - Memo: types.StringValue(""), - Start: types.Int64Value(1735707600), - Duration: types.Int64Value(3600), - Recurrence: []DowntimeRecurrence{{ - Type: types.StringValue("weekly"), - Interval: types.Int64Value(2), - Weekdays: nil, - Until: types.Int64Value(1767193199), - }}, - ServiceScopes: nil, - ServiceExcludeScopes: nil, - RoleScopes: nil, - RoleExcludeScopes: nil, - MonitorScopes: nil, - MonitorExcludeScopes: []string{}, - }, - inNew: DowntimeModel{ - ID: types.StringValue("5ghjb6vgDFN"), - Name: types.StringValue("basic"), - Memo: types.StringValue("memo"), // changed - Start: types.Int64Value(1735707600), - Duration: types.Int64Value(3600), - Recurrence: []DowntimeRecurrence{{ - Type: types.StringValue("weekly"), - Interval: types.Int64Value(2), - Weekdays: []string{}, - Until: types.Int64Value(1767193199), - }}, - ServiceScopes: []string{}, - ServiceExcludeScopes: []string{}, - RoleScopes: []string{}, - RoleExcludeScopes: []string{}, - MonitorScopes: []string{}, - MonitorExcludeScopes: nil, - }, - wants: DowntimeModel{ - ID: types.StringValue("5ghjb6vgDFN"), - Name: types.StringValue("basic"), - Memo: types.StringValue("memo"), // changed - Start: types.Int64Value(1735707600), - Duration: types.Int64Value(3600), - Recurrence: []DowntimeRecurrence{{ - Type: types.StringValue("weekly"), - Interval: types.Int64Value(2), - Weekdays: nil, - Until: types.Int64Value(1767193199), - }}, - ServiceScopes: nil, - ServiceExcludeScopes: nil, - RoleScopes: nil, - RoleExcludeScopes: nil, - MonitorScopes: nil, - MonitorExcludeScopes: []string{}, - }, - }, - } - - for name, tt := range cases { - t.Run(name, func(t *testing.T) { - t.Parallel() - - tt.in.merge(tt.inNew) - if diff := cmp.Diff(tt.in, tt.wants); diff != "" { - t.Error(diff) - } - }) - } -} diff --git a/internal/planmodifierutil/nil_relaxed.go b/internal/planmodifierutil/nil_relaxed.go index f5c19d8a..d012216c 100644 --- a/internal/planmodifierutil/nil_relaxed.go +++ b/internal/planmodifierutil/nil_relaxed.go @@ -15,6 +15,10 @@ func NilRelaxedSet() planmodifier.Set { return nilRelaxedModifier{} } +func NilRelaxedList() planmodifier.List { + return nilRelaxedModifier{} +} + type nilRelaxedModifier struct{} const desctiprion = "For compatibility with the states created by SDK provider, Terraform consider nil and zero values to be same." @@ -42,3 +46,11 @@ func (_ nilRelaxedModifier) PlanModifySet(ctx context.Context, req planmodifier. resp.PlanValue = req.StateValue } } + +func (_ nilRelaxedModifier) PlanModifyList(ctx context.Context, req planmodifier.ListRequest, resp *planmodifier.ListResponse) { + if req.PlanValue.IsUnknown() { + resp.PlanValue = types.ListNull(req.PlanValue.ElementType(ctx)) + } else if req.PlanValue.IsNull() && len(req.StateValue.Elements()) == 0 { + resp.PlanValue = req.StateValue + } +} diff --git a/internal/planmodifierutil/nil_relaxed_test.go b/internal/planmodifierutil/nil_relaxed_test.go index 28db30e5..ab1ead9d 100644 --- a/internal/planmodifierutil/nil_relaxed_test.go +++ b/internal/planmodifierutil/nil_relaxed_test.go @@ -112,3 +112,54 @@ func TestNilRelaxedSet(t *testing.T) { }) } } + +func TestNilRelaxedList(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + request planmodifier.ListRequest + expected *planmodifier.ListResponse + }{ + "unknown to null": { + request: planmodifier.ListRequest{ + PlanValue: types.ListUnknown(types.StringType), + }, + expected: &planmodifier.ListResponse{ + PlanValue: types.ListNull(types.StringType), + }, + }, + "no updates when plan is null and state is empty": { + request: planmodifier.ListRequest{ + PlanValue: types.ListNull(types.StringType), + StateValue: types.ListValueMust(types.StringType, []attr.Value{}), + }, + expected: &planmodifier.ListResponse{ + PlanValue: types.ListValueMust(types.StringType, []attr.Value{}), + }, + }, + "passthrough otherwise": { + request: planmodifier.ListRequest{ + PlanValue: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("a")}), + }, + expected: &planmodifier.ListResponse{ + PlanValue: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("a")}), + }, + }, + } + + for name, tt := range cases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + resp := &planmodifier.ListResponse{ + PlanValue: tt.request.PlanValue, + } + + planmodifierutil.NilRelaxedList().PlanModifyList(context.Background(), tt.request, resp) + + if diff := cmp.Diff(tt.expected, resp); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + }) + } +} diff --git a/internal/provider/resource_mackerel_alert_group_setting.go b/internal/provider/resource_mackerel_alert_group_setting.go index 6e08cbca..886e4da4 100644 --- a/internal/provider/resource_mackerel_alert_group_setting.go +++ b/internal/provider/resource_mackerel_alert_group_setting.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/mackerelio-labs/terraform-provider-mackerel/internal/mackerel" + "github.com/mackerelio-labs/terraform-provider-mackerel/internal/planmodifierutil" ) var ( @@ -156,19 +157,25 @@ var schemaAlertGroupSettingResource = schema.Schema{ Description: schemaAlertGroupSettingServiceScopesDesc, ElementType: types.StringType, Optional: true, + Computed: true, Validators: []validator.Set{ setvalidator.ValueStringsAre(mackerel.ServiceNameValidator()), }, + PlanModifiers: []planmodifier.Set{planmodifierutil.NilRelaxedSet()}, }, "role_scopes": schema.SetAttribute{ - Description: schemaAlertGroupSettingRoleScopesDesc, - ElementType: types.StringType, - Optional: true, + Description: schemaAlertGroupSettingRoleScopesDesc, + ElementType: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Set{planmodifierutil.NilRelaxedSet()}, }, "monitor_scopes": schema.SetAttribute{ - Description: schemaAlertGroupSettingMonitorScopesDesc, - ElementType: types.StringType, - Optional: true, + Description: schemaAlertGroupSettingMonitorScopesDesc, + ElementType: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Set{planmodifierutil.NilRelaxedSet()}, }, "notification_interval": schema.Int64Attribute{ Description: schemaAlertGroupSettingNotificationIntervalDesc, diff --git a/internal/provider/resource_mackerel_alert_group_setting_test.go b/internal/provider/resource_mackerel_alert_group_setting_test.go index e6eeb6af..ecb2bc48 100644 --- a/internal/provider/resource_mackerel_alert_group_setting_test.go +++ b/internal/provider/resource_mackerel_alert_group_setting_test.go @@ -5,6 +5,8 @@ import ( "testing" fwresource "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/mackerelio-labs/terraform-provider-mackerel/internal/provider" ) @@ -24,3 +26,57 @@ func Test_MackerelAlertGroupSettingResource_schema(t *testing.T) { t.Fatalf("schema validation diagnostica: %+v", diags) } } + +func TestAccCompat_MackerelAlertGroupSettingResource(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + config func(name string) string + }{ + "minimal": { + config: func(name string) string { + return ` +resource "mackerel_alert_group_setting" "alert_group" { + name = "` + name + `" +}` + }, + }, + "empty": { + config: func(name string) string { + return ` +resource "mackerel_alert_group_setting" "alert_group" { + name = "` + name + `" + service_scopes = [] + role_scopes = [] + monitor_scopes = [] +}` + }, + }, + } + + for name, tt := range cases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + name := acctest.RandomWithPrefix("tf-alert-group-setting-compat") + config := tt.config(name) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { preCheck(t) }, + + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5SDKProviderFactories, + Config: config, + }, + stepNoPlanInFramework(config), + { + ProtoV5ProviderFactories: protoV5SDKProviderFactories, + Config: config, + }, + stepNoPlanInFramework(config), + }, + }) + }) + } +} diff --git a/internal/provider/resource_mackerel_aws_integration.go b/internal/provider/resource_mackerel_aws_integration.go index 5c47be20..df282b8e 100644 --- a/internal/provider/resource_mackerel_aws_integration.go +++ b/internal/provider/resource_mackerel_aws_integration.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/mackerelio-labs/terraform-provider-mackerel/internal/mackerel" + "github.com/mackerelio-labs/terraform-provider-mackerel/internal/planmodifierutil" ) var ( @@ -195,6 +196,10 @@ func schemaAWSIntegrationResource() schema.Schema { Description: schemaAWSIntegrationServiceExcludedMetricsDesc, ElementType: types.StringType, Optional: true, + Computed: true, + PlanModifiers: []planmodifier.List{ + planmodifierutil.NilRelaxedList(), + }, }, }, }, @@ -220,6 +225,10 @@ func schemaAWSIntegrationResource() schema.Schema { Description: schemaAWSIntegrationServiceExcludedMetricsDesc, ElementType: types.StringType, Optional: true, + Computed: true, + PlanModifiers: []planmodifier.List{ + planmodifierutil.NilRelaxedList(), + }, }, "retire_automatically": schema.BoolAttribute{ Description: schemaAWSIntegrationServiceRetireAutomaticallyDesc, @@ -277,8 +286,6 @@ func schemaAWSIntegrationResource() schema.Schema { Description: schemaAWSIntegrationSecretKeyDesc, Optional: true, Sensitive: true, - Computed: true, - Default: stringdefault.StaticString(""), Validators: []validator.String{ // Secret access key cannot be set alone stringvalidator.AlsoRequires(path.MatchRoot("key")), @@ -293,6 +300,7 @@ func schemaAWSIntegrationResource() schema.Schema { "external_id": schema.StringAttribute{ Description: schemaAWSIntegrationExternalIDDesc, Optional: true, + Sensitive: true, Computed: true, Default: stringdefault.StaticString(""), Validators: []validator.String{ diff --git a/internal/provider/resource_mackerel_aws_integration_test.go b/internal/provider/resource_mackerel_aws_integration_test.go index e8593317..6a4fff20 100644 --- a/internal/provider/resource_mackerel_aws_integration_test.go +++ b/internal/provider/resource_mackerel_aws_integration_test.go @@ -2,9 +2,12 @@ package provider_test import ( "context" + "os" "testing" fwresource "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/mackerelio-labs/terraform-provider-mackerel/internal/provider" ) @@ -23,3 +26,76 @@ func Test_MackerelAWSIntegrationResource_schema(t *testing.T) { t.Fatalf("schema validation diagnostics: %+v", diags) } } + +func TestAccCompat_MackerelAWSIntegrationResource(t *testing.T) { + t.Parallel() + + roleARN := os.Getenv("AWS_ROLE_ARN") + if roleARN == "" { + t.Skip("AWS_ROLE_ARN must be set for acceptance tests") + } + externalID := os.Getenv("EXTERNAL_ID") + if externalID == "" { + t.Skip("EXTERNAL_ID must be set for acceptance tests") + } + + cases := map[string]struct { + config func(name string) string + }{ + "basic": { + config: func(name string) string { + return ` +resource "mackerel_service" "service" { + name = "` + name + `-service" +} +resource "mackerel_role" "role" { + service = mackerel_service.service.name + name = "` + name + `-role" +} +resource "mackerel_aws_integration" "aws_integration" { + name = "` + name + `" + role_arn = "` + roleARN + `" + external_id = "` + externalID + `" + region = "ap-northeast-1" + included_tags = "Name:staging-server,Environment:staging" + excluded_tags = "Name:develop-server,Environment:develop" + + ec2 { + enable = true + role = "${mackerel_service.service.name}: ${mackerel_role.role.name}" + } + alb { + enable = true + role = "${mackerel_service.service.name}: ${mackerel_role.role.name}" + } +}` + }, + }, + } + + for testName, tt := range cases { + t.Run(testName, func(t *testing.T) { + t.Parallel() + + name := acctest.RandomWithPrefix("tf-test-compat-aws-integration") + config := tt.config(name) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { preCheck(t) }, + + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5SDKProviderFactories, + Config: config, + }, + stepNoPlanInFramework(config), + { + ProtoV5ProviderFactories: protoV5SDKProviderFactories, + Config: config, + }, + stepNoPlanInFramework(config), + }, + }) + }) + } +} diff --git a/internal/provider/resource_mackerel_downtime.go b/internal/provider/resource_mackerel_downtime.go index aece15c9..e1f7389a 100644 --- a/internal/provider/resource_mackerel_downtime.go +++ b/internal/provider/resource_mackerel_downtime.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/mackerelio-labs/terraform-provider-mackerel/internal/mackerel" + "github.com/mackerelio-labs/terraform-provider-mackerel/internal/planmodifierutil" ) var ( @@ -178,37 +179,49 @@ func schemaDowntimeResource() schema.Schema { Description: schemaDowntimeServiceScopesDesc, ElementType: types.StringType, Optional: true, + Computed: true, Validators: []validator.Set{ setvalidator.ValueStringsAre(mackerel.ServiceNameValidator()), }, + PlanModifiers: []planmodifier.Set{planmodifierutil.NilRelaxedSet()}, }, "service_exclude_scopes": schema.SetAttribute{ Description: schemaDowntimeServiceExcludeScopesDesc, ElementType: types.StringType, Optional: true, + Computed: true, Validators: []validator.Set{ setvalidator.ValueStringsAre(mackerel.ServiceNameValidator()), }, + PlanModifiers: []planmodifier.Set{planmodifierutil.NilRelaxedSet()}, }, "role_scopes": schema.SetAttribute{ - Description: schemaDowntimeRoleScopesDesc, - ElementType: types.StringType, - Optional: true, + Description: schemaDowntimeRoleScopesDesc, + ElementType: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Set{planmodifierutil.NilRelaxedSet()}, }, "role_exclude_scopes": schema.SetAttribute{ - Description: schemaDowntimeRoleExcludeScopesDesc, - ElementType: types.StringType, - Optional: true, + Description: schemaDowntimeRoleExcludeScopesDesc, + ElementType: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Set{planmodifierutil.NilRelaxedSet()}, }, "monitor_scopes": schema.SetAttribute{ - Description: schemaDowntimeMonitorScopesDesc, - ElementType: types.StringType, - Optional: true, + Description: schemaDowntimeMonitorScopesDesc, + ElementType: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Set{planmodifierutil.NilRelaxedSet()}, }, "monitor_exclude_scopes": schema.SetAttribute{ - Description: schemaDowntimeMonitorExcludeScopesDesc, - ElementType: types.StringType, - Optional: true, + Description: schemaDowntimeMonitorExcludeScopesDesc, + ElementType: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Set{planmodifierutil.NilRelaxedSet()}, }, }, Blocks: map[string]schema.Block{ @@ -235,6 +248,7 @@ func schemaDowntimeResource() schema.Schema { MarkdownDescription: schemaDowntimeRecurrence_weekdaysDesc, ElementType: types.StringType, Optional: true, + Computed: true, Validators: []validator.Set{ setvalidator.ValueStringsAre( stringvalidator.OneOf( @@ -243,6 +257,7 @@ func schemaDowntimeResource() schema.Schema { ), ), }, + PlanModifiers: []planmodifier.Set{planmodifierutil.NilRelaxedSet()}, }, "until": schema.Int64Attribute{ Description: schemaDowntimeRecurrence_untilDesc, diff --git a/internal/provider/resource_mackerel_downtime_test.go b/internal/provider/resource_mackerel_downtime_test.go index d265fbd3..c15a38fe 100644 --- a/internal/provider/resource_mackerel_downtime_test.go +++ b/internal/provider/resource_mackerel_downtime_test.go @@ -5,6 +5,8 @@ import ( "testing" fwresource "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/mackerelio-labs/terraform-provider-mackerel/internal/provider" ) @@ -24,3 +26,93 @@ func Test_MackerelDowntimeResource_schema(t *testing.T) { t.Fatalf("schema validation diagnostics: %+v", diags) } } + +func TestAccCompat_MackerelDowntimeResource(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + config func(name string) string + }{ + "undefined": { + config: func(name string) string { + return ` +resource "mackerel_downtime" "foo" { + name = "` + name + `" + start = 2051254800 + duration = 3600 + recurrence { + type = "weekly" + interval = 2 + } +}` + }, + }, + "null": { + config: func(name string) string { + return ` +resource "mackerel_downtime" "foo" { + name = "` + name + `" + start = 2051254800 + duration = 3600 + service_scopes = null + service_exclude_scopes = null + role_scopes = null + role_exclude_scopes = null + monitor_scopes = null + monitor_exclude_scopes = null + recurrence { + type = "weekly" + interval = 2 + weekdays = null + } +}` + }, + }, + "empty": { + config: func(name string) string { + return ` +resource "mackerel_downtime" "foo" { + name = "` + name + `" + start = 2051254800 + duration = 3600 + service_scopes = [] + service_exclude_scopes = [] + role_scopes = [] + role_exclude_scopes = [] + monitor_scopes = [] + monitor_exclude_scopes = [] + recurrence { + type = "weekly" + interval = 2 + weekdays = [] + } +}` + }, + }, + } + + for name, tt := range cases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + name := acctest.RandomWithPrefix("tf-compat-downtime") + config := tt.config(name) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { preCheck(t) }, + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5SDKProviderFactories, + Config: config, + }, + stepNoPlanInFramework(config), + { + ProtoV5ProviderFactories: protoV5SDKProviderFactories, + Config: config, + }, + stepNoPlanInFramework(config), + }, + }) + }) + } +}