-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
all: Add ephemeral resource schema and lifecycle tests (#283)
* echo resource * ephemeral resource with tests * move echo provider to plugin testing * update with testing echo provider * switch to constants * constants * rename files * added basic ephemeral schema test * add protov6 ephemeral resource test * receiver variable * add lifecycle tests * add nested attributes + dynamics to ephemeral schema test * update interface checks * add copyright headers * update resource type name * use alpha skip for deferred action tests * update plugin testing dep * update to latest testing commit with new server behavior * switch to using depends_on * share the data path from a variable * adjust the newly fixed RC1 tests + adjust test assertion order
- Loading branch information
1 parent
f9d3212
commit 384a2a9
Showing
15 changed files
with
2,929 additions
and
18 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
107 changes: 107 additions & 0 deletions
107
internal/framework5provider/ephemeral_lifecycle_resource.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,107 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package framework | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/ephemeral" | ||
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema" | ||
"github.com/hashicorp/terraform-plugin-framework/path" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
) | ||
|
||
var ( | ||
_ ephemeral.EphemeralResourceWithConfigure = &EphemeralLifecycleResource{} | ||
_ ephemeral.EphemeralResourceWithRenew = &EphemeralLifecycleResource{} | ||
_ ephemeral.EphemeralResourceWithClose = &EphemeralLifecycleResource{} | ||
) | ||
|
||
func NewEphemeralLifecycleResource() ephemeral.EphemeralResource { | ||
return &EphemeralLifecycleResource{} | ||
} | ||
|
||
// EphemeralLifecycleResource is for testing the ephemeral resource lifecycle (Open, Renew, Close) | ||
type EphemeralLifecycleResource struct { | ||
spyClient *EphemeralResourceSpyClient | ||
} | ||
|
||
type EphemeralLifecycleResourceModel struct { | ||
Name types.String `tfsdk:"name"` | ||
Token types.String `tfsdk:"token"` | ||
} | ||
|
||
func (e *EphemeralLifecycleResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { | ||
resp.TypeName = req.ProviderTypeName + "_lifecycle" | ||
} | ||
|
||
func (e *EphemeralLifecycleResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { | ||
resp.Schema = schema.Schema{ | ||
Attributes: map[string]schema.Attribute{ | ||
"name": schema.StringAttribute{ | ||
Required: true, | ||
}, | ||
"token": schema.StringAttribute{ | ||
Computed: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func (e *EphemeralLifecycleResource) Configure(ctx context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) { | ||
if req.ProviderData == nil { | ||
return | ||
} | ||
|
||
spyClient, ok := req.ProviderData.(*EphemeralResourceSpyClient) | ||
|
||
if !ok { | ||
resp.Diagnostics.AddError( | ||
"Unexpected Ephemeral Resource Configure Type", | ||
fmt.Sprintf("Expected *EphemeralResourceSpyClient, got: %T.", req.ProviderData), | ||
) | ||
|
||
return | ||
} | ||
|
||
e.spyClient = spyClient | ||
} | ||
|
||
func (e *EphemeralLifecycleResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) { | ||
var data EphemeralLifecycleResourceModel | ||
|
||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
if data.Name.IsUnknown() { | ||
resp.Diagnostics.AddAttributeError( | ||
path.Root("name"), | ||
"Unknown value encountered in Open lifecycle handler", | ||
`The "name" attribute should never be unknown, Terraform core should skip executing the Open lifecycle handler until the value becomes known.`, | ||
) | ||
return | ||
} | ||
|
||
data.Token = types.StringValue("fake-token-12345") | ||
|
||
// Renew in 5 seconds | ||
resp.RenewAt = time.Now().Add(5 * time.Second) | ||
|
||
resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...) | ||
} | ||
|
||
func (e *EphemeralLifecycleResource) Renew(ctx context.Context, req ephemeral.RenewRequest, resp *ephemeral.RenewResponse) { | ||
e.spyClient.Renew() | ||
|
||
// Renew again in 5 seconds | ||
resp.RenewAt = time.Now().Add(5 * time.Second) | ||
} | ||
|
||
func (e *EphemeralLifecycleResource) Close(ctx context.Context, req ephemeral.CloseRequest, resp *ephemeral.CloseResponse) { | ||
e.spyClient.Close() | ||
} |
120 changes: 120 additions & 0 deletions
120
internal/framework5provider/ephemeral_lifecycle_resource_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,120 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package framework | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/providerserver" | ||
"github.com/hashicorp/terraform-plugin-go/tfprotov5" | ||
"github.com/hashicorp/terraform-plugin-go/tfprotov6" | ||
"github.com/hashicorp/terraform-plugin-testing/echoprovider" | ||
"github.com/hashicorp/terraform-plugin-testing/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-testing/knownvalue" | ||
"github.com/hashicorp/terraform-plugin-testing/statecheck" | ||
"github.com/hashicorp/terraform-plugin-testing/terraform" | ||
"github.com/hashicorp/terraform-plugin-testing/tfversion" | ||
) | ||
|
||
// This test is a smoke test for the ephemeral resource lifecycle (Open, Renew, and Close). | ||
func TestEphemeralLifecycleResource_basic(t *testing.T) { | ||
t.Parallel() | ||
|
||
spyClient := &EphemeralResourceSpyClient{} | ||
resource.UnitTest(t, resource.TestCase{ | ||
// Ephemeral resources are only available in 1.10 and later | ||
TerraformVersionChecks: []tfversion.TerraformVersionCheck{ | ||
tfversion.SkipBelow(tfversion.Version1_10_0), | ||
}, | ||
ExternalProviders: map[string]resource.ExternalProvider{ | ||
"time": { | ||
Source: "hashicorp/time", | ||
}, | ||
}, | ||
ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ | ||
"framework": providerserver.NewProtocol5WithError(NewWithEphemeralSpy(spyClient)), | ||
}, | ||
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ | ||
"echo": echoprovider.NewProviderServer(), | ||
}, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: ` | ||
ephemeral "framework_lifecycle" "test" { | ||
name = "John Doe" | ||
} | ||
resource "time_sleep" "wait_20_seconds" { | ||
depends_on = [ephemeral.framework_lifecycle.test] | ||
create_duration = "20s" | ||
}`, | ||
}, | ||
}, | ||
CheckDestroy: func(_ *terraform.State) error { | ||
// We only really care that renew was being invoked multiple times, it should always be 4 invocations (with no skew), but we'll give a little leeway here. | ||
if spyClient.RenewInvocations() < 3 { | ||
t.Errorf("Renew lifecycle handler should have been executed at least 3 times (5s renewals in 20s), but was only executed %d times", spyClient.RenewInvocations()) | ||
} | ||
|
||
// Close will be invoked 6 times (due to all of the planning/refreshing of the testing framework), but we only care that it was executed once. | ||
if spyClient.CloseInvocations() < 1 { | ||
t.Errorf("Close lifecycle handler should have been executed at least once") | ||
} | ||
|
||
return nil | ||
}, | ||
}) | ||
} | ||
|
||
// This test ensures that Terraform will skip invoking an ephemeral resource when unknown values are present in configuration. | ||
// The framework_lifecycle ephemeral resource will return a diagnostic if an unknown value is encountered in "name". | ||
func TestEphemeralLifecycleResource_SkipWithUnknown(t *testing.T) { | ||
t.Parallel() | ||
|
||
resource.UnitTest(t, resource.TestCase{ | ||
// Ephemeral resources are only available in 1.10 and later | ||
TerraformVersionChecks: []tfversion.TerraformVersionCheck{ | ||
tfversion.SkipBelow(tfversion.Version1_10_0), | ||
}, | ||
ExternalProviders: map[string]resource.ExternalProvider{ | ||
"random": { | ||
Source: "hashicorp/random", | ||
}, | ||
}, | ||
ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ | ||
"framework": providerserver.NewProtocol5WithError(New()), | ||
}, | ||
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ | ||
"echo": echoprovider.NewProviderServer(), | ||
}, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: addEchoToEphemeralLifecycleConfig(` | ||
resource "random_string" "str" { | ||
length = 12 | ||
} | ||
ephemeral "framework_lifecycle" "test" { | ||
name = "John ${random_string.str.result}" | ||
}`), | ||
ConfigStateChecks: []statecheck.StateCheck{ | ||
statecheck.ExpectKnownValue("echo.lifecycle_test", echoDataPath.AtMapKey("name"), knownvalue.StringRegexp(regexp.MustCompile(`^John\s.{12}$`))), | ||
statecheck.ExpectKnownValue("echo.lifecycle_test", echoDataPath.AtMapKey("token"), knownvalue.StringExact("fake-token-12345")), | ||
}, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
// Adds the test echo provider to enable using state checks with ephemeral resources | ||
func addEchoToEphemeralLifecycleConfig(cfg string) string { | ||
return fmt.Sprintf(` | ||
%s | ||
provider "echo" { | ||
data = ephemeral.framework_lifecycle.test | ||
} | ||
resource "echo" "lifecycle_test" {} | ||
`, cfg) | ||
} |
30 changes: 30 additions & 0 deletions
30
internal/framework5provider/ephemeral_resource_spy_client.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,30 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package framework | ||
|
||
// EphemeralResourceSpyClient is used in tests to verify that an ephemeral resource lifecycle handler has been executed. | ||
type EphemeralResourceSpyClient struct { | ||
renewInvocations int | ||
closeInvocations int | ||
} | ||
|
||
// Renew will increment the number of invocations for this instance, which can be retrieved with the `RenewInvocations` method | ||
func (e *EphemeralResourceSpyClient) Renew() { | ||
e.renewInvocations++ | ||
} | ||
|
||
// RenewInvocations returns the number of times the `Renew` method has been called on this instance. | ||
func (e *EphemeralResourceSpyClient) RenewInvocations() int { | ||
return e.renewInvocations | ||
} | ||
|
||
// Close will increment the number of invocations for this instance, which can be retrieved with the `CloseInvocations` method | ||
func (e *EphemeralResourceSpyClient) Close() { | ||
e.closeInvocations++ | ||
} | ||
|
||
// CloseInvocations returns the number of times the `Close` method has been called on this instance. | ||
func (e *EphemeralResourceSpyClient) CloseInvocations() int { | ||
return e.closeInvocations | ||
} |
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
Oops, something went wrong.