Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

r/aws_elasticache_replication_group: v2 state upgrader #34600

Merged
merged 2 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/34600.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_elaticache_replication_group: Fix regression caused by the introduction of the `auth_token_update_strategy` argument with a default value
```
15 changes: 13 additions & 2 deletions internal/service/elasticache/replication_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ func ResourceReplicationGroup() *schema.Resource {
Optional: true,
ValidateFunc: validation.StringInSlice(elasticache.AuthTokenUpdateStrategyType_Values(), true),
Default: elasticache.AuthTokenUpdateStrategyTypeRotate,
RequiredWith: []string{"auth_token"},
},
"auto_minor_version_upgrade": {
Type: nullable.TypeNullableBool,
Expand Down Expand Up @@ -349,14 +348,26 @@ func ResourceReplicationGroup() *schema.Resource {
},
},

SchemaVersion: 1,
SchemaVersion: 2,
// 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.
MigrateState: func(v int, inst *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
return inst, nil
},

StateUpgraders: []schema.StateUpgrader{
// v5.27.0 introduced the auth_token_update_strategy argument with a default
// value required to preserve backward compatibility. In order to prevent
// differences and attempted modifications on upgrade, the default value
// must be written to state via a state upgrader.
{
Type: resourceReplicationGroupConfigV1().CoreConfigSchema().ImpliedType(),
Upgrade: replicationGroupStateUpgradeV1,
Version: 1,
},
},

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(ReplicationGroupDefaultCreatedTimeout),
Delete: schema.DefaultTimeout(ReplicationGroupDefaultDeletedTimeout),
Expand Down
330 changes: 330 additions & 0 deletions internal/service/elasticache/replication_group_migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package elasticache

import (
"context"
"strings"

"github.com/aws/aws-sdk-go/service/elasticache"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
"github.com/hashicorp/terraform-provider-aws/internal/types/nullable"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
"github.com/hashicorp/terraform-provider-aws/names"
)

func replicationGroupStateUpgradeV1(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
if rawState == nil {
rawState = map[string]interface{}{}
}

// Set auth_token_update_strategy to new default value
rawState["auth_token_update_strategy"] = elasticache.AuthTokenUpdateStrategyTypeRotate

return rawState, nil
}

func resourceReplicationGroupConfigV1() *schema.Resource {
//lintignore:R011
return &schema.Resource{
Schema: map[string]*schema.Schema{
"apply_immediately": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"arn": {
Type: schema.TypeString,
Computed: true,
},
"at_rest_encryption_enabled": {
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
Computed: true,
},
"auth_token": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
ValidateFunc: validReplicationGroupAuthToken,
ConflictsWith: []string{"user_group_ids"},
},
"auto_minor_version_upgrade": {
Type: nullable.TypeNullableBool,
Optional: true,
Computed: true,
ValidateFunc: nullable.ValidateTypeStringNullableBool,
},
"automatic_failover_enabled": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"cluster_enabled": {
Type: schema.TypeBool,
Computed: true,
},
"configuration_endpoint_address": {
Type: schema.TypeString,
Computed: true,
},
"data_tiering_enabled": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
ForceNew: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringIsNotEmpty,
},
"engine": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: engineRedis,
ValidateFunc: validation.StringInSlice([]string{engineRedis}, true),
},
"engine_version": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validRedisVersionString,
},
"engine_version_actual": {
Type: schema.TypeString,
Computed: true,
},
"global_replication_group_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
ConflictsWith: []string{
"num_node_groups",
"parameter_group_name",
"engine",
"engine_version",
"node_type",
"security_group_names",
"transit_encryption_enabled",
"at_rest_encryption_enabled",
"snapshot_arns",
"snapshot_name",
},
},
"ip_discovery": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice(elasticache.IpDiscovery_Values(), false),
},
"log_delivery_configuration": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 2,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"destination_type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(elasticache.DestinationType_Values(), false),
},
"destination": {
Type: schema.TypeString,
Required: true,
},
"log_format": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(elasticache.LogFormat_Values(), false),
},
"log_type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(elasticache.LogType_Values(), false),
},
},
},
},
"maintenance_window": {
Type: schema.TypeString,
Optional: true,
Computed: true,
StateFunc: func(val interface{}) string {
// ElastiCache always changes the maintenance to lowercase
return strings.ToLower(val.(string))
},
ValidateFunc: verify.ValidOnceAWeekWindowFormat,
},
"member_clusters": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"multi_az_enabled": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"network_type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice(elasticache.NetworkType_Values(), false),
},
"node_type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"notification_topic_arn": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: verify.ValidARN,
},
"num_cache_clusters": {
Type: schema.TypeInt,
Computed: true,
Optional: true,
ConflictsWith: []string{"num_node_groups"},
},
"num_node_groups": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ConflictsWith: []string{"num_cache_clusters", "global_replication_group_id"},
},
"parameter_group_name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return strings.HasPrefix(old, "global-datastore-")
},
},
"port": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
// Suppress default Redis ports when not defined
if !d.IsNewResource() && new == "0" && old == defaultRedisPort {
return true
}
return false
},
},
"preferred_cache_cluster_azs": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"primary_endpoint_address": {
Type: schema.TypeString,
Computed: true,
},
"reader_endpoint_address": {
Type: schema.TypeString,
Computed: true,
},
"replicas_per_node_group": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"replication_group_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateReplicationGroupID,
StateFunc: func(val interface{}) string {
return strings.ToLower(val.(string))
},
},
"security_group_names": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"security_group_ids": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"snapshot_arns": {
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
// Note: Unlike aws_elasticache_cluster, this does not have a limit of 1 item.
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.All(
verify.ValidARN,
validation.StringDoesNotContainAny(","),
),
},
Set: schema.HashString,
},
"snapshot_retention_limit": {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntAtMost(35),
},
"snapshot_window": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: verify.ValidOnceADayWindowFormat,
},
"snapshot_name": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"subnet_group_name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
names.AttrTags: tftags.TagsSchema(),
names.AttrTagsAll: tftags.TagsSchemaComputed(),
"transit_encryption_enabled": {
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
Computed: true,
},
"user_group_ids": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
ConflictsWith: []string{"auth_token"},
},
"kms_key_id": {
Type: schema.TypeString,
ForceNew: true,
Optional: true,
},
"final_snapshot_identifier": {
Type: schema.TypeString,
Optional: true,
},
},
}
}