Skip to content

Commit c29d7ed

Browse files
committed
fix: redis 8 modules correctly handled by active active databases in state
1 parent 1b800ef commit c29d7ed

File tree

2 files changed

+119
-9
lines changed

2 files changed

+119
-9
lines changed

provider/rediscloud_active_active_database_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ func TestAccResourceRedisCloudActiveActiveDatabase_CRUDI(t *testing.T) {
5050
resource.TestCheckResourceAttr(databaseResourceName, "global_alert.#", "1"),
5151
resource.TestCheckResourceAttr(databaseResourceName, "global_alert.0.name", "dataset-size"),
5252
resource.TestCheckResourceAttr(databaseResourceName, "global_alert.0.value", "1"),
53-
resource.TestCheckResourceAttr(databaseResourceName, "global_modules.#", "1"),
54-
resource.TestCheckResourceAttr(databaseResourceName, "global_modules.0", "RedisJSON"),
53+
resource.TestCheckResourceAttr(databaseResourceName, "global_modules.#", "0"),
5554
resource.TestCheckResourceAttr(databaseResourceName, "global_source_ips.#", "2"),
5655
resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "true"),
5756

@@ -118,8 +117,7 @@ func TestAccResourceRedisCloudActiveActiveDatabase_CRUDI(t *testing.T) {
118117
resource.TestCheckResourceAttrSet(datasourceName, "tls_certificate"),
119118

120119
resource.TestCheckResourceAttr(datasourceName, "data_eviction", "volatile-lru"),
121-
resource.TestCheckResourceAttr(datasourceName, "global_modules.#", "1"),
122-
resource.TestCheckResourceAttr(datasourceName, "global_modules.0", "RedisJSON"),
120+
resource.TestCheckResourceAttr(datasourceName, "global_modules.#", "0"),
123121

124122
resource.TestCheckResourceAttr(datasourceName, "tags.deployment_family", "blue"),
125123
resource.TestCheckResourceAttr(datasourceName, "tags.priority", "code-2"),

provider/resource_rediscloud_active_active_database.go

Lines changed: 117 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ func resourceRedisCloudActiveActiveDatabase() *schema.Resource {
272272
Description: "When 'true', enables connecting to the database with the 'default' user. If not specified, the region inherits the value from global_enable_default_user.",
273273
Type: schema.TypeBool,
274274
Optional: true,
275+
Computed: true,
275276
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
276277
// Smart diff suppression: only suppress when truly inheriting from global
277278
// Check both: value matches global AND field not explicitly set in config
@@ -692,10 +693,45 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R
692693

693694
regionDbConfig["remote_backup"] = pro.FlattenBackupPlan(regionDb.Backup, getStateRemoteBackup(d, region), "")
694695

695-
// Always include enable_default_user in state (even when inheriting from global)
696-
// DiffSuppressFunc will prevent drift when value matches global
696+
// Conditionally include enable_default_user in state
697+
// Only include if: (1) explicitly set in config, OR (2) differs from global (API override)
698+
// DiffSuppressFunc provides additional safety net for edge cases
697699
if regionDb.Security.EnableDefaultUser != nil {
698-
regionDbConfig["enable_default_user"] = redis.BoolValue(regionDb.Security.EnableDefaultUser)
700+
globalValue := d.Get("global_enable_default_user").(bool)
701+
regionValue := redis.BoolValue(regionDb.Security.EnableDefaultUser)
702+
703+
shouldInclude := false
704+
705+
rawConfig := d.GetRawConfig()
706+
if !rawConfig.IsNull() && rawConfig.IsKnown() {
707+
// Apply/Update mode: GetRawConfig available - check actual HCL config
708+
explicitInConfig := isFieldInConfigForRegion(d, region, "enable_default_user")
709+
710+
if explicitInConfig {
711+
// User explicitly set it in config → always include
712+
shouldInclude = true
713+
} else if regionValue != globalValue {
714+
// Not in config but differs from global → API override exists, include to track it
715+
shouldInclude = true
716+
}
717+
// else: not in config and matches global → inheriting, omit from state
718+
} else {
719+
// Refresh mode: GetRawConfig is null - check previous state via GetRawState
720+
fieldWasInState := isFieldInStateForRegion(d, region, "enable_default_user")
721+
722+
if fieldWasInState {
723+
// Was explicitly tracked before → continue tracking
724+
shouldInclude = true
725+
} else if regionValue != globalValue {
726+
// New API override detected → start tracking
727+
shouldInclude = true
728+
}
729+
// else: wasn't tracked and matches global → keep omitted
730+
}
731+
732+
if shouldInclude {
733+
regionDbConfig["enable_default_user"] = regionValue
734+
}
699735
}
700736

701737
regionDbConfigs = append(regionDbConfigs, regionDbConfig)
@@ -714,8 +750,21 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R
714750
if err := d.Set("private_endpoint", privateEndpointConfig); err != nil {
715751
return diag.FromErr(err)
716752
}
717-
if err := d.Set("global_modules", flattenModulesToNames(db.Modules)); err != nil {
718-
return diag.FromErr(err)
753+
// For Redis 8.0+, modules are bundled by default and returned by the API
754+
// Only set modules in state if they were explicitly defined in the config
755+
redisVersion := redis.StringValue(db.RedisVersion)
756+
if shouldSuppressModuleDiffsForRedis8(redisVersion) {
757+
// Only set modules if they were explicitly configured by the user
758+
if _, ok := d.GetOk("global_modules"); ok {
759+
if err := d.Set("global_modules", flattenModulesToNames(db.Modules)); err != nil {
760+
return diag.FromErr(err)
761+
}
762+
}
763+
} else {
764+
// For Redis < 8.0, always set modules from API response
765+
if err := d.Set("global_modules", flattenModulesToNames(db.Modules)); err != nil {
766+
return diag.FromErr(err)
767+
}
719768
}
720769

721770
if err := d.Set("redis_version", redis.StringValue(db.RedisVersion)); err != nil {
@@ -1102,3 +1151,66 @@ func isFieldInConfigForRegion(d *schema.ResourceData, regionName string, fieldNa
11021151
return false
11031152
}
11041153

1154+
// isFieldInStateForRegion checks if a field is present in the actual persisted state
1155+
// for a specific region's override_region block. Uses GetRawState() to bypass materialization.
1156+
// Returns true only if the field exists in the actual state file.
1157+
func isFieldInStateForRegion(d *schema.ResourceData, regionName string, fieldName string) bool {
1158+
rawState := d.GetRawState()
1159+
if rawState.IsNull() || !rawState.IsKnown() {
1160+
return false
1161+
}
1162+
1163+
if !rawState.Type().HasAttribute("override_region") {
1164+
return false
1165+
}
1166+
1167+
overrideRegions := rawState.GetAttr("override_region")
1168+
if overrideRegions.IsNull() || !overrideRegions.IsKnown() {
1169+
return false
1170+
}
1171+
1172+
// Iterate through regions to find matching name
1173+
iter := overrideRegions.ElementIterator()
1174+
for iter.Next() {
1175+
_, regionVal := iter.Element()
1176+
1177+
if regionVal.IsNull() || !regionVal.IsKnown() {
1178+
continue
1179+
}
1180+
1181+
if !regionVal.Type().HasAttribute("name") {
1182+
continue
1183+
}
1184+
1185+
nameAttr := regionVal.GetAttr("name")
1186+
if nameAttr.IsNull() || !nameAttr.IsKnown() {
1187+
continue
1188+
}
1189+
1190+
// Check if the name matches
1191+
if nameAttr.AsString() == regionName {
1192+
// Found the region, check if field exists
1193+
if !regionVal.Type().HasAttribute(fieldName) {
1194+
return false
1195+
}
1196+
fieldAttr := regionVal.GetAttr(fieldName)
1197+
return !fieldAttr.IsNull()
1198+
}
1199+
}
1200+
1201+
return false
1202+
}
1203+
1204+
// shouldSuppressModuleDiffsForRedis8 checks if the Redis version is 8.0 or higher.
1205+
// For Redis 8+, modules are bundled by default, so we suppress drift for modules
1206+
// that weren't explicitly configured by the user.
1207+
func shouldSuppressModuleDiffsForRedis8(version string) bool {
1208+
if len(version) == 0 {
1209+
return false
1210+
}
1211+
majorVersionStr := strings.Split(version, ".")[0]
1212+
if majorVersion, err := strconv.Atoi(majorVersionStr); err == nil {
1213+
return majorVersion >= 8
1214+
}
1215+
return false
1216+
}

0 commit comments

Comments
 (0)