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 <aurel.canciu@nexhealth.com>
  • Loading branch information
relu committed Oct 16, 2024
1 parent 1a99c39 commit 834404e
Show file tree
Hide file tree
Showing 23 changed files with 814 additions and 105 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.
```
23 changes: 17 additions & 6 deletions internal/service/elasticache/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,11 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta int
requestUpdate = true
}

if d.HasChange(names.AttrEngine) {
input.Engine = aws.String(d.Get(names.AttrEngine).(string))
requestUpdate = true
}

if d.HasChange(names.AttrEngineVersion) {
input.EngineVersion = aws.String(d.Get(names.AttrEngineVersion).(string))
requestUpdate = true
Expand Down Expand Up @@ -974,11 +979,16 @@ func setFromCacheCluster(d *schema.ResourceData, c *awstypes.CacheCluster) error
d.Set("node_type", c.CacheNodeType)

d.Set(names.AttrEngine, c.Engine)
if aws.ToString(c.Engine) == engineRedis {
switch aws.ToString(c.Engine) {
case engineValkey:
if err := setEngineVersionValkey(d, c.EngineVersion); err != nil {
return err // nosemgrep:ci.bare-error-returns
}
case engineRedis:
if err := setEngineVersionRedis(d, c.EngineVersion); err != nil {
return err // nosemgrep:ci.bare-error-returns
}
} else {
default:
setEngineVersionMemcached(d, c.EngineVersion)
}
d.Set(names.AttrAutoMinorVersionUpgrade, strconv.FormatBool(aws.ToBool(c.AutoMinorVersionUpgrade)))
Expand Down Expand Up @@ -1010,13 +1020,14 @@ func clusterValidateAZMode(_ context.Context, diff *schema.ResourceDiff, v inter

// clusterValidateNumCacheNodes validates that `num_cache_nodes` is 1 when `engine` is "redis"
func clusterValidateNumCacheNodes(_ context.Context, diff *schema.ResourceDiff, v interface{}) error {
if v, ok := diff.GetOk(names.AttrEngine); !ok || v.(string) == engineMemcached {
engine, ok := diff.GetOk(names.AttrEngine)
if !ok || engine.(string) == engineMemcached {
return nil
}
if v, ok := diff.GetOk("num_cache_nodes"); !ok || v.(int) == 1 {
return nil
}
return errors.New(`engine "redis" does not support num_cache_nodes > 1`)
return fmt.Errorf(`engine "%s" does not support num_cache_nodes > 1`, engine.(string))
}

// clusterForceNewOnMemcachedNodeTypeChange causes re-creation when `node_type` is changed and `engine` is "memcached"
Expand All @@ -1026,15 +1037,15 @@ func clusterForceNewOnMemcachedNodeTypeChange(_ context.Context, diff *schema.Re
if diff.Id() == "" || !diff.HasChange("node_type") {
return nil
}
if v, ok := diff.GetOk(names.AttrEngine); !ok || v.(string) == engineRedis {
if v, ok := diff.GetOk(names.AttrEngine); !ok || v.(string) == engineRedis || v.(string) == engineValkey {
return nil
}
return diff.ForceNew("node_type")
}

// clusterValidateMemcachedSnapshotIdentifier validates that `final_snapshot_identifier` is not set when `engine` is "memcached"
func clusterValidateMemcachedSnapshotIdentifier(_ context.Context, diff *schema.ResourceDiff, v interface{}) error {
if v, ok := diff.GetOk(names.AttrEngine); !ok || v.(string) == engineRedis {
if v, ok := diff.GetOk(names.AttrEngine); !ok || v.(string) == engineRedis || v.(string) == engineValkey {
return nil
}
if _, ok := diff.GetOk(names.AttrFinalSnapshotIdentifier); !ok {
Expand Down
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 aws.ToString(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
Loading

0 comments on commit 834404e

Please sign in to comment.