Skip to content

Commit

Permalink
Add Valkey engine support for ElastiCache resources
Browse files Browse the repository at this point in the history
Fixes #39641

Signed-off-by: Aurel Canciu <[email protected]>
  • Loading branch information
relu committed Oct 16, 2024
1 parent 1a99c39 commit 2995833
Show file tree
Hide file tree
Showing 22 changed files with 723 additions and 69 deletions.
15 changes: 15 additions & 0 deletions .changelog/39745.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
```release-note:enhancement
resource/aws_elasticache_global_replication_group: Add `valkey` as valid engine option.
```

```release-note:enhancement
resource/aws_elasticache_replication_group: Add `valkey` as valid engine option.
```

```release-note:enhancement
resource/aws_elasticache_serverless_cache: Add `valkey` as valid engine option.
```

```release-note:enhancement
data-source/aws_elasticache_reserved_cache_node_offering: Allow `valkey` as valid `product_description` value.
```
2 changes: 2 additions & 0 deletions internal/service/elasticache/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ package elasticache
const (
engineMemcached = "memcached"
engineRedis = "redis"
engineValkey = "valkey"
)

// engine_Values returns all elements of the Engine enum
func engine_Values() []string {
return []string{
engineMemcached,
engineRedis,
engineValkey,
}
}

Expand Down
37 changes: 35 additions & 2 deletions internal/service/elasticache/engine_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,24 @@ func validRedisVersionString(v any, k string) (ws []string, errors []error) {
return
}

const (
valkeyVersionRegexpPattern = `^[7-9]\.[[:digit:]]+$`
)

var (
valkeyVersionRegexp = regexache.MustCompile(valkeyVersionRegexpPattern)
)

func validValkeyVersionString(v any, k string) (ws []string, errors []error) {
value := v.(string)

if !valkeyVersionRegexp.MatchString(value) {
errors = append(errors, fmt.Errorf("%s: %s is invalid. For Valkey use <major>.<minor>.", k, value))
}

return
}

