From 530948d5f1a752f7e426401728799827d647f406 Mon Sep 17 00:00:00 2001 From: wai-wong-edb <119956756+wai-wong-edb@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:26:14 +0100 Subject: [PATCH] Ww plan modifier tests (#338) * test: witness group tests * test: witness group tests and fix * test: wip refactored code, dg tests, pg config test * test: added more tests --- pkg/plan_modifier/data_group_custom_diff.go | 12 +- .../data_group_custom_diff_test.go | 296 ++++++++++++++++++ pkg/plan_modifier/pg_config.go | 50 +-- pkg/plan_modifier/pg_config_test.go | 128 ++++++++ pkg/plan_modifier/witness_group.go | 21 +- pkg/plan_modifier/witness_group_test.go | 151 +++++++++ pkg/provider/resource_pgd.go | 16 +- 7 files changed, 629 insertions(+), 45 deletions(-) create mode 100644 pkg/plan_modifier/data_group_custom_diff_test.go create mode 100644 pkg/plan_modifier/pg_config_test.go create mode 100644 pkg/plan_modifier/witness_group_test.go diff --git a/pkg/plan_modifier/data_group_custom_diff.go b/pkg/plan_modifier/data_group_custom_diff.go index 9c772c2e..08166132 100644 --- a/pkg/plan_modifier/data_group_custom_diff.go +++ b/pkg/plan_modifier/data_group_custom_diff.go @@ -11,24 +11,24 @@ import ( ) func CustomDataGroupDiffConfig() planmodifier.Set { - return customDataGroupDiffModifier{} + return CustomDataGroupDiffModifier{} } -// customDataGroupModifier implements the plan modifier. -type customDataGroupDiffModifier struct{} +// CustomDataGroupModifier implements the plan modifier. +type CustomDataGroupDiffModifier struct{} // Description returns a human-readable description of the plan modifier. -func (m customDataGroupDiffModifier) Description(_ context.Context) string { +func (m CustomDataGroupDiffModifier) Description(_ context.Context) string { return "Once set, the value of this attribute in state will not change." } // MarkdownDescription returns a markdown description of the plan modifier. -func (m customDataGroupDiffModifier) MarkdownDescription(_ context.Context) string { +func (m CustomDataGroupDiffModifier) MarkdownDescription(_ context.Context) string { return "Once set, the value of this attribute in state will not change." } // PlanModifySet implements the plan modification logic. -func (m customDataGroupDiffModifier) PlanModifySet(ctx context.Context, req planmodifier.SetRequest, resp *planmodifier.SetResponse) { +func (m CustomDataGroupDiffModifier) PlanModifySet(ctx context.Context, req planmodifier.SetRequest, resp *planmodifier.SetResponse) { if req.StateValue.IsNull() { return } diff --git a/pkg/plan_modifier/data_group_custom_diff_test.go b/pkg/plan_modifier/data_group_custom_diff_test.go new file mode 100644 index 00000000..24e181d6 --- /dev/null +++ b/pkg/plan_modifier/data_group_custom_diff_test.go @@ -0,0 +1,296 @@ +package plan_modifier_test + +import ( + "context" + "reflect" + "testing" + + "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/plan_modifier" + "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/provider" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +func Test_customDataGroupDiffModifier_PlanModifySet(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + pgdSchema := provider.PgdSchema(ctx) + + regionType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["region"].(schema.Attribute).GetType().(types.ObjectType).AttributeTypes() + cloudProviderType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["cloud_provider"].(schema.Attribute).GetType().(types.ObjectType).AttributeTypes() + storageAttrType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["storage"].(schema.Attribute).GetType().(types.ObjectType).AttributeTypes() + conditionsElemType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["conditions"].(schema.Attribute).GetType().(types.SetType).ElemType + resizingPvcElemType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["resizing_pvc"].(schema.Attribute).GetType().(types.SetType).ElemType + allowedIpRangesElemType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["allowed_ip_ranges"].(schema.Attribute).GetType().(types.SetType).ElemType + allowedIpRangesElemObjectType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["allowed_ip_ranges"].(schema.Attribute).GetType().(types.SetType).ElemType.(types.ObjectType).AttributeTypes() + clusterArchAttrType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["cluster_architecture"].(schema.Attribute).GetType().(types.ObjectType).AttributeTypes() + instanceTypeType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["instance_type"].(schema.Attribute).GetType().(types.ObjectType).AttributeTypes() + pgTypeType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["pg_type"].(schema.Attribute).GetType().(types.ObjectType).AttributeTypes() + pgVersionType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["pg_version"].(schema.Attribute).GetType().(types.ObjectType).AttributeTypes() + cmwType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["maintenance_window"].(schema.Attribute).GetType().(types.ObjectType).AttributeTypes() + + defaultRegion := map[string]attr.Value{ + "region_id": basetypes.NewStringValue("us-east-1"), + } + + defaultCloudProvider := map[string]attr.Value{ + "cloud_provider_id": basetypes.NewStringValue("aws"), + } + + defaultStorage := map[string]attr.Value{ + "volume_type": basetypes.NewStringValue("gp3"), + "volume_properties": basetypes.NewStringValue("gp3"), + "size": basetypes.NewStringValue("4 Gi"), + "iops": basetypes.NewStringUnknown(), + "throughput": basetypes.NewStringUnknown(), + } + + defaultAllowedIpRange := []attr.Value{ + basetypes.NewObjectValueMust(allowedIpRangesElemObjectType, map[string]attr.Value{ + "cidr_block": basetypes.NewStringValue("127.0.0.1/32"), + "description": basetypes.NewStringValue("test ip 1"), + }), + basetypes.NewObjectValueMust(allowedIpRangesElemObjectType, map[string]attr.Value{ + "cidr_block": basetypes.NewStringValue("192.0.0.1/32"), + "description": basetypes.NewStringValue("test ip 2"), + }), + } + + defaultClusterArch := map[string]attr.Value{ + "cluster_architecture_id": basetypes.NewStringValue("pgd"), + "cluster_architecture_name": basetypes.NewStringUnknown(), + "nodes": basetypes.NewFloat64Value(3), + "witness_nodes": basetypes.NewFloat64Unknown(), + } + + defaultInstanceType := map[string]attr.Value{ + "instance_type_id": basetypes.NewStringValue("aws:m5.large"), + } + + defaultPgType := map[string]attr.Value{ + "pg_type_id": basetypes.NewStringValue("epas"), + } + + defaultPgVersion := map[string]attr.Value{ + "pg_version_id": basetypes.NewStringValue("15"), + } + + defaultCmw := map[string]attr.Value{ + "is_enabled": basetypes.NewBoolValue(true), + "start_day": basetypes.NewFloat64Value(1), + "start_time": basetypes.NewStringValue("03:00"), + } + + defaultBackupRetentionPeriod := "3d" + + defaultDgAttr := map[string]attr.Value{ + "region": basetypes.NewObjectValueMust(regionType, defaultRegion), + "cloud_provider": basetypes.NewObjectValueMust(cloudProviderType, defaultCloudProvider), + "storage": basetypes.NewObjectValueMust(storageAttrType, defaultStorage), + "cluster_name": basetypes.NewStringUnknown(), + "cluster_type": basetypes.NewStringUnknown(), + "conditions": basetypes.NewSetUnknown(conditionsElemType), + "connection_uri": basetypes.NewStringUnknown(), + "created_at": basetypes.NewStringUnknown(), + "group_id": basetypes.NewStringUnknown(), + "logs_url": basetypes.NewStringUnknown(), + "metrics_url": basetypes.NewStringUnknown(), + "phase": basetypes.NewStringUnknown(), + "resizing_pvc": basetypes.NewSetUnknown(resizingPvcElemType), + "allowed_ip_ranges": basetypes.NewSetValueMust(allowedIpRangesElemType, defaultAllowedIpRange), + "backup_retention_period": basetypes.NewStringValue(defaultBackupRetentionPeriod), + "cluster_architecture": basetypes.NewObjectValueMust(clusterArchAttrType, defaultClusterArch), + "csp_auth": basetypes.NewBoolValue(false), + "instance_type": basetypes.NewObjectValueMust(instanceTypeType, defaultInstanceType), + "pg_type": basetypes.NewObjectValueMust(pgTypeType, defaultPgType), + "pg_version": basetypes.NewObjectValueMust(pgVersionType, defaultPgVersion), + "private_networking": basetypes.NewBoolValue(false), + "maintenance_window": basetypes.NewObjectValueMust(cmwType, defaultCmw), + } + + defaultDgAttrTypes := map[string]attr.Type{ + "region": defaultDgAttr["region"].Type(ctx), + "cloud_provider": defaultDgAttr["cloud_provider"].Type(ctx), + "storage": defaultDgAttr["storage"].Type(ctx), + "cluster_name": defaultDgAttr["cluster_name"].Type(ctx), + "cluster_type": defaultDgAttr["cluster_type"].Type(ctx), + "conditions": defaultDgAttr["conditions"].Type(ctx), + "connection_uri": defaultDgAttr["connection_uri"].Type(ctx), + "created_at": defaultDgAttr["created_at"].Type(ctx), + "group_id": defaultDgAttr["group_id"].Type(ctx), + "logs_url": defaultDgAttr["logs_url"].Type(ctx), + "metrics_url": defaultDgAttr["metrics_url"].Type(ctx), + "phase": defaultDgAttr["phase"].Type(ctx), + "resizing_pvc": defaultDgAttr["resizing_pvc"].Type(ctx), + "allowed_ip_ranges": defaultDgAttr["allowed_ip_ranges"].Type(ctx), + "backup_retention_period": defaultDgAttr["backup_retention_period"].Type(ctx), + "cluster_architecture": defaultDgAttr["cluster_architecture"].Type(ctx), + "csp_auth": defaultDgAttr["csp_auth"].Type(ctx), + "instance_type": defaultDgAttr["instance_type"].Type(ctx), + "pg_type": defaultDgAttr["pg_type"].Type(ctx), + "pg_version": defaultDgAttr["pg_version"].Type(ctx), + "private_networking": defaultDgAttr["private_networking"].Type(ctx), + "maintenance_window": defaultDgAttr["maintenance_window"].Type(ctx), + } + + defaultDgObject := basetypes.NewObjectValueMust(defaultDgAttrTypes, defaultDgAttr) + defaultDgObjects := []attr.Value{} + defaultDgObjects = append(defaultDgObjects, defaultDgObject) + defaultDgSet := basetypes.NewSetValueMust(defaultDgObject.Type(ctx), defaultDgObjects) + + addGroupObject := map[string]attr.Value{ + "region": basetypes.NewObjectValueMust(regionType, + map[string]attr.Value{ + "region_id": basetypes.NewStringValue("us-east-2"), + }, + ), + "cloud_provider": basetypes.NewObjectValueMust(cloudProviderType, defaultCloudProvider), + "storage": basetypes.NewObjectValueMust(storageAttrType, defaultStorage), + "cluster_name": basetypes.NewStringUnknown(), + "cluster_type": basetypes.NewStringUnknown(), + "conditions": basetypes.NewSetUnknown(conditionsElemType), + "connection_uri": basetypes.NewStringUnknown(), + "created_at": basetypes.NewStringUnknown(), + "group_id": basetypes.NewStringUnknown(), + "logs_url": basetypes.NewStringUnknown(), + "metrics_url": basetypes.NewStringUnknown(), + "phase": basetypes.NewStringUnknown(), + "resizing_pvc": basetypes.NewSetUnknown(resizingPvcElemType), + "allowed_ip_ranges": basetypes.NewSetValueMust(allowedIpRangesElemType, defaultAllowedIpRange), + "backup_retention_period": basetypes.NewStringValue(defaultBackupRetentionPeriod), + "cluster_architecture": basetypes.NewObjectValueMust(clusterArchAttrType, defaultClusterArch), + "csp_auth": basetypes.NewBoolValue(false), + "instance_type": basetypes.NewObjectValueMust(instanceTypeType, defaultInstanceType), + "pg_type": basetypes.NewObjectValueMust(pgTypeType, defaultPgType), + "pg_version": basetypes.NewObjectValueMust(pgVersionType, defaultPgVersion), + "private_networking": basetypes.NewBoolValue(false), + "maintenance_window": basetypes.NewObjectValueMust(cmwType, + map[string]attr.Value{ + "is_enabled": basetypes.NewBoolValue(true), + "start_day": basetypes.NewFloat64Value(2), + "start_time": basetypes.NewStringValue("06:00"), + }, + ), + } + + updateObjectAttr := map[string]attr.Value{} + + for k, v := range defaultDgAttr { + updateObjectAttr[k] = v + } + + updateObjectAttr["allowed_ip_ranges"] = basetypes.NewSetValueMust(allowedIpRangesElemType, []attr.Value{ + basetypes.NewObjectValueMust(allowedIpRangesElemObjectType, map[string]attr.Value{ + "cidr_block": basetypes.NewStringValue("168.0.0.1/32"), + "description": basetypes.NewStringValue("updated"), + }), + }) + updateObjectAttr["backup_retention_period"] = basetypes.NewStringValue("5d") + updateObjectAttr["cluster_architecture"] = basetypes.NewObjectValueMust(clusterArchAttrType, map[string]attr.Value{ + "cluster_architecture_id": basetypes.NewStringValue("pgd"), + "cluster_architecture_name": basetypes.NewStringUnknown(), + "nodes": basetypes.NewFloat64Value(1), + "witness_nodes": basetypes.NewFloat64Unknown(), + }) + + updateObject := basetypes.NewObjectValueMust(defaultDgAttrTypes, updateObjectAttr) + updateObjects := []attr.Value{} + updateObjects = append(updateObjects, updateObject) + updateSet := basetypes.NewSetValueMust(defaultDgObject.Type(ctx), updateObjects) + + type args struct { + ctx context.Context + req planmodifier.SetRequest + resp *planmodifier.SetResponse + } + tests := []struct { + name string + m plan_modifier.CustomDataGroupDiffModifier + args args + expectedWarningsCount int + expectedWarningSummary []string + expectedPlanElements []attr.Value + }{ + { + name: "Add dg expected success", + args: args{ + req: planmodifier.SetRequest{ + StateValue: defaultDgSet, + }, + resp: &planmodifier.SetResponse{ + PlanValue: basetypes.NewSetValueMust(defaultDgObject.Type(ctx), + append(defaultDgObjects, basetypes.NewObjectValueMust(defaultDgAttrTypes, addGroupObject)), + ), + }, + }, + expectedWarningsCount: 1, + expectedWarningSummary: []string{"Adding new data group"}, + expectedPlanElements: append(defaultDgObjects, basetypes.NewObjectValueMust(defaultDgAttrTypes, + addGroupObject, + )), + }, + { + name: "Remove dg expected success", + args: args{ + req: planmodifier.SetRequest{ + StateValue: basetypes.NewSetValueMust(defaultDgObject.Type(ctx), + append(defaultDgObjects, basetypes.NewObjectValueMust(defaultDgAttrTypes, addGroupObject)), + ), + }, + resp: &planmodifier.SetResponse{ + PlanValue: defaultDgSet, + }, + }, + expectedWarningsCount: 1, + expectedWarningSummary: []string{"Removing data group"}, + expectedPlanElements: defaultDgObjects, + }, + { + name: "Update object expected success", + args: args{ + req: planmodifier.SetRequest{ + StateValue: defaultDgSet, + }, + resp: &planmodifier.SetResponse{ + PlanValue: updateSet, + }, + }, + expectedWarningsCount: 3, + expectedWarningSummary: []string{ + "Allowed IP ranges changed", + "Backup retention changed", + "Cluster architecture changed", + }, + expectedPlanElements: updateObjects, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + tt.m.PlanModifySet(tt.args.ctx, tt.args.req, tt.args.resp) + + if tt.args.resp.Diagnostics.WarningsCount() != tt.expectedWarningsCount { + t.Fatalf("expected warning count: %v, got: %v", tt.expectedWarningsCount, tt.args.resp.Diagnostics.WarningsCount()) + } + + if tt.args.resp.Diagnostics.WarningsCount() != 0 { + for k, v := range tt.args.resp.Diagnostics.Warnings() { + if tt.expectedWarningSummary[k] != v.Summary() { + t.Fatalf("expected warning summary: %v, got: %v", tt.expectedWarningSummary[k], v.Summary()) + } + } + } + + if !reflect.DeepEqual(tt.expectedPlanElements, tt.args.resp.PlanValue.Elements()) { + t.Fatalf("expected plan elements: %v, got: %v", tt.expectedPlanElements, tt.args.resp.PlanValue.Elements()) + } + }) + } +} diff --git a/pkg/plan_modifier/pg_config.go b/pkg/plan_modifier/pg_config.go index 4b4753e5..5704ed35 100644 --- a/pkg/plan_modifier/pg_config.go +++ b/pkg/plan_modifier/pg_config.go @@ -11,40 +11,44 @@ import ( ) func CustomPGConfig() planmodifier.Set { - return customPGConfigModifier{} + return CustomPGConfigModifier{} } -// customPGConfigModifier implements the plan modifier. -type customPGConfigModifier struct{} +// CustomPGConfigModifier implements the plan modifier. +type CustomPGConfigModifier struct{} // Description returns a human-readable description of the plan modifier. -func (m customPGConfigModifier) Description(_ context.Context) string { +func (m CustomPGConfigModifier) Description(_ context.Context) string { return "Once set, the value of this attribute in state will not change." } // MarkdownDescription returns a markdown description of the plan modifier. -func (m customPGConfigModifier) MarkdownDescription(_ context.Context) string { +func (m CustomPGConfigModifier) MarkdownDescription(_ context.Context) string { return "Once set, the value of this attribute in state will not change." } -// PlanModifySet implements the plan modification logic. -func (m customPGConfigModifier) PlanModifySet(ctx context.Context, req planmodifier.SetRequest, resp *planmodifier.SetResponse) { - defaults := map[string]string{ - "autovacuum_max_workers": "5", - "autovacuum_vacuum_cost_limit": "3000", - "checkpoint_completion_target": "0.9", - "checkpoint_timeout": "15min", - "cpu_tuple_cost": "0.03", - "effective_cache_size": "0.75 * ram", - "maintenance_work_mem": "(0.15 * (ram - shared_buffers) / autovacuum_max_workers) > 1GB ? 1GB : (0.15 * (ram - shared_buffers) / autovacuum_max_workers)", - "random_page_cost": "1.1", - "shared_buffers": "((0.25 * ram) > 80GB) ? 80GB : (0.25 * ram)", - "tcp_keepalives_idle": "120", - "tcp_keepalives_interval": "30", - "wal_buffers": "64MB", - "wal_compression": "on", - } +var pgConfigDefaults map[string]string = map[string]string{ + "autovacuum_max_workers": "5", + "autovacuum_vacuum_cost_limit": "3000", + "checkpoint_completion_target": "0.9", + "checkpoint_timeout": "15min", + "cpu_tuple_cost": "0.03", + "effective_cache_size": "0.75 * ram", + "maintenance_work_mem": "(0.15 * (ram - shared_buffers) / autovacuum_max_workers) > 1GB ? 1GB : (0.15 * (ram - shared_buffers) / autovacuum_max_workers)", + "random_page_cost": "1.1", + "shared_buffers": "((0.25 * ram) > 80GB) ? 80GB : (0.25 * ram)", + "tcp_keepalives_idle": "120", + "tcp_keepalives_interval": "30", + "wal_buffers": "64MB", + "wal_compression": "on", +} +func PgConfigDefaults() map[string]string { + return pgConfigDefaults +} + +// PlanModifySet implements the plan modification logic. +func (m CustomPGConfigModifier) PlanModifySet(ctx context.Context, req planmodifier.SetRequest, resp *planmodifier.SetResponse) { elementTypeAttrTypes := req.StateValue.ElementType(ctx).(basetypes.ObjectType).AttrTypes setOfObjects := resp.PlanValue.Elements() @@ -52,7 +56,7 @@ func (m customPGConfigModifier) PlanModifySet(ctx context.Context, req planmodif // This is independent from what is in the terraform state. // The only source of truth that matters is the plan. // If the default values that are added to the plan already exists in the terraform state, terraform will not show any drift - for k, v := range defaults { + for k, v := range PgConfigDefaults() { if !pgConfigNameExists(resp.PlanValue.Elements(), k) { defaultAttrs := map[string]attr.Value{"name": basetypes.NewStringValue(k), "value": basetypes.NewStringValue(v)} defaultObjectValue := basetypes.NewObjectValueMust(elementTypeAttrTypes, defaultAttrs) diff --git a/pkg/plan_modifier/pg_config_test.go b/pkg/plan_modifier/pg_config_test.go new file mode 100644 index 00000000..b9ec0477 --- /dev/null +++ b/pkg/plan_modifier/pg_config_test.go @@ -0,0 +1,128 @@ +package plan_modifier_test + +import ( + "context" + "testing" + + "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/plan_modifier" + "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/provider" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +func Test_customPGConfigModifier_PlanModifySet(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + pgdSchema := provider.PgdSchema(ctx) + pgConfigElemType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["pg_config"].(schema.Attribute).GetType().(types.SetType).ElemType + pgConfigObjectType := pgdSchema.Attributes["data_groups"].(schema.NestedAttribute).GetNestedObject().GetAttributes()["pg_config"].(schema.Attribute).GetType().(types.SetType).ElemType.(types.ObjectType).AttributeTypes() + + defaultPgConfigDefaultsObjects := []attr.Value{} + + for k, v := range plan_modifier.PgConfigDefaults() { + defaultPgConfigDefaultsObjects = append(defaultPgConfigDefaultsObjects, + basetypes.NewObjectValueMust(pgConfigObjectType, map[string]attr.Value{ + "name": basetypes.NewStringValue(k), + "value": basetypes.NewStringValue(v), + }), + ) + } + + userCustomPgConfigValues := []attr.Value([]attr.Value(append(defaultPgConfigDefaultsObjects, + basetypes.NewObjectValueMust(pgConfigObjectType, map[string]attr.Value{ + "name": basetypes.NewStringValue("test name"), + "value": basetypes.NewStringValue("test value"), + }), + ))) + + // defaultPgConfigSet := basetypes.NewSetValueMust(pgConfigElemType, defaultPgConfigDefaultsObjects) + + type args struct { + ctx context.Context + req planmodifier.SetRequest + resp *planmodifier.SetResponse + } + tests := []struct { + name string + m plan_modifier.CustomPGConfigModifier + args args + expectedWarningsCount int + expectedWarningSummary []string + expectedPlanElements []attr.Value + }{ + { + name: "Use defaults only expected success", + args: args{ + req: planmodifier.SetRequest{ + StateValue: basetypes.NewSetNull(pgConfigElemType), + }, + resp: &planmodifier.SetResponse{ + PlanValue: basetypes.NewSetNull(pgConfigElemType), + }, + }, + expectedWarningsCount: 1, + expectedWarningSummary: []string{"PG config changed"}, + expectedPlanElements: defaultPgConfigDefaultsObjects, + }, + { + name: "Add user custom pg confg value on create expected success", + args: args{ + req: planmodifier.SetRequest{ + StateValue: basetypes.NewSetNull(pgConfigElemType), + }, + resp: &planmodifier.SetResponse{ + PlanValue: basetypes.NewSetValueMust(pgConfigElemType, userCustomPgConfigValues), + }, + }, + expectedWarningsCount: 1, + expectedWarningSummary: []string{"PG config changed"}, + expectedPlanElements: userCustomPgConfigValues, + }, + { + name: "Add user custom pg confg value on update expected success", + args: args{ + req: planmodifier.SetRequest{ + StateValue: basetypes.NewSetValueMust(pgConfigElemType, defaultPgConfigDefaultsObjects), + }, + resp: &planmodifier.SetResponse{ + PlanValue: basetypes.NewSetValueMust(pgConfigElemType, userCustomPgConfigValues), + }, + }, + expectedWarningsCount: 1, + expectedWarningSummary: []string{"PG config changed"}, + expectedPlanElements: userCustomPgConfigValues, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + tt.m.PlanModifySet(tt.args.ctx, tt.args.req, tt.args.resp) + + if tt.args.resp.Diagnostics.WarningsCount() != tt.expectedWarningsCount { + t.Fatalf("expected warning count: %v, got: %v", tt.expectedWarningsCount, tt.args.resp.Diagnostics.WarningsCount()) + } + + if tt.args.resp.Diagnostics.WarningsCount() != 0 { + for k, v := range tt.args.resp.Diagnostics.Warnings() { + if tt.expectedWarningSummary[k] != v.Summary() { + t.Fatalf("expected warning summary: %v, got: %v", tt.expectedWarningSummary[k], v.Summary()) + } + } + } + + expectedPlanSet := basetypes.NewSetValueMust(pgConfigElemType, tt.expectedPlanElements) + respPlanValueSet := basetypes.NewSetValueMust(pgConfigElemType, tt.args.resp.PlanValue.Elements()) + + if !expectedPlanSet.Equal(respPlanValueSet) { + t.Fatalf("expected plan elements: %v, got: %v", expectedPlanSet, respPlanValueSet) + } + }) + } +} diff --git a/pkg/plan_modifier/witness_group.go b/pkg/plan_modifier/witness_group.go index d72953ee..21571458 100644 --- a/pkg/plan_modifier/witness_group.go +++ b/pkg/plan_modifier/witness_group.go @@ -10,24 +10,24 @@ import ( ) func CustomWitnessGroupDiffConfig() planmodifier.Set { - return customWitnessGroupDiffModifier{} + return CustomWitnessGroupDiffModifier{} } -// customWitnessGroupModifier implements the plan modifier. -type customWitnessGroupDiffModifier struct{} +// CustomWitnessGroupModifier implements the plan modifier. +type CustomWitnessGroupDiffModifier struct{} // Description returns a human-readable description of the plan modifier. -func (m customWitnessGroupDiffModifier) Description(_ context.Context) string { +func (m CustomWitnessGroupDiffModifier) Description(_ context.Context) string { return "Once set, the value of this attribute in state will not change." } // MarkdownDescription returns a markdown description of the plan modifier. -func (m customWitnessGroupDiffModifier) MarkdownDescription(_ context.Context) string { +func (m CustomWitnessGroupDiffModifier) MarkdownDescription(_ context.Context) string { return "Once set, the value of this attribute in state will not change." } // PlanModifySet implements the plan modification logic. -func (m customWitnessGroupDiffModifier) PlanModifySet(ctx context.Context, req planmodifier.SetRequest, resp *planmodifier.SetResponse) { +func (m CustomWitnessGroupDiffModifier) PlanModifySet(ctx context.Context, req planmodifier.SetRequest, resp *planmodifier.SetResponse) { if req.StateValue.IsNull() { return } @@ -69,11 +69,12 @@ func (m customWitnessGroupDiffModifier) PlanModifySet(ctx context.Context, req p } // validations - for _, pWg := range planWgs { - for _, sWg := range stateWgs { + for pk, pWg := range planWgs { + for sk, sWg := range stateWgs { planRegion := pWg.(basetypes.ObjectValue).Attributes()["region"] stateRegion := sWg.(basetypes.ObjectValue).Attributes()["region"] - if !stateRegion.Equal(planRegion) { + + if !stateRegion.Equal(planRegion) && pk == sk { resp.Diagnostics.AddError("Witness group region cannot be changed", fmt.Sprintf("Witness group region cannot be changed. Witness group region changed from expected value %v to %v in config", stateRegion, @@ -84,7 +85,7 @@ func (m customWitnessGroupDiffModifier) PlanModifySet(ctx context.Context, req p planCloudProvider := pWg.(basetypes.ObjectValue).Attributes()["cloud_provider"] stateCloudProvider := sWg.(basetypes.ObjectValue).Attributes()["cloud_provider"] - if !planCloudProvider.Equal(stateCloudProvider) { + if !planCloudProvider.Equal(stateCloudProvider) && pk == sk { resp.Diagnostics.AddError("Witness group cloud provider cannot be changed", fmt.Sprintf("witness group cloud provider cannot be changed. witness group cloud provider changed from expected value: %v to %v in config", stateCloudProvider, diff --git a/pkg/plan_modifier/witness_group_test.go b/pkg/plan_modifier/witness_group_test.go new file mode 100644 index 00000000..bff7a874 --- /dev/null +++ b/pkg/plan_modifier/witness_group_test.go @@ -0,0 +1,151 @@ +package plan_modifier_test + +import ( + "context" + "reflect" + "testing" + + "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/plan_modifier" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +func Test_customWitnessGroupDiffModifier_PlanModifySet(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + defaultWgAttr := map[string]attr.Value{ + "region": basetypes.NewObjectValueMust( + map[string]attr.Type{ + "region_id": basetypes.StringType{}, + }, + map[string]attr.Value{ + "region_id": basetypes.NewStringValue("us-east-1"), + }, + ), + "cloud_provider": basetypes.NewObjectValueMust( + map[string]attr.Type{ + "cloud_provider_id": basetypes.StringType{}, + }, + map[string]attr.Value{ + "cloud_provider_id": basetypes.NewStringValue("aws"), + }, + ), + } + + defaultWgAttrTypes := map[string]attr.Type{ + "region": defaultWgAttr["region"].Type(ctx), + "cloud_provider": defaultWgAttr["cloud_provider"].Type(ctx), + } + + defaultWgObject := basetypes.NewObjectValueMust(defaultWgAttrTypes, defaultWgAttr) + defaultWgObjects := []attr.Value{} + defaultWgObjects = append(defaultWgObjects, defaultWgObject) + defaultWgSet := basetypes.NewSetValueMust(defaultWgObject.Type(ctx), defaultWgObjects) + + addGroupObject := map[string]attr.Value{ + "region": basetypes.NewObjectValueMust( + map[string]attr.Type{ + "region_id": defaultWgAttr["region"].(basetypes.ObjectValue).Attributes()["region_id"].Type(ctx), + }, + map[string]attr.Value{ + "region_id": basetypes.NewStringValue("us-east-2"), + }, + ), + "cloud_provider": basetypes.NewObjectValueMust( + map[string]attr.Type{ + "cloud_provider_id": defaultWgAttr["cloud_provider"].(basetypes.ObjectValue).Attributes()["cloud_provider_id"].Type(ctx), + }, + map[string]attr.Value{ + "cloud_provider_id": basetypes.NewStringValue("aws"), + }, + ), + } + + type args struct { + ctx context.Context + req planmodifier.SetRequest + resp *planmodifier.SetResponse + } + + tests := []struct { + name string + m plan_modifier.CustomWitnessGroupDiffModifier + args args + expectedWarningsCount int + expectedWarningSummary []string + expectedPlanElements []attr.Value + }{ + { + name: "Add wg expected success", + args: args{ + req: planmodifier.SetRequest{ + StateValue: defaultWgSet, + }, + resp: &planmodifier.SetResponse{ + PlanValue: basetypes.NewSetValueMust(defaultWgObject.Type(ctx), + append(defaultWgObjects, basetypes.NewObjectValueMust(defaultWgAttrTypes, + addGroupObject, + )), + ), + }, + }, + expectedWarningsCount: 1, + expectedWarningSummary: []string{"Adding new witness group"}, + expectedPlanElements: append(defaultWgObjects, basetypes.NewObjectValueMust(defaultWgAttrTypes, + addGroupObject, + )), + }, + { + name: "Create new wg expected success", + args: args{ + req: planmodifier.SetRequest{ + StateValue: basetypes.NewSetNull(defaultWgObject.Type(ctx)), + }, + resp: &planmodifier.SetResponse{ + PlanValue: basetypes.NewSetValueMust(defaultWgObject.Type(ctx), defaultWgObjects), + }, + }, + expectedPlanElements: defaultWgObjects, + }, + { + name: "Use state for unknown expected success", + args: args{ + req: planmodifier.SetRequest{ + StateValue: basetypes.NewSetValueMust(defaultWgObject.Type(ctx), defaultWgObjects), + PlanValue: basetypes.NewSetUnknown(defaultWgObject.Type(ctx)), + }, + resp: &planmodifier.SetResponse{ + PlanValue: basetypes.NewSetUnknown(defaultWgObject.Type(ctx)), + }, + }, + expectedPlanElements: defaultWgObjects, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + tt.m.PlanModifySet(tt.args.ctx, tt.args.req, tt.args.resp) + + if tt.args.resp.Diagnostics.WarningsCount() != tt.expectedWarningsCount { + t.Fatalf("expected warning count: %v, got: %v", tt.expectedWarningsCount, tt.args.resp.Diagnostics.WarningsCount()) + } + + if tt.args.resp.Diagnostics.WarningsCount() != 0 { + for k, v := range tt.args.resp.Diagnostics.Warnings() { + if tt.expectedWarningSummary[k] != v.Summary() { + t.Fatalf("expected warning summary: %v, got: %v", tt.expectedWarningSummary[k], v.Summary()) + } + } + } + + if !reflect.DeepEqual(tt.expectedPlanElements, tt.args.resp.PlanValue.Elements()) { + t.Fatalf("expected plan elements: %v, got: %v", tt.expectedPlanElements, tt.args.resp.PlanValue.Elements()) + } + }) + } +} diff --git a/pkg/provider/resource_pgd.go b/pkg/provider/resource_pgd.go index b664547d..cd50fbb4 100644 --- a/pkg/provider/resource_pgd.go +++ b/pkg/provider/resource_pgd.go @@ -46,12 +46,8 @@ type pgdResource struct { client *api.PGDClient } -func (p pgdResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_pgd" -} - -func (p pgdResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ +func PgdSchema(ctx context.Context) schema.Schema { + return schema.Schema{ MarkdownDescription: "The PGD cluster data source describes a BigAnimal cluster. The data source requires your PGD cluster name.", Blocks: map[string]schema.Block{ "timeouts": timeouts.Block(ctx, @@ -587,6 +583,14 @@ func (p pgdResource) Schema(ctx context.Context, req resource.SchemaRequest, res } } +func (p pgdResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_pgd" +} + +func (p pgdResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = PgdSchema(ctx) +} + // Configure adds the provider configured client to the data source. func (p *pgdResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { if req.ProviderData == nil {