Skip to content

Commit

Permalink
internal/acctest: custom framework disappears state function (hashico…
Browse files Browse the repository at this point in the history
…rp#36410)

This change adds a new helper function to the `acctest` package,
`CheckFrameworkResourceDisappearsWithStateFunc`, that allows for a custom
function to be provided which constructs the plugin framework state from
the Terraform instance state. This is helpful for resoures where the
default behavior of the `CheckFrameworkResourceDisappears` helper (which
only copies root level string arguments into the plugin framework state)
results in missing inputs for the delete operation. Cases where nested
or non-string arguments must be copied can now rely on custom state functions
local to the service testing package.
  • Loading branch information
jar-b committed Mar 15, 2024
1 parent 896e539 commit dbccf03
Showing 1 changed file with 76 additions and 36 deletions.
112 changes: 76 additions & 36 deletions internal/acctest/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,55 +21,95 @@ import (

// Terraform Plugin Framework variants of standard acceptance test helpers.

func deleteFrameworkResource(ctx context.Context, factory func(context.Context) (fwresource.ResourceWithConfigure, error), is *terraform.InstanceState, meta interface{}) error {
resource, err := factory(ctx)

if err != nil {
return err
}

resource.Configure(ctx, fwresource.ConfigureRequest{ProviderData: meta}, &fwresource.ConfigureResponse{})
// CheckFrameworkResourceDisappears destroys an existing resource out of band
//
// By default, this check will only copy root-level string arguments into the state
// used to delete the remote resource. For resources requiring nested or non-string
// arguments to be available for the delete operation, consider using
// CheckFrameworkResourceDisappearsWithStateFunc with a custom state function
// instead.
func CheckFrameworkResourceDisappears(
ctx context.Context,
provo *schema.Provider,
factory func(context.Context) (fwresource.ResourceWithConfigure, error),
n string,
) resource.TestCheckFunc {
return deleteFrameworkResource(ctx, provo, factory, n, rootStringStateFunc())
}

schemaResp := fwresource.SchemaResponse{}
resource.Schema(ctx, fwresource.SchemaRequest{}, &schemaResp)
// CheckFrameworkResourceDisappearsWithStateFunc destroys an existing resource
// out of band, constructing state from the provided state function
func CheckFrameworkResourceDisappearsWithStateFunc(
ctx context.Context,
provo *schema.Provider,
factory func(context.Context) (fwresource.ResourceWithConfigure, error),
n string,
stateFunc func(ctx context.Context, state *tfsdk.State, is *terraform.InstanceState) error,
) resource.TestCheckFunc {
return deleteFrameworkResource(ctx, provo, factory, n, stateFunc)
}

// Construct a simple Framework State that contains just top-level attributes.
state := tfsdk.State{
Raw: tftypes.NewValue(schemaResp.Schema.Type().TerraformType(ctx), nil),
Schema: schemaResp.Schema,
}
func deleteFrameworkResource(
ctx context.Context,
provo *schema.Provider,
factory func(context.Context) (fwresource.ResourceWithConfigure, error),
n string,
stateFunc func(ctx context.Context, state *tfsdk.State, is *terraform.InstanceState) error,
) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("resource not found: %s", n)
}

for name, v := range is.Attributes {
if name == "%" || strings.Contains(name, ".") {
continue
if rs.Primary.ID == "" {
return fmt.Errorf("resource ID missing: %s", n)
}

if err := fwdiag.DiagnosticsError(state.SetAttribute(ctx, path.Root(name), v)); err != nil {
log.Printf("[WARN] %s(%s): %s", name, v, err)
resource, err := factory(ctx)
if err != nil {
return err
}
}

response := fwresource.DeleteResponse{}
resource.Delete(ctx, fwresource.DeleteRequest{State: state}, &response)
resource.Configure(ctx, fwresource.ConfigureRequest{ProviderData: provo.Meta()}, &fwresource.ConfigureResponse{})

if response.Diagnostics.HasError() {
return fwdiag.DiagnosticsError(response.Diagnostics)
}
schemaResp := fwresource.SchemaResponse{}
resource.Schema(ctx, fwresource.SchemaRequest{}, &schemaResp)

return nil
}
// Construct a simple Framework State that contains just top-level attributes.
state := tfsdk.State{
Raw: tftypes.NewValue(schemaResp.Schema.Type().TerraformType(ctx), nil),
Schema: schemaResp.Schema,
}

func CheckFrameworkResourceDisappears(ctx context.Context, provo *schema.Provider, factory func(context.Context) (fwresource.ResourceWithConfigure, error), n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("resource not found: %s", n)
err = stateFunc(ctx, &state, rs.Primary)
if err != nil {
return err
}

if rs.Primary.ID == "" {
return fmt.Errorf("resource ID missing: %s", n)
response := fwresource.DeleteResponse{}
resource.Delete(ctx, fwresource.DeleteRequest{State: state}, &response)

if response.Diagnostics.HasError() {
return fwdiag.DiagnosticsError(response.Diagnostics)
}

return deleteFrameworkResource(ctx, factory, rs.Primary, provo.Meta())
return nil
}
}

// rootStringStateFunc copies root-level string arguments into `state`
func rootStringStateFunc() func(ctx context.Context, state *tfsdk.State, is *terraform.InstanceState) error {
return func(ctx context.Context, state *tfsdk.State, is *terraform.InstanceState) error {
for name, v := range is.Attributes {
if name == "%" || strings.Contains(name, ".") {
continue
}

if err := fwdiag.DiagnosticsError(state.SetAttribute(ctx, path.Root(name), v)); err != nil {
log.Printf("[WARN] %s(%s): %s", name, v, err)
}
}
return nil
}
}

0 comments on commit dbccf03

Please sign in to comment.