Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions internal/common/customplanmodifier/create_only_default_bool.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,36 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)

// CreateOnlyAttributePlanModifierWithBoolDefault sets a default value on create operation that will show in the plan.
// CreateOnlyBoolWithDefault sets a default value on create operation that will show in the plan.
// This avoids any custom logic in the resource "Create" handler.
// On update the default has no impact and the UseStateForUnknown behavior is observed instead.
// Always use Optional+Computed when using a default value.
func CreateOnlyAttributePlanModifierWithBoolDefault(b bool) planmodifier.Bool {
return &createOnlyAttributePlanModifierWithBoolDefault{defaultBool: &b}
// If the attribute is not in the API Response implement CopyFromPlan behavior when converting API Model to TF Model.
func CreateOnlyBoolWithDefault(b bool) planmodifier.Bool {
return &createOnlyBoolPlanModifier{defaultBool: b}
}

type createOnlyAttributePlanModifierWithBoolDefault struct {
defaultBool *bool
type createOnlyBoolPlanModifier struct {
defaultBool bool
}

func (d *createOnlyAttributePlanModifierWithBoolDefault) Description(ctx context.Context) string {
func (d *createOnlyBoolPlanModifier) Description(ctx context.Context) string {
return d.MarkdownDescription(ctx)
}

func (d *createOnlyAttributePlanModifierWithBoolDefault) MarkdownDescription(ctx context.Context) string {
return "Ensures the update operation fails when updating an attribute. If the read after import don't equal the configuration value it will also raise an error."
func (d *createOnlyBoolPlanModifier) MarkdownDescription(ctx context.Context) string {
return "Ensures the update operation fails when updating an attribute. If the read after import doesn't equal the configuration value it will also raise an error."
}

// isCreate uses the full state to check if this is a create operation
func isCreate(t *tfsdk.State) bool {
return t.Raw.IsNull()
}

func (d *createOnlyAttributePlanModifierWithBoolDefault) UseDefault() bool {
return d.defaultBool != nil
}

func (d *createOnlyAttributePlanModifierWithBoolDefault) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) {
func (d *createOnlyBoolPlanModifier) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) {
if isCreate(&req.State) {
if !IsKnown(req.PlanValue) && d.UseDefault() {
resp.PlanValue = types.BoolPointerValue(d.defaultBool)
if !IsKnown(req.PlanValue) {
resp.PlanValue = types.BoolPointerValue(&d.defaultBool)
}
return
}
Expand All @@ -55,14 +53,18 @@ func (d *createOnlyAttributePlanModifierWithBoolDefault) PlanModifyBool(ctx cont
}
}

// isUpdated checks if the attribute was updated.
// Special case when the attribute is removed/set to null in the plan:
// Computed Attribute: returns false (unknown in the plan)
// Optional Attribute: returns true if the state has a value
func isUpdated(state, plan attr.Value) bool {
if !IsKnown(plan) {
return false
}
return !state.Equal(plan)
}

func (d *createOnlyAttributePlanModifierWithBoolDefault) addDiags(diags *diag.Diagnostics, attrPath path.Path, stateValue attr.Value) {
func (d *createOnlyBoolPlanModifier) addDiags(diags *diag.Diagnostics, attrPath path.Path, stateValue attr.Value) {
message := fmt.Sprintf("%s cannot be updated or set after import, remove it from the configuration or use the state value (see below).", attrPath)
detail := fmt.Sprintf("The current state value is %s", stateValue)
diags.AddError(message, detail)
Expand Down
3 changes: 1 addition & 2 deletions internal/service/flexcluster/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ func (r *rs) Create(ctx context.Context, req resource.CreateRequest, resp *resou
flexClusterResp, err := CreateFlexCluster(ctx, projectID, clusterName, flexClusterReq, connV2.FlexClustersApi, &createTimeout)

// Handle timeout with cleanup logic
deleteOnCreateTimeout := cleanup.ResolveDeleteOnCreateTimeout(tfModel.DeleteOnCreateTimeout)
err = cleanup.HandleCreateTimeout(deleteOnCreateTimeout, err, func(ctxCleanup context.Context) error {
err = cleanup.HandleCreateTimeout(tfModel.DeleteOnCreateTimeout.ValueBool(), err, func(ctxCleanup context.Context) error {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed logic

cleanResp, cleanErr := r.Client.AtlasV2.FlexClustersApi.DeleteFlexCluster(ctxCleanup, projectID, clusterName).Execute()
if validate.StatusNotFound(cleanResp) {
return nil
Expand Down
3 changes: 2 additions & 1 deletion internal/service/flexcluster/resource_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,9 @@ func ResourceSchema(ctx context.Context) schema.Schema {
},
"delete_on_create_timeout": schema.BoolAttribute{
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Bool{
customplanmodifier.CreateOnly(),
customplanmodifier.CreateOnlyBoolWithDefault(true),
Copy link
Collaborator Author

@EspenAlbert EspenAlbert Sep 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change will be applied to the other resources in a follow up PR:

  • encryptionatrestprivateendpoint
  • pushbasedlogexport
  • streamprocessor

},
MarkdownDescription: "Indicates whether to delete the resource being created if a timeout is reached when waiting for completion. When set to `true` and timeout occurs, it triggers the deletion and returns immediately without waiting for deletion to complete. When set to `false`, the timeout will not trigger resource deletion. If you suspect a transient error when the value is `true`, wait before retrying to allow resource deletion to finish. Default is `true`.",
},
Expand Down
9 changes: 5 additions & 4 deletions internal/service/flexcluster/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,11 @@ func basicTestCase(t *testing.T) *resource.TestCase {
Check: checksFlexCluster(projectID, clusterName, false, true),
},
{
ResourceName: resourceName,
ImportStateIdFunc: acc.ImportStateIDFuncProjectIDClusterName(resourceName, "project_id", "name"),
ImportState: true,
ImportStateVerify: true,
ResourceName: resourceName,
ImportStateIdFunc: acc.ImportStateIDFuncProjectIDClusterName(resourceName, "project_id", "name"),
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"delete_on_create_timeout"},
},
},
}
Expand Down
2 changes: 1 addition & 1 deletion internal/service/project/resource_project_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func ResourceSchema(ctx context.Context) schema.Schema {
// Provider produced invalid plan: planned an invalid value for a non-computed attribute.
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Bool{customplanmodifier.CreateOnlyAttributePlanModifierWithBoolDefault(true)},
PlanModifiers: []planmodifier.Bool{customplanmodifier.CreateOnlyBoolWithDefault(true)},
},
"is_collect_database_specifics_statistics_enabled": schema.BoolAttribute{
Computed: true,
Expand Down