// customizeDiffValidateClusterEngineVersion validates the correct format for `engine_version`, based on `engine`
func customizeDiffValidateClusterEngineVersion(_ context.Context, diff *schema.ResourceDiff, _ any) error {
engineVersion, ok := diff.GetOk(names.AttrEngineVersion)
Expand All @@ -70,11 +88,15 @@ func customizeDiffValidateClusterEngineVersion(_ context.Context, diff *schema.R
func validateClusterEngineVersion(engine, engineVersion string) error {
// Memcached: Versions in format <major>.<minor>.<patch>
// Redis: Starting with version 6, must match <major>.<minor>, prior to version 6, <major>.<minor>.<patch>
// Valkey: Versions in format <major>.<minor>
var validator schema.SchemaValidateFunc
if engine == "" || engine == engineMemcached {
switch engine {
case "", engineMemcached:
validator = validMemcachedVersionString
} else {
case engineRedis:
validator = validRedisVersionString
case engineValkey:
validator = validValkeyVersionString
}

_, errs := validator(engineVersion, names.AttrEngineVersion)
Expand Down Expand Up @@ -187,6 +209,17 @@ func setEngineVersionRedis(d *schema.ResourceData, version *string) error {
return nil
}

func setEngineVersionValkey(d *schema.ResourceData, version *string) error {
engineVersion, err := gversion.NewVersion(aws.ToString(version))
if err != nil {
return fmt.Errorf("reading engine version: %w", err)
}
d.Set(names.AttrEngineVersion, fmt.Sprintf("%d.%d", engineVersion.Segments()[0], engineVersion.Segments()[1]))
d.Set("engine_version_actual", engineVersion.String())

return nil
}

type versionDiff [3]int

// diffVersion returns a diff of the versions, component by component.
Expand Down
16 changes: 16 additions & 0 deletions internal/service/elasticache/engine_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,22 @@ func TestValidateClusterEngineVersion(t *testing.T) {
version: "7.0",
valid: true,
},

{
engine: tfelasticache.EngineValkey,
version: "7.x",
valid: false,
},
{
engine: tfelasticache.EngineValkey,
version: "7.2",
valid: true,
},
{
engine: tfelasticache.EngineValkey,
version: "7.2.6",
valid: false,
},
}

for _, testcase := range testcases {
Expand Down
2 changes: 2 additions & 0 deletions internal/service/elasticache/exports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var (
EmptyDescription = emptyDescription
EngineMemcached = engineMemcached
EngineRedis = engineRedis
EngineValkey = engineValkey
EngineVersionForceNewOnDowngrade = engineVersionForceNewOnDowngrade
EngineVersionIsDowngrade = engineVersionIsDowngrade
GlobalReplicationGroupRegionPrefixFormat = globalReplicationGroupRegionPrefixFormat
Expand All @@ -44,6 +45,7 @@ var (
ValidateClusterEngineVersion = validateClusterEngineVersion
ValidMemcachedVersionString = validMemcachedVersionString
ValidRedisVersionString = validRedisVersionString
ValidValkeyVersionString = validValkeyVersionString
)

type (
Expand Down
19 changes: 14 additions & 5 deletions internal/service/elasticache/global_replication_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,13 @@ func resourceGlobalReplicationGroup() *schema.Resource {
Computed: true,
},
names.AttrEngineVersion: {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validRedisVersionString,
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.Any(
validRedisVersionString,
validValkeyVersionString,
),
DiffSuppressFunc: func(_, old, new string, _ *schema.ResourceData) bool {
if t, _ := regexp.MatchString(`[6-9]\.x`, new); t && old != "" {
oldVersion, err := gversion.NewVersion(old)
Expand Down Expand Up @@ -399,7 +402,13 @@ func resourceGlobalReplicationGroupRead(ctx context.Context, d *schema.ResourceD
d.Set("global_replication_group_id", globalReplicationGroup.GlobalReplicationGroupId)
d.Set("transit_encryption_enabled", globalReplicationGroup.TransitEncryptionEnabled)

if err := setEngineVersionRedis(d, globalReplicationGroup.EngineVersion); err != nil {
switch *globalReplicationGroup.Engine {
case engineValkey:
err = setEngineVersionValkey(d, globalReplicationGroup.EngineVersion)
default:
err = setEngineVersionRedis(d, globalReplicationGroup.EngineVersion)
}
if err != nil {
return sdkdiag.AppendErrorf(diags, "reading ElastiCache Replication Group (%s): %s", d.Id(), err)
}

Expand Down
81 changes: 77 additions & 4 deletions internal/service/elasticache/global_replication_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"github.com/hashicorp/terraform-provider-aws/names"
)

func TestAccElastiCacheGlobalReplicationGroup_basic(t *testing.T) {
func TestAccElastiCacheGlobalReplicationGroup_Redis_basic(t *testing.T) {
ctx := acctest.Context(t)
if testing.Short() {
t.Skip("skipping long-running test in short mode")
Expand All @@ -48,7 +48,61 @@ func TestAccElastiCacheGlobalReplicationGroup_basic(t *testing.T) {
),
Steps: []resource.TestStep{
{
Config: testAccGlobalReplicationGroupConfig_basic(rName, primaryReplicationGroupId),
Config: testAccGlobalReplicationGroupConfig_Redis_basic(rName, primaryReplicationGroupId),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckGlobalReplicationGroupExists(ctx, resourceName, &globalReplicationGroup),
testAccCheckReplicationGroupExists(ctx, primaryReplicationGroupResourceName, &primaryReplicationGroup),
acctest.MatchResourceAttrGlobalARN(resourceName, names.AttrARN, "elasticache", regexache.MustCompile(`globalreplicationgroup:`+tfelasticache.GlobalReplicationGroupRegionPrefixFormat+rName)),
resource.TestCheckResourceAttrPair(resourceName, "at_rest_encryption_enabled", primaryReplicationGroupResourceName, "at_rest_encryption_enabled"),
resource.TestCheckResourceAttr(resourceName, "auth_token_enabled", acctest.CtFalse),
resource.TestCheckResourceAttrPair(resourceName, "automatic_failover_enabled", primaryReplicationGroupResourceName, "automatic_failover_enabled"),
resource.TestCheckResourceAttrPair(resourceName, "cache_node_type", primaryReplicationGroupResourceName, "node_type"),
resource.TestCheckResourceAttrPair(resourceName, "cluster_enabled", primaryReplicationGroupResourceName, "cluster_enabled"),
resource.TestCheckResourceAttrPair(resourceName, names.AttrEngine, primaryReplicationGroupResourceName, names.AttrEngine),
resource.TestCheckResourceAttrPair(resourceName, "engine_version_actual", primaryReplicationGroupResourceName, "engine_version_actual"),
resource.TestCheckResourceAttr(resourceName, "global_replication_group_id_suffix", rName),
resource.TestMatchResourceAttr(resourceName, "global_replication_group_id", regexache.MustCompile(tfelasticache.GlobalReplicationGroupRegionPrefixFormat+rName)),
resource.TestCheckResourceAttr(resourceName, "global_replication_group_description", tfelasticache.EmptyDescription),
resource.TestCheckResourceAttr(resourceName, "global_node_groups.#", acctest.Ct0),
resource.TestCheckResourceAttr(resourceName, "num_node_groups", acctest.Ct0),
resource.TestCheckResourceAttr(resourceName, "primary_replication_group_id", primaryReplicationGroupId),
resource.TestCheckResourceAttr(resourceName, "transit_encryption_enabled", acctest.CtFalse),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccElastiCacheGlobalReplicationGroup_Valkey_basic(t *testing.T) {
ctx := acctest.Context(t)
if testing.Short() {
t.Skip("skipping long-running test in short mode")
}

var globalReplicationGroup awstypes.GlobalReplicationGroup
var primaryReplicationGroup awstypes.ReplicationGroup

rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
primaryReplicationGroupId := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

resourceName := "aws_elasticache_global_replication_group.test"
primaryReplicationGroupResourceName := "aws_elasticache_replication_group.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheckGlobalReplicationGroup(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.ElastiCacheServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: resource.ComposeAggregateTestCheckFunc(
testAccCheckGlobalReplicationGroupDestroy(ctx),
),
Steps: []resource.TestStep{
{
Config: testAccGlobalReplicationGroupConfig_Valkey_basic(rName, primaryReplicationGroupId),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckGlobalReplicationGroupExists(ctx, resourceName, &globalReplicationGroup),
testAccCheckReplicationGroupExists(ctx, primaryReplicationGroupResourceName, &primaryReplicationGroup),
Expand Down Expand Up @@ -96,7 +150,7 @@ func TestAccElastiCacheGlobalReplicationGroup_disappears(t *testing.T) {
CheckDestroy: testAccCheckGlobalReplicationGroupDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccGlobalReplicationGroupConfig_basic(rName, primaryReplicationGroupId),
Config: testAccGlobalReplicationGroupConfig_Redis_basic(rName, primaryReplicationGroupId),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckGlobalReplicationGroupExists(ctx, resourceName, &globalReplicationGroup),
acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelasticache.ResourceGlobalReplicationGroup(), resourceName),
Expand Down Expand Up @@ -1515,7 +1569,7 @@ func testAccMatchReplicationGroupActualVersion(ctx context.Context, j *awstypes.
}
}

func testAccGlobalReplicationGroupConfig_basic(rName, primaryReplicationGroupId string) string {
func testAccGlobalReplicationGroupConfig_Redis_basic(rName, primaryReplicationGroupId string) string {
return fmt.Sprintf(`
resource "aws_elasticache_global_replication_group" "test" {
global_replication_group_id_suffix = %[1]q
Expand All @@ -1534,6 +1588,25 @@ resource "aws_elasticache_replication_group" "test" {
`, rName, primaryReplicationGroupId)
}

func testAccGlobalReplicationGroupConfig_Valkey_basic(rName, primaryReplicationGroupId string) string {
return fmt.Sprintf(`
resource "aws_elasticache_global_replication_group" "test" {
global_replication_group_id_suffix = %[1]q
primary_replication_group_id = aws_elasticache_replication_group.test.id
}
resource "aws_elasticache_replication_group" "test" {
replication_group_id = %[2]q
description = "test"
engine = "valkey"
engine_version = "7.2"
node_type = "cache.m5.large"
num_cache_clusters = 1
}
`, rName, primaryReplicationGroupId)
}

func testAccGlobalReplicationGroupConfig_basic_nodeType(rName, primaryReplicationGroupId, nodeType string) string {
return fmt.Sprintf(`
resource "aws_elasticache_global_replication_group" "test" {
Expand Down
55 changes: 48 additions & 7 deletions internal/service/elasticache/parameter_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"github.com/hashicorp/terraform-provider-aws/names"
)

func TestAccElastiCacheParameterGroup_basic(t *testing.T) {
func TestAccElastiCacheParameterGroup_Redis_basic(t *testing.T) {
ctx := acctest.Context(t)
var v awstypes.CacheParameterGroup
resourceName := "aws_elasticache_parameter_group.test"
Expand All @@ -35,7 +35,7 @@ func TestAccElastiCacheParameterGroup_basic(t *testing.T) {
CheckDestroy: testAccCheckParameterGroupDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccParameterGroupConfig_basic(rName),
Config: testAccParameterGroupConfig_Redis_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckParameterGroupExists(ctx, resourceName, &v),
testAccCheckParameterGroupAttributes(&v, rName),
Expand All @@ -54,6 +54,38 @@ func TestAccElastiCacheParameterGroup_basic(t *testing.T) {
})
}

func TestAccElastiCacheParameterGroup_Valkey_basic(t *testing.T) {
ctx := acctest.Context(t)
var v awstypes.CacheParameterGroup
resourceName := "aws_elasticache_parameter_group.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.ElastiCacheServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckParameterGroupDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccParameterGroupConfig_Valkey_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckParameterGroupExists(ctx, resourceName, &v),
testAccCheckParameterGroupAttributes(&v, rName),
resource.TestCheckResourceAttr(resourceName, names.AttrDescription, "Managed by Terraform"),
resource.TestCheckResourceAttr(resourceName, names.AttrFamily, "valkey7"),
resource.TestCheckResourceAttr(resourceName, names.AttrName, rName),
resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccElastiCacheParameterGroup_disappears(t *testing.T) {
ctx := acctest.Context(t)
var v awstypes.CacheParameterGroup
Expand All @@ -67,7 +99,7 @@ func TestAccElastiCacheParameterGroup_disappears(t *testing.T) {
CheckDestroy: testAccCheckParameterGroupDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccParameterGroupConfig_basic(rName),
Config: testAccParameterGroupConfig_Redis_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckParameterGroupExists(ctx, resourceName, &v),
acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelasticache.ResourceParameterGroup(), resourceName),
Expand Down Expand Up @@ -154,7 +186,7 @@ func TestAccElastiCacheParameterGroup_removeAllParameters(t *testing.T) {
),
},
{
Config: testAccParameterGroupConfig_basic(rName),
Config: testAccParameterGroupConfig_Redis_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckParameterGroupExists(ctx, resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "parameter.#", acctest.Ct0),
Expand Down Expand Up @@ -190,7 +222,7 @@ func TestAccElastiCacheParameterGroup_RemoveReservedMemoryParameter_allParameter
),
},
{
Config: testAccParameterGroupConfig_basic(rName),
Config: testAccParameterGroupConfig_Redis_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckParameterGroupExists(ctx, resourceName, &cacheParameterGroup1),
resource.TestCheckResourceAttr(resourceName, "parameter.#", acctest.Ct0),
Expand Down Expand Up @@ -431,7 +463,7 @@ func TestAccElastiCacheParameterGroup_tags(t *testing.T) {
),
},
{
Config: testAccParameterGroupConfig_basic(rName),
Config: testAccParameterGroupConfig_Redis_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckParameterGroupExists(ctx, resourceName, &cacheParameterGroup1),
resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0),
Expand Down Expand Up @@ -511,7 +543,7 @@ func testAccCheckParameterGroupAttributes(v *awstypes.CacheParameterGroup, rName
}
}

func testAccParameterGroupConfig_basic(rName string) string {
func testAccParameterGroupConfig_Redis_basic(rName string) string {
return fmt.Sprintf(`
resource "aws_elasticache_parameter_group" "test" {
family = "redis2.8"
Expand All @@ -520,6 +552,15 @@ resource "aws_elasticache_parameter_group" "test" {
`, rName)
}

func testAccParameterGroupConfig_Valkey_basic(rName string) string {
return fmt.Sprintf(`
resource "aws_elasticache_parameter_group" "test" {
family = "valkey7"
name = %[1]q
}
`, rName)
}

func testAccParameterGroupConfig_description(rName, description string) string {
return fmt.Sprintf(`
resource "aws_elasticache_parameter_group" "test" {
Expand Down
Loading

0 comments on commit 2995833

Please sign in to comment.