From dbccf03ad2ecf74ac54987302ad3384a77481883 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Fri, 15 Mar 2024 14:54:22 -0400 Subject: [PATCH] internal/acctest: custom framework disappears state function (#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. --- internal/acctest/framework.go | 112 +++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 36 deletions(-) diff --git a/internal/acctest/framework.go b/internal/acctest/framework.go index 74ed7e7719c..2054f0f42f6 100644 --- a/internal/acctest/framework.go +++ b/internal/acctest/framework.go @@ -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 } }