-
Notifications
You must be signed in to change notification settings - Fork 92
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
resource/schema: New packages which contain type-specific schema plan…
… modifier implementations (#565) Reference: #132 When developers migrate from `tfsdk.Schema` to `resource/schema.Schema`, they will need to also migrate to the type-specific plan modifiers. New packages have been introduced under `resource/schema`, such as `resource/schema/stringplanmodifier`. The existing plan modifiers are deprecated, similar to the `tfsdk` schema handling.
- Loading branch information
Showing
93 changed files
with
7,215 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
```release-note:feature | ||
resource/schema: New packages, such as `stringplanmodifier` which contain type-specific schema plan modifier implementations | ||
``` | ||
|
||
```release-note:note | ||
resource: The `RequiresReplace()` plan modifier has been deprecated. Use a type-specific plan modifier instead, such as `resource/schema/stringplanmodifier.RequiresReplace()` or `resource/schema/stringplanmodifier.RequiresReplaceIfConfigured()` | ||
``` | ||
|
||
```release-note:note | ||
resource: The `RequiresReplaceIf()` plan modifier has been deprecated. Use a type-specific plan modifier instead, such as `resource/schema/stringplanmodifier.RequiresReplaceIf()` | ||
``` | ||
|
||
```release-note:note | ||
resource: The `UseStateForUnknown()` plan modifier has been deprecated. Use a type-specific plan modifier instead, such as `resource/schema/stringplanmodifier.UseStateForUnknown()` | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// Package boolplanmodifier provides plan modifiers for types.Bool attributes. | ||
package boolplanmodifier |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package boolplanmodifier | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" | ||
) | ||
|
||
// RequiresReplace returns a plan modifier that conditionally requires | ||
// resource replacement if: | ||
// | ||
// - The resource is planned for update. | ||
// - The plan and state values are not equal. | ||
// | ||
// Use RequiresReplaceIfConfigured if the resource replacement should | ||
// only occur if there is a configuration value (ignore unconfigured drift | ||
// detection changes). Use RequiresReplaceIf if the resource replacement | ||
// should check provider-defined conditional logic. | ||
func RequiresReplace() planmodifier.Bool { | ||
return RequiresReplaceIf( | ||
func(_ context.Context, _ planmodifier.BoolRequest, resp *RequiresReplaceIfFuncResponse) { | ||
resp.RequiresReplace = true | ||
}, | ||
"If the value of this attribute changes, Terraform will destroy and recreate the resource.", | ||
"If the value of this attribute changes, Terraform will destroy and recreate the resource.", | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package boolplanmodifier | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" | ||
) | ||
|
||
// RequiresReplaceIf returns a plan modifier that conditionally requires | ||
// resource replacement if: | ||
// | ||
// - The resource is planned for update. | ||
// - The plan and state values are not equal. | ||
// - The given function returns true. Returning false will not unset any | ||
// prior resource replacement. | ||
// | ||
// Use RequiresReplace if the resource replacement should always occur on value | ||
// changes. Use RequiresReplaceIfConfigured if the resource replacement should | ||
// occur on value changes, but only if there is a configuration value (ignore | ||
// unconfigured drift detection changes). | ||
func RequiresReplaceIf(f RequiresReplaceIfFunc, description, markdownDescription string) planmodifier.Bool { | ||
return requiresReplaceIfModifier{ | ||
ifFunc: f, | ||
description: description, | ||
markdownDescription: markdownDescription, | ||
} | ||
} | ||
|
||
// requiresReplaceIfModifier is an plan modifier that sets RequiresReplace | ||
// on the attribute if a given function is true. | ||
type requiresReplaceIfModifier struct { | ||
ifFunc RequiresReplaceIfFunc | ||
description string | ||
markdownDescription string | ||
} | ||
|
||
// Description returns a human-readable description of the plan modifier. | ||
func (m requiresReplaceIfModifier) Description(_ context.Context) string { | ||
return m.description | ||
} | ||
|
||
// MarkdownDescription returns a markdown description of the plan modifier. | ||
func (m requiresReplaceIfModifier) MarkdownDescription(_ context.Context) string { | ||
return m.markdownDescription | ||
} | ||
|
||
// PlanModifyBool implements the plan modification logic. | ||
func (m requiresReplaceIfModifier) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) { | ||
// Do not replace on resource creation. | ||
if req.State.Raw.IsNull() { | ||
return | ||
} | ||
|
||
// Do not replace on resource destroy. | ||
if req.Plan.Raw.IsNull() { | ||
return | ||
} | ||
|
||
// Do not replace if the plan and state values are equal. | ||
if req.PlanValue.Equal(req.StateValue) { | ||
return | ||
} | ||
|
||
ifFuncResp := &RequiresReplaceIfFuncResponse{} | ||
|
||
m.ifFunc(ctx, req, ifFuncResp) | ||
|
||
resp.Diagnostics.Append(ifFuncResp.Diagnostics...) | ||
resp.RequiresReplace = ifFuncResp.RequiresReplace | ||
} |
31 changes: 31 additions & 0 deletions
31
resource/schema/boolplanmodifier/requires_replace_if_configured.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package boolplanmodifier | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" | ||
) | ||
|
||
// RequiresReplaceIfConfigured returns a plan modifier that conditionally requires | ||
// resource replacement if: | ||
// | ||
// - The resource is planned for update. | ||
// - The plan and state values are not equal. | ||
// - The configuration value is not null. | ||
// | ||
// Use RequiresReplace if the resource replacement should occur regardless of | ||
// the presence of a configuration value. Use RequiresReplaceIf if the resource | ||
// replacement should check provider-defined conditional logic. | ||
func RequiresReplaceIfConfigured() planmodifier.Bool { | ||
return RequiresReplaceIf( | ||
func(_ context.Context, req planmodifier.BoolRequest, resp *RequiresReplaceIfFuncResponse) { | ||
if req.ConfigValue.IsNull() { | ||
return | ||
} | ||
|
||
resp.RequiresReplace = true | ||
}, | ||
"If the value of this attribute is configured and changes, Terraform will destroy and recreate the resource.", | ||
"If the value of this attribute is configured and changes, Terraform will destroy and recreate the resource.", | ||
) | ||
} |
167 changes: 167 additions & 0 deletions
167
resource/schema/boolplanmodifier/requires_replace_if_configured_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
package boolplanmodifier_test | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/hashicorp/terraform-plugin-framework/resource/schema" | ||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" | ||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" | ||
"github.com/hashicorp/terraform-plugin-framework/tfsdk" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
"github.com/hashicorp/terraform-plugin-go/tftypes" | ||
) | ||
|
||
func TestRequiresReplaceIfConfiguredModifierPlanModifyBool(t *testing.T) { | ||
t.Parallel() | ||
|
||
testSchema := schema.Schema{ | ||
Attributes: map[string]schema.Attribute{ | ||
"testattr": schema.BoolAttribute{}, | ||
}, | ||
} | ||
|
||
nullPlan := tfsdk.Plan{ | ||
Schema: testSchema, | ||
Raw: tftypes.NewValue( | ||
testSchema.Type().TerraformType(context.Background()), | ||
nil, | ||
), | ||
} | ||
|
||
nullState := tfsdk.State{ | ||
Schema: testSchema, | ||
Raw: tftypes.NewValue( | ||
testSchema.Type().TerraformType(context.Background()), | ||
nil, | ||
), | ||
} | ||
|
||
testPlan := func(value types.Bool) tfsdk.Plan { | ||
tfValue, err := value.ToTerraformValue(context.Background()) | ||
|
||
if err != nil { | ||
panic("ToTerraformValue error: " + err.Error()) | ||
} | ||
|
||
return tfsdk.Plan{ | ||
Schema: testSchema, | ||
Raw: tftypes.NewValue( | ||
testSchema.Type().TerraformType(context.Background()), | ||
map[string]tftypes.Value{ | ||
"testattr": tfValue, | ||
}, | ||
), | ||
} | ||
} | ||
|
||
testState := func(value types.Bool) tfsdk.State { | ||
tfValue, err := value.ToTerraformValue(context.Background()) | ||
|
||
if err != nil { | ||
panic("ToTerraformValue error: " + err.Error()) | ||
} | ||
|
||
return tfsdk.State{ | ||
Schema: testSchema, | ||
Raw: tftypes.NewValue( | ||
testSchema.Type().TerraformType(context.Background()), | ||
map[string]tftypes.Value{ | ||
"testattr": tfValue, | ||
}, | ||
), | ||
} | ||
} | ||
|
||
testCases := map[string]struct { | ||
request planmodifier.BoolRequest | ||
expected *planmodifier.BoolResponse | ||
}{ | ||
"state-null": { | ||
// resource creation | ||
request: planmodifier.BoolRequest{ | ||
ConfigValue: types.BoolValue(true), | ||
Plan: testPlan(types.BoolValue(true)), | ||
PlanValue: types.BoolValue(true), | ||
State: nullState, | ||
StateValue: types.BoolNull(), | ||
}, | ||
expected: &planmodifier.BoolResponse{ | ||
PlanValue: types.BoolValue(true), | ||
RequiresReplace: false, | ||
}, | ||
}, | ||
"plan-null": { | ||
// resource destroy | ||
request: planmodifier.BoolRequest{ | ||
ConfigValue: types.BoolNull(), | ||
Plan: nullPlan, | ||
PlanValue: types.BoolNull(), | ||
State: testState(types.BoolValue(true)), | ||
StateValue: types.BoolValue(true), | ||
}, | ||
expected: &planmodifier.BoolResponse{ | ||
PlanValue: types.BoolNull(), | ||
RequiresReplace: false, | ||
}, | ||
}, | ||
"planvalue-statevalue-different-configured": { | ||
request: planmodifier.BoolRequest{ | ||
ConfigValue: types.BoolValue(false), | ||
Plan: testPlan(types.BoolValue(false)), | ||
PlanValue: types.BoolValue(false), | ||
State: testState(types.BoolValue(true)), | ||
StateValue: types.BoolValue(true), | ||
}, | ||
expected: &planmodifier.BoolResponse{ | ||
PlanValue: types.BoolValue(false), | ||
RequiresReplace: true, | ||
}, | ||
}, | ||
"planvalue-statevalue-different-unconfigured": { | ||
request: planmodifier.BoolRequest{ | ||
ConfigValue: types.BoolNull(), | ||
Plan: testPlan(types.BoolValue(false)), | ||
PlanValue: types.BoolValue(false), | ||
State: testState(types.BoolValue(true)), | ||
StateValue: types.BoolValue(true), | ||
}, | ||
expected: &planmodifier.BoolResponse{ | ||
PlanValue: types.BoolValue(false), | ||
RequiresReplace: false, | ||
}, | ||
}, | ||
"planvalue-statevalue-equal": { | ||
request: planmodifier.BoolRequest{ | ||
ConfigValue: types.BoolValue(true), | ||
Plan: testPlan(types.BoolValue(true)), | ||
PlanValue: types.BoolValue(true), | ||
State: testState(types.BoolValue(true)), | ||
StateValue: types.BoolValue(true), | ||
}, | ||
expected: &planmodifier.BoolResponse{ | ||
PlanValue: types.BoolValue(true), | ||
RequiresReplace: false, | ||
}, | ||
}, | ||
} | ||
|
||
for name, testCase := range testCases { | ||
name, testCase := name, testCase | ||
|
||
t.Run(name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
resp := &planmodifier.BoolResponse{ | ||
PlanValue: testCase.request.PlanValue, | ||
} | ||
|
||
boolplanmodifier.RequiresReplaceIfConfigured().PlanModifyBool(context.Background(), testCase.request, resp) | ||
|
||
if diff := cmp.Diff(testCase.expected, resp); diff != "" { | ||
t.Errorf("unexpected difference: %s", diff) | ||
} | ||
}) | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
resource/schema/boolplanmodifier/requires_replace_if_func.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package boolplanmodifier | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/diag" | ||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" | ||
) | ||
|
||
// RequiresReplaceIfFunc is a conditional function used in the RequiresReplaceIf | ||
// plan modifier to determine whether the attribute requires replacement. | ||
type RequiresReplaceIfFunc func(context.Context, planmodifier.BoolRequest, *RequiresReplaceIfFuncResponse) | ||
|
||
// RequiresReplaceIfFuncResponse is the response type for a RequiresReplaceIfFunc. | ||
type RequiresReplaceIfFuncResponse struct { | ||
// Diagnostics report errors or warnings related to this logic. An empty | ||
// or unset slice indicates success, with no warnings or errors generated. | ||
Diagnostics diag.Diagnostics | ||
|
||
// RequiresReplace should be enabled if the resource should be replaced. | ||
RequiresReplace bool | ||
} |
Oops, something went wrong.