diff --git a/.changelog/42336.txt b/.changelog/42336.txt new file mode 100644 index 000000000000..3774c4feb421 --- /dev/null +++ b/.changelog/42336.txt @@ -0,0 +1,3 @@ +```release-note:breaking-change +resource/aws_elasticache_replication_group: `auth_token_update_strategy` no longer has a default value. If `auth_token` is set, `auth_token_update_strategy` must also be explicitly configured. +``` diff --git a/internal/service/elasticache/replication_group.go b/internal/service/elasticache/replication_group.go index 5bfcde9989c7..6ef5e04e64da 100644 --- a/internal/service/elasticache/replication_group.go +++ b/internal/service/elasticache/replication_group.go @@ -85,7 +85,7 @@ func resourceReplicationGroup() *schema.Resource { Type: schema.TypeString, Optional: true, ValidateDiagFunc: enum.Validate[awstypes.AuthTokenUpdateStrategyType](), - Default: awstypes.AuthTokenUpdateStrategyTypeRotate, + RequiredWith: []string{"auth_token"}, }, names.AttrAutoMinorVersionUpgrade: { Type: nullable.TypeNullableBool, @@ -371,7 +371,7 @@ func resourceReplicationGroup() *schema.Resource { }, }, - SchemaVersion: 2, + SchemaVersion: 3, // SchemaVersion: 1 did not include any state changes via MigrateState. // Perform a no-operation state upgrade for Terraform 0.12 compatibility. // Future state migrations should be performed with StateUpgraders. @@ -386,9 +386,16 @@ func resourceReplicationGroup() *schema.Resource { // must be written to state via a state upgrader. { Type: resourceReplicationGroupConfigV1().CoreConfigSchema().ImpliedType(), - Upgrade: replicationGroupStateUpgradeV1, + Upgrade: replicationGroupStateUpgradeFromV1, Version: 1, }, + // v6.0.0 removed the default auth_token_update_strategy value. To prevent + // differences, the default value is removed when auth_token is not set. + { + Type: resourceReplicationGroupConfigV2().CoreConfigSchema().ImpliedType(), + Upgrade: replicationGroupStateUpgradeFromV2, + Version: 2, + }, }, Timeouts: &schema.ResourceTimeout{ diff --git a/internal/service/elasticache/replication_group_migrate.go b/internal/service/elasticache/replication_group_migrate.go index 0470741deace..0e98da4a2e9f 100644 --- a/internal/service/elasticache/replication_group_migrate.go +++ b/internal/service/elasticache/replication_group_migrate.go @@ -16,7 +16,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func replicationGroupStateUpgradeV1(ctx context.Context, rawState map[string]any, meta any) (map[string]any, error) { +func replicationGroupStateUpgradeFromV1(ctx context.Context, rawState map[string]any, meta any) (map[string]any, error) { if rawState == nil { rawState = map[string]any{} } @@ -283,3 +283,38 @@ func resourceReplicationGroupConfigV1() *schema.Resource { }, } } + +func replicationGroupStateUpgradeFromV2(ctx context.Context, rawState map[string]any, meta any) (map[string]any, error) { + if rawState == nil { + rawState = map[string]any{} + } + + // Remove auth_token_update_strategy default value if auth_token is not set + if v, ok := rawState["auth_token"].(string); !ok || v == "" { + delete(rawState, "auth_token_update_strategy") + } + + return rawState, nil +} + +func resourceReplicationGroupConfigV2() *schema.Resource { + s := resourceReplicationGroupConfigV1() + + // Attributes added in V2 of the schema + s.Schema["auth_token_update_strategy"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + } + s.Schema["cluster_mode"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + } + s.Schema["transit_encryption_mode"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + } + + return s +} diff --git a/internal/service/elasticache/replication_group_migrate_test.go b/internal/service/elasticache/replication_group_migrate_test.go new file mode 100644 index 000000000000..3c7448f11778 --- /dev/null +++ b/internal/service/elasticache/replication_group_migrate_test.go @@ -0,0 +1,113 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package elasticache + +import ( + "reflect" + "testing" + + awstypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" +) + +func Test_replicationGroupStateUpgradeFromV1(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + rawState map[string]any + want map[string]any + wantErr bool + }{ + { + name: "empty rawState", + rawState: nil, + want: map[string]any{ + "auth_token_update_strategy": awstypes.AuthTokenUpdateStrategyTypeRotate, + }, + }, + { + name: "auth_token", + rawState: map[string]any{ + "auth_token": "foo", + }, + want: map[string]any{ + "auth_token": "foo", + "auth_token_update_strategy": awstypes.AuthTokenUpdateStrategyTypeRotate, + }, + }, + { + name: "cluster_mode block", + rawState: map[string]any{ + "cluster_mode.num_node_groups": "2", + "cluster_mode.replicas_per_node_group": "2", + }, + want: map[string]any{ + "auth_token_update_strategy": awstypes.AuthTokenUpdateStrategyTypeRotate, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := replicationGroupStateUpgradeFromV1(t.Context(), tt.rawState, nil) + if (err != nil) != tt.wantErr { + t.Errorf("replicationGroupStateUpgradeFromV1() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("replicationGroupStateUpgradeFromV1() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_replicationGroupStateUpgradeFromV2(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + rawState map[string]any + want map[string]any + wantErr bool + }{ + { + name: "empty rawState", + rawState: nil, + want: map[string]any{}, + }, + { + name: "no auth_token, default auth_token_update_strategy", + rawState: map[string]any{ + "auth_token_update_strategy": string(awstypes.AuthTokenUpdateStrategyTypeRotate), + }, + want: map[string]any{}, + }, + { + name: "auth_token and auth_token_update_strategy", + rawState: map[string]any{ + "auth_token": "foo", + "auth_token_update_strategy": string(awstypes.AuthTokenUpdateStrategyTypeSet), + }, + want: map[string]any{ + "auth_token": "foo", + "auth_token_update_strategy": string(awstypes.AuthTokenUpdateStrategyTypeSet), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := replicationGroupStateUpgradeFromV2(t.Context(), tt.rawState, nil) + if (err != nil) != tt.wantErr { + t.Errorf("replicationGroupStateUpgradeFromV2() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("replicationGroupStateUpgradeFromV2() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/service/elasticache/replication_group_test.go b/internal/service/elasticache/replication_group_test.go index 9adfb5b2f58d..65d3300ae58f 100644 --- a/internal/service/elasticache/replication_group_test.go +++ b/internal/service/elasticache/replication_group_test.go @@ -415,6 +415,11 @@ func TestAccElastiCacheReplicationGroup_disappears(t *testing.T) { acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelasticache.ResourceReplicationGroup(), resourceName), ), ExpectNonEmptyPlan: true, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, }, }, }) @@ -702,7 +707,51 @@ func TestAccElastiCacheReplicationGroup_authToken(t *testing.T) { }) } -func TestAccElastiCacheReplicationGroup_stateUpgrade5270(t *testing.T) { +func TestAccElastiCacheReplicationGroup_upgrade_6_0_0(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var rg awstypes.ReplicationGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_elasticache_replication_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ElastiCacheServiceID), + CheckDestroy: testAccCheckReplicationGroupDestroy(ctx), + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "aws": { + Source: "hashicorp/aws", + VersionConstraint: "5.95.0", + }, + }, + Config: testAccReplicationGroupConfig_basic_engine(rName, "redis"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckReplicationGroupExists(ctx, resourceName, &rg), + ), + }, + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Config: testAccReplicationGroupConfig_basic_engine(rName, "redis"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckReplicationGroupExists(ctx, resourceName, &rg), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, + }) +} + +// At v5.26.0 the resource's schema is v1 and auth_token_update_strategy is not an argument +func TestAccElastiCacheReplicationGroup_upgrade_5_27_0(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -718,7 +767,6 @@ func TestAccElastiCacheReplicationGroup_stateUpgrade5270(t *testing.T) { CheckDestroy: testAccCheckReplicationGroupDestroy(ctx), Steps: []resource.TestStep{ { - // At v5.26.0 the resource's schema is v1 and auth_token_update_strategy is not an argument ExternalProviders: map[string]resource.ExternalProvider{ "aws": { Source: "hashicorp/aws", @@ -736,13 +784,18 @@ func TestAccElastiCacheReplicationGroup_stateUpgrade5270(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccCheckReplicationGroupExists(ctx, resourceName, &rg), ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, }, }, }) } // https://github.com/hashicorp/terraform-provider-aws/issues/38464. -func TestAccElastiCacheReplicationGroup_stateUpgrade5590(t *testing.T) { +func TestAccElastiCacheReplicationGroup_upgrade_4_68_0(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -775,6 +828,11 @@ func TestAccElastiCacheReplicationGroup_stateUpgrade5590(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccCheckReplicationGroupExists(ctx, resourceName, &rg), ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, }, }, }) diff --git a/website/docs/guides/version-6-upgrade.html.markdown b/website/docs/guides/version-6-upgrade.html.markdown index 8f9aec5495e6..d4d98667b3f5 100644 --- a/website/docs/guides/version-6-upgrade.html.markdown +++ b/website/docs/guides/version-6-upgrade.html.markdown @@ -40,6 +40,7 @@ Upgrade topics: - [resource/aws_cloudfront_response_headers_policy](#resourceaws_cloudfront_response_headers_policy) - [resource/aws_dx_gateway_association](#resourceaws_dx_gateway_association) - [resource/aws_ecs_task_definition](#resourceaws_ecs_task_definition) +- [resource/aws_elasticache_replication_group](#resourceaws_elasticache_replication_group) - [resource/aws_eks_addon](#resourceaws_eks_addon) - [resource/aws_guardduty_organization_configuration](#resourceaws_guardduty_organization_configuration) - [resource/aws_instance](#resourceaws_instance) @@ -319,6 +320,11 @@ Use the `associated_gateway_id` attribute instead. Remove `inference_accelerator` from your configuration—it no longer exists. Amazon Elastic Inference reached end of life in April 2024. +## resource/aws_elasticache_replication_group + +The `auth_token_update_strategy` argument no longer has a default value. +If `auth_token` is set, this argument must also be explicitly configured. + ## resource/aws_eks_addon The `resolve_conflicts` argument has been removed. Use `resolve_conflicts_on_create` and `resolve_conflicts_on_update` instead. diff --git a/website/docs/r/elasticache_replication_group.html.markdown b/website/docs/r/elasticache_replication_group.html.markdown index 2b7c66b79454..9dc89ea7da0e 100644 --- a/website/docs/r/elasticache_replication_group.html.markdown +++ b/website/docs/r/elasticache_replication_group.html.markdown @@ -198,7 +198,7 @@ The following arguments are optional: When `engine` is `redis`, default is `false`. When `engine` is `valkey`, default is `true`. * `auth_token` - (Optional) Password used to access a password protected server. Can be specified only if `transit_encryption_enabled = true`. -* `auth_token_update_strategy` - (Optional) Strategy to use when updating the `auth_token`. Valid values are `SET`, `ROTATE`, and `DELETE`. Defaults to `ROTATE`. +* `auth_token_update_strategy` - (Optional) Strategy to use when updating the `auth_token`. Valid values are `SET`, `ROTATE`, and `DELETE`. Required if `auth_token` is set. * `auto_minor_version_upgrade` - (Optional) Specifies whether minor version engine upgrades will be applied automatically to the underlying Cache Cluster instances during the maintenance window. Only supported for engine types `"redis"` and `"valkey"` and if the engine version is 6 or higher. Defaults to `true`.