Skip to content

Commit

Permalink
adjust server implementation to always keep prior state
Browse files Browse the repository at this point in the history
  • Loading branch information
austinvalle committed Nov 12, 2024
1 parent bf1f1f7 commit e5b632d
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 41 deletions.
6 changes: 4 additions & 2 deletions echoprovider/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,10 @@ func (e *echoProviderServer) PlanResourceChange(ctx context.Context, req *tfprot
}, nil
}

// If we're updating and the provider config data hasn't changed, return prior state to indicate no diff
if priorState.Equal(tftypes.NewValue(echoTestSchema.ValueType(), map[string]tftypes.Value{"data": e.providerConfigData})) {
// If the echo resource has prior state, don't plan anything new as it's valid for the ephemeral data to change
// between operations and we don't want to produce constant diffs. This resource is only for testing data, which a
// single plan/apply should suffice.
if !priorState.IsNull() {
return &tfprotov6.PlanResourceChangeResponse{
PlannedState: req.PriorState,
}, nil
Expand Down
46 changes: 10 additions & 36 deletions echoprovider/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,44 +49,31 @@ func TestEchoProviderServer_primitive(t *testing.T) {
provider "echo" {
data = 200
}
resource "echo" "test_one" {}
resource "echo" "test_two" {}
`,
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectUnknownValue("echo.test_one", tfjsonpath.New("data")),
plancheck.ExpectUnknownValue("echo.test_two", tfjsonpath.New("data")),
},
},
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("echo.test_one", tfjsonpath.New("data"), knownvalue.Int64Exact(200)),
statecheck.ExpectKnownValue("echo.test_two", tfjsonpath.New("data"), knownvalue.Int64Exact(200)),
},
},
{
Config: `
provider "echo" {
data = true
}
resource "echo" "test_one" {}
resource "echo" "test_three" {}
`,
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectUnknownValue("echo.test_one", tfjsonpath.New("data")),
plancheck.ExpectUnknownValue("echo.test_three", tfjsonpath.New("data")),
},
},
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("echo.test_one", tfjsonpath.New("data"), knownvalue.Bool(true)),
},
},
{
Config: `
provider "echo" {
data = true
}
resource "echo" "test_one" {}
`,
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectEmptyPlan(),
},
statecheck.ExpectKnownValue("echo.test_three", tfjsonpath.New("data"), knownvalue.Bool(true)),
},
},
},
Expand Down Expand Up @@ -130,15 +117,15 @@ func TestEchoProviderServer_complex(t *testing.T) {
provider "echo" {
data = tomap({"key1": "hello", "key2": "world"})
}
resource "echo" "test_one" {}
resource "echo" "test_two" {}
`,
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectUnknownValue("echo.test_one", tfjsonpath.New("data")),
plancheck.ExpectUnknownValue("echo.test_two", tfjsonpath.New("data")),
},
},
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("echo.test_one", tfjsonpath.New("data"),
statecheck.ExpectKnownValue("echo.test_two", tfjsonpath.New("data"),
knownvalue.MapExact(map[string]knownvalue.Check{
"key1": knownvalue.StringExact("hello"),
"key2": knownvalue.StringExact("world"),
Expand All @@ -151,7 +138,7 @@ func TestEchoProviderServer_complex(t *testing.T) {
provider "echo" {
data = tomap({"key1": "hello", "key2": "world"})
}
resource "echo" "test_one" {}
resource "echo" "test_two" {}
`,
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
Expand Down Expand Up @@ -188,19 +175,6 @@ func TestEchoProviderServer_null(t *testing.T) {
statecheck.ExpectKnownValue("echo.test_one", tfjsonpath.New("data"), knownvalue.Null()),
},
},
{
Config: `
provider "echo" {
data = null
}
resource "echo" "test_one" {}
`,
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectEmptyPlan(),
},
},
},
},
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ After including both providers, our test step `Config` references the ephemeral
```go
func TestExampleCloudSecret(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
// .. test case setup from previous step
// .. test case setup

Steps: []resource.TestStep{
{
Expand All @@ -161,7 +161,7 @@ The `echo.test_krb` managed resource has a single computed `data` attribute, whi
```go
func TestExampleCloudSecret(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
// .. test case setup from previous step
// .. test case setup

Steps: []resource.TestStep{
{
Expand Down Expand Up @@ -194,7 +194,7 @@ You can also reference the entire ephemeral resource instance for assertions, ra
```go
func TestExampleCloudSecret(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
// .. test case setup from previous step
// .. test case setup

Steps: []resource.TestStep{
{
Expand All @@ -217,3 +217,58 @@ func TestExampleCloudSecret(t *testing.T) {
})
}
```

### Caveats with `echo` provider

Since data produced by an ephemeral resource is allowed to change between plan/apply operations, the `echo` resource has special handling to allow this data to be used in the `terraform-plugin-testing` Go module without producing confusing error messages:

* During plan, if the `echo` resource is being created, the `data` attribute will always be marked as unknown.
* During plan, if the `echo` resource already exists and is not being destroyed, prior state will always be fully preserved regardless of changes to the provider configuration. This essentially means an instance of the `echo` resource is immutable.
* During refresh, the prior state of the `echo` resource is always returned, regardless of changes to the provider configuration.

Due to this special handling, if multiple test steps are required for testing data, provider developers should create new instances of `echo` for each new test step, for example:

```go
func TestExampleCloudSecret(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
// .. test case setup

Steps: []resource.TestStep{
{
Config: `
ephemeral "examplecloud_secret" "krb" {
name = "user_one"
}
provider "echo" {
data = ephemeral.examplecloud_secret.krb
}
# First test object -> 1
resource "echo" "test_krb_one" {}
`,
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("echo.test_krb_one", tfjsonpath.New("data").AtMapKey("name"), knownvalue.StringExact("user_one")),
},
},
{
Config: `
ephemeral "examplecloud_secret" "krb" {
name = "user_two"
}
provider "echo" {
data = ephemeral.examplecloud_secret.krb
}
# New test object -> 2
resource "echo" "test_krb_two" {}
`,
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("echo.test_krb_two", tfjsonpath.New("data").AtMapKey("name"), knownvalue.StringExact("user_two")),
},
},
},
})
}
```

0 comments on commit e5b632d

Please sign in to comment.