diff --git a/.changelog/4405.txt b/.changelog/4405.txt new file mode 100644 index 0000000000..071903d1ab --- /dev/null +++ b/.changelog/4405.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/mongodbatlas_advanced_cluster: Emits a warning during plan when `use_effective_fields` is true, auto-scaling is enabled, and `instance_size`, `disk_size_gb`, or `disk_iops` is modified, as Atlas silently ignores these changes in that combination +``` diff --git a/internal/service/advancedcluster/plan_modifier.go b/internal/service/advancedcluster/plan_modifier.go index b7431d842a..3477b699d5 100644 --- a/internal/service/advancedcluster/plan_modifier.go +++ b/internal/service/advancedcluster/plan_modifier.go @@ -2,6 +2,8 @@ package advancedcluster import ( "context" + "fmt" + "strings" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -12,6 +14,9 @@ import ( ) var ( + // Spec fields that Atlas controls when auto-scaling is active. + autoScalingManagedSpecFields = []string{"instance_size", "disk_size_gb", "disk_iops"} + // Change mappings uses `attribute_name`, it doesn't care about the nested level. attributeRootChangeMapping = map[string][]string{ "replication_specs": {}, @@ -56,10 +61,37 @@ func handleModifyPlan(ctx context.Context, diags *diag.Diagnostics, state, plan attributeChanges := schemafunc.NewAttributeChanges(ctx, state, plan) keepUnknown := []string{"connection_strings", "state_name", "mongo_db_version", "config_server_type"} // Volatile attributes, should not be copied from state keepUnknown = append(keepUnknown, attributeChanges.KeepUnknown(attributeRootChangeMapping)...) - keepUnknown = append(keepUnknown, determineKeepUnknownsAutoScaling(ctx, diags, state, plan)...) + autoScalingFields := determineKeepUnknownsAutoScaling(ctx, diags, state, plan) + keepUnknown = append(keepUnknown, autoScalingFields...) + emitWarningIfSpecChangedWithAutoScaling(ctx, diags, plan, attributeChanges) schemafunc.CopyUnknowns(ctx, state, plan, keepUnknown, nil) } +// emitWarningIfSpecChangedWithAutoScaling warns when use_effective_fields=true and auto-scaling is enabled but the user +// changed instance_size, disk_size_gb, or disk_iops. Atlas silently ignores these changes in that combination +func emitWarningIfSpecChangedWithAutoScaling(ctx context.Context, diags *diag.Diagnostics, plan *TFModel, attributeChanges schemafunc.AttributeChanges) { + if !plan.UseEffectiveFields.ValueBool() || !autoScalingUsed(ctx, diags, plan) { + return + } + var changedFields []string + for _, field := range autoScalingManagedSpecFields { + if attributeChanges.AttributeChanged(field) { + changedFields = append(changedFields, field) + } + } + if len(changedFields) == 0 { + return + } + diags.AddWarning( + "Spec change ignored due to auto-scaling", + fmt.Sprintf("The change to %s will be stored in Terraform state but will not modify the actual cluster in Atlas. "+ + "When use_effective_fields is true and auto-scaling is enabled, Atlas controls instance_size, disk_size_gb, and disk_iops values. "+ + "To apply this change, temporarily disable auto-scaling. "+ + "See: https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/advanced_cluster#manually-updating-specs-with-use_effective_fields", + strings.Join(changedFields, ", ")), + ) +} + // adjustRegionConfigsChildren modifies the planned values of region configs based on the current state. // This ensures proper handling of removing auto scaling and specs attributes by preserving state values. func adjustRegionConfigsChildren(ctx context.Context, diags *diag.Diagnostics, state, plan *TFModel) { @@ -164,28 +196,26 @@ func findDefinedElectableSpecInReplicationSpec(ctx context.Context, regionConfig } func determineKeepUnknownsAutoScaling(ctx context.Context, diags *diag.Diagnostics, state, plan *TFModel) []string { - if !autoScalingUsed(ctx, diags, state, plan) { + if !autoScalingUsed(ctx, diags, state) && !autoScalingUsed(ctx, diags, plan) { return nil } // When either compute or disk auto-scaling is enabled, all three fields may be adjusted by Atlas - return []string{"instance_size", "disk_size_gb", "disk_iops"} + return autoScalingManagedSpecFields } -// autoScalingUsed checks if auto-scaling was enabled (state) or will be enabled (plan). -func autoScalingUsed(ctx context.Context, diags *diag.Diagnostics, state, plan *TFModel) bool { - for _, model := range []*TFModel{state, plan} { - repSpecsTF := TFModelList[TFReplicationSpecsModel](ctx, diags, model.ReplicationSpecs) - for i := range repSpecsTF { - regiongConfigsTF := TFModelList[TFRegionConfigsModel](ctx, diags, repSpecsTF[i].RegionConfigs) - for j := range regiongConfigsTF { - for _, autoScalingTF := range []types.Object{regiongConfigsTF[j].AutoScaling, regiongConfigsTF[j].AnalyticsAutoScaling} { - autoscaling := TFModelObject[TFAutoScalingModel](ctx, autoScalingTF) - if autoscaling == nil { - continue - } - if autoscaling.ComputeEnabled.ValueBool() || autoscaling.DiskGBEnabled.ValueBool() { - return true - } +// autoScalingUsed checks if auto-scaling is enabled in the given cluster model. +func autoScalingUsed(ctx context.Context, diags *diag.Diagnostics, model *TFModel) bool { + repSpecsTF := TFModelList[TFReplicationSpecsModel](ctx, diags, model.ReplicationSpecs) + for i := range repSpecsTF { + regiongConfigsTF := TFModelList[TFRegionConfigsModel](ctx, diags, repSpecsTF[i].RegionConfigs) + for j := range regiongConfigsTF { + for _, autoScalingTF := range []types.Object{regiongConfigsTF[j].AutoScaling, regiongConfigsTF[j].AnalyticsAutoScaling} { + autoscaling := TFModelObject[TFAutoScalingModel](ctx, autoScalingTF) + if autoscaling == nil { + continue + } + if autoscaling.ComputeEnabled.ValueBool() || autoscaling.DiskGBEnabled.ValueBool() { + return true } } }