diff --git a/.changelog/40969.txt b/.changelog/40969.txt new file mode 100644 index 00000000000..cfdcbd7f169 --- /dev/null +++ b/.changelog/40969.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_elasticache_serverless_cache: Fix `InvalidParameterCombination` error during update +``` \ No newline at end of file diff --git a/internal/service/elasticache/serverless_cache.go b/internal/service/elasticache/serverless_cache.go index c1ffa7b5d09..69838a90af5 100644 --- a/internal/service/elasticache/serverless_cache.go +++ b/internal/service/elasticache/serverless_cache.go @@ -340,9 +340,20 @@ func (r *serverlessCacheResource) Update(ctx context.Context, request resource.U conn := r.Meta().ElastiCacheClient(ctx) - if serverlessCacheHasChanges(ctx, new, old) { - input := &elasticache.ModifyServerlessCacheInput{} - response.Diagnostics.Append(fwflex.Expand(ctx, new, input)...) + diff, d := fwflex.Calculate(ctx, new, old) + response.Diagnostics.Append(d...) + if response.Diagnostics.HasError() { + return + } + + if diff.HasChanges() { + input := elasticache.ModifyServerlessCacheInput{ + ServerlessCacheName: new.ServerlessCacheName.ValueStringPointer(), + } + response.Diagnostics.Append(fwflex.Expand(ctx, new, &input, diff.IgnoredFieldNamesOpts()...)...) + if response.Diagnostics.HasError() { + return + } // Unset engine related stuff to prevent the following error: // This API supports only cross-engine upgrades to Valkey engine currently. if new.Engine.Equal(old.Engine) { @@ -351,11 +362,8 @@ func (r *serverlessCacheResource) Update(ctx context.Context, request resource.U if new.MajorEngineVersion.Equal(old.MajorEngineVersion) { input.MajorEngineVersion = nil } - if response.Diagnostics.HasError() { - return - } - _, err := conn.ModifyServerlessCache(ctx, input) + _, err := conn.ModifyServerlessCache(ctx, &input) if err != nil { response.Diagnostics.AddError(fmt.Sprintf("updating ElastiCache Serverless Cache (%s)", new.ID.ValueString()), err.Error()) @@ -596,12 +604,3 @@ type endpointModel struct { Address types.String `tfsdk:"address"` Port types.Int64 `tfsdk:"port"` } - -func serverlessCacheHasChanges(_ context.Context, plan, state serverlessCacheResourceModel) bool { - return !plan.CacheUsageLimits.Equal(state.CacheUsageLimits) || - !plan.DailySnapshotTime.Equal(state.DailySnapshotTime) || - !plan.Description.Equal(state.Description) || - !plan.UserGroupID.Equal(state.UserGroupID) || - !plan.SecurityGroupIDs.Equal(state.SecurityGroupIDs) || - !plan.SnapshotRetentionLimit.Equal(state.SnapshotRetentionLimit) -} diff --git a/internal/service/elasticache/serverless_cache_test.go b/internal/service/elasticache/serverless_cache_test.go index 90770c03e1c..a4a49ddfc5c 100644 --- a/internal/service/elasticache/serverless_cache_test.go +++ b/internal/service/elasticache/serverless_cache_test.go @@ -201,6 +201,46 @@ func TestAccElastiCacheServerlessCache_fullRedis(t *testing.T) { }) } +func TestAccElastiCacheServerlessCache_redisUpdateWithUserGroup(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_elasticache_serverless_cache.test" + var serverlessElasticCache awstypes.ServerlessCache + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ElastiCacheServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: resource.ComposeAggregateTestCheckFunc( + testAccCheckServerlessCacheDestroy(ctx), + ), + Steps: []resource.TestStep{ + { + Config: testAccServerlessCacheConfig_redisUpdateWithUserGroup(rName, "test description"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckServerlessCacheExists(ctx, resourceName, &serverlessElasticCache), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "elasticache", "serverlesscache:{name}"), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttr(resourceName, names.AttrDescription, "test description"), + ), + }, + { + Config: testAccServerlessCacheConfig_redisUpdateWithUserGroup(rName, "test description updated"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckServerlessCacheExists(ctx, resourceName, &serverlessElasticCache), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "elasticache", "serverlesscache:{name}"), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttr(resourceName, names.AttrDescription, "test description updated"), + ), + }, + }, + }) +} + func TestAccElastiCacheServerlessCache_fullValkey(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { @@ -725,6 +765,43 @@ resource "aws_security_group" "test" { `, rName)) } +func testAccServerlessCacheConfig_redisUpdateWithUserGroup(rName, description string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 2), fmt.Sprintf(` +resource "aws_elasticache_user" "test" { + user_id = "testuserid" + user_name = "default" + access_string = "on ~* +@all" + engine = "REDIS" + passwords = ["password123456789"] +} + +resource "aws_elasticache_user_group" "test" { + engine = "REDIS" + user_group_id = "usergroupid" + user_ids = [aws_elasticache_user.test.user_id] +} + +resource "aws_elasticache_serverless_cache" "test" { + engine = "redis" + name = %[1]q + cache_usage_limits { + data_storage { + maximum = 12 + unit = "GB" + } + ecpu_per_second { + maximum = 5100 + } + } + daily_snapshot_time = "09:00" + description = %[2]q + major_engine_version = "7" + snapshot_retention_limit = 1 + user_group_id = aws_elasticache_user_group.test.id +} +`, rName, description)) +} + func testAccServerlessCacheConfig_fullValkey(rName string) string { return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 2), fmt.Sprintf(` resource "aws_elasticache_serverless_cache" "test" {