Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding State Checks for Known Type and Value, and Sensitive Checks #273

Closed
wants to merge 54 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
34cee4a
Add KnownValue interface and types (#243)
bendbennett Dec 15, 2023
6915f0e
Add ExpectKnownValue plan check (#243)
bendbennett Dec 15, 2023
f503594
Handling different permutations for equality checking of interface ty…
bendbennett Dec 15, 2023
4f8eed9
Adding tests for missing resource, and attribute value null (#243)
bendbennett Dec 15, 2023
a1f35f6
Adding plan checks for known output value and known output value at p…
bendbennett Dec 18, 2023
853a3e4
Adding documentation (#243)
bendbennett Dec 18, 2023
f88c44b
Merge branch 'main' into bendbennett/issues-243
bendbennett Dec 18, 2023
0ccf758
Adding changelog entries (#243)
bendbennett Dec 18, 2023
f161203
Adding TerraformVersionChecks (#243)
bendbennett Dec 18, 2023
e13bdaa
Modifying to handle numerical values returned as json.Number for tfjs…
bendbennett Dec 20, 2023
916674e
Merge remote-tracking branch 'origin/main' into bendbennett/issues-243
bendbennett Dec 20, 2023
450339d
Renaming known value constructors (#243)
bendbennett Jan 2, 2024
31cbf09
Refactoring to Check interface (#243)
bendbennett Jan 3, 2024
73a5300
Merge remote-tracking branch 'origin/main' into bendbennett/issues-243
bendbennett Jan 3, 2024
3f14259
Linting (#243)
bendbennett Jan 3, 2024
5e61524
Modifying known value check error messages and tests (#243)
bendbennett Jan 3, 2024
41eb4de
Updating tests for ExpectKnownValue, ExpectKnownOutputValue and Expec…
bendbennett Jan 3, 2024
94bdfa7
Adding changelog entry to note the switch to using json.Number for nu…
bendbennett Jan 4, 2024
f09f7e9
Remove reference to state checks (#243)
bendbennett Jan 4, 2024
7c5c177
Moving concepts under title and removing reference to Framework types…
bendbennett Jan 4, 2024
1638808
Updating Go doc comments to clarify usage of partial equality and rem…
bendbennett Jan 4, 2024
213b81a
Modifying known-values.mdx page description (#243)
bendbennett Jan 4, 2024
fa8f125
Restructuring and updating references to knownvalue.Check (#243)
bendbennett Jan 4, 2024
91417e1
Adding individual docs pages for each type of known value check (#243)
bendbennett Jan 4, 2024
463c0e3
Removing references to num elements (#243)
bendbennett Jan 4, 2024
a661490
Removing references to state (#243)
bendbennett Jan 4, 2024
6b626f4
Adding docs page for custom known value checks (#243)
bendbennett Jan 4, 2024
ad071ae
Fixing error message (#243)
bendbennett Jan 5, 2024
efb0b98
Refactoring to accomodate custom known value checks in ExpectKnownVal…
bendbennett Jan 5, 2024
6accadc
Adding StateCheck interface (#266)
bendbennett Jan 9, 2024
50b202f
Adding validation to ensure state checks are only defined for config …
bendbennett Jan 9, 2024
64d470d
Adding ExpectKnownValue state check (#266)
bendbennett Jan 9, 2024
8f6fe6d
Adding ExpectKnownOutputValue state check (#266)
bendbennett Jan 9, 2024
a90b4d9
Adding ExpectKnownOutputValueAtPath state check (#266)
bendbennett Jan 9, 2024
26efbb8
Modifying ExpectKnown<Value|OutputValue|OutputValueAtPath> to allow f…
bendbennett Jan 10, 2024
7bc660c
Adding ExpectSensitiveValue state check (#266)
bendbennett Jan 10, 2024
483b364
Adding documentation for state checks and null known value check type…
bendbennett Jan 10, 2024
3880516
Adding to the documentation for the custom known value check (#266)
bendbennett Jan 11, 2024
9e5cde1
Apply suggestions from code review
bendbennett Jan 11, 2024
d14429e
Unexporting types that implement known value check (#266)
bendbennett Jan 11, 2024
f1b9656
Document usage of 512-bit precision in the number known value check (…
bendbennett Jan 11, 2024
ed9d236
Adding attribute or output path to error message (#266)
bendbennett Jan 11, 2024
9bdf445
Replacing alias in example code (#266)
bendbennett Jan 11, 2024
0849854
Rename file (#266)
bendbennett Jan 11, 2024
4f283cf
Merge remote-tracking branch 'refs/remotes/origin/bendbennett/issues-…
bendbennett Jan 11, 2024
831ba05
Adding changelog entries (#266)
bendbennett Jan 11, 2024
c3ce2f2
Merge remote-tracking branch 'origin/bendbennett/issues-243' into ben…
bendbennett Jan 11, 2024
39cc063
Refactoring to use updated known value check types (#266)
bendbennett Jan 11, 2024
b4ec27f
Merge remote-tracking branch 'origin/main' into bendbennett/issues-266
bendbennett Jan 15, 2024
69f516d
Removing unused known value check types (#266)
bendbennett Jan 15, 2024
66f9b5c
Correcting documentation for revised naming of known value check type…
bendbennett Jan 15, 2024
6c773cc
Renaming nul known value check (#266)
bendbennett Jan 15, 2024
05d7c91
Fixing tests (#266)
bendbennett Jan 15, 2024
8b158ae
Adding address and path to state check errors (#266)
bendbennett Jan 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240111-142126.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'statecheck: Introduced new `statecheck` package with interface and built-in
state check functionality'
time: 2024-01-11T14:21:26.261094Z
custom:
Issue: "273"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240111-142223.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'statecheck: Added `ExpectKnownValue` state check, which asserts that a given
resource attribute has a defined type, and value'
time: 2024-01-11T14:22:23.072321Z
custom:
Issue: "273"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240111-142314.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'statecheck: Added `ExpectKnownOutputValue` state check, which asserts that
a given output value has a defined type, and value'
time: 2024-01-11T14:23:14.025585Z
custom:
Issue: "273"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240111-142353.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'statecheck: Added `ExpectKnownOutputValueAtPath` plan check, which asserts
that a given output value at a specified path has a defined type, and value'
time: 2024-01-11T14:23:53.633255Z
custom:
Issue: "273"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240111-142544.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'statecheck: Added `ExpectSensitiveValue` built-in state check, which asserts
that a given attribute has a sensitive value'
time: 2024-01-11T14:25:44.598583Z
custom:
Issue: "273"
29 changes: 29 additions & 0 deletions helper/resource/state_checks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package resource

import (
"context"
"errors"

tfjson "github.com/hashicorp/terraform-json"
"github.com/mitchellh/go-testing-interface"

"github.com/hashicorp/terraform-plugin-testing/statecheck"
)

func runStateChecks(ctx context.Context, t testing.T, state *tfjson.State, stateChecks []statecheck.StateCheck) error {
t.Helper()

var result []error

for _, stateCheck := range stateChecks {
resp := statecheck.CheckStateResponse{}
stateCheck.CheckState(ctx, statecheck.CheckStateRequest{State: state}, &resp)

result = append(result, resp.Error)
}

return errors.Join(result...)
}
22 changes: 22 additions & 0 deletions helper/resource/state_checks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package resource

import (
"context"

"github.com/hashicorp/terraform-plugin-testing/statecheck"
)

var _ statecheck.StateCheck = &stateCheckSpy{}

type stateCheckSpy struct {
err error
called bool
}

func (s *stateCheckSpy) CheckState(ctx context.Context, req statecheck.CheckStateRequest, resp *statecheck.CheckStateResponse) {
s.called = true
resp.Error = s.err
}
12 changes: 12 additions & 0 deletions helper/resource/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/hashicorp/terraform-plugin-testing/config"
"github.com/hashicorp/terraform-plugin-testing/plancheck"
"github.com/hashicorp/terraform-plugin-testing/statecheck"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/hashicorp/terraform-plugin-testing/tfversion"

Expand Down Expand Up @@ -590,6 +591,13 @@ type TestStep struct {
// [plancheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck
RefreshPlanChecks RefreshPlanChecks

// ConfigStateChecks allow assertions to be made against the state file at different points of a Config (apply) test using a state check.
// Custom state checks can be created by implementing the [StateCheck] interface, or by using a StateCheck implementation from the provided [statecheck] package
//
// [StateCheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#StateCheck
// [statecheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck
ConfigStateChecks ConfigStateChecks

// PlanOnly can be set to only run `plan` with this configuration, and not
// actually apply it. This is useful for ensuring config changes result in
// no-op plans
Expand Down Expand Up @@ -795,6 +803,10 @@ type RefreshPlanChecks struct {
PostRefresh []plancheck.PlanCheck
}

// ConfigStateChecks runs all state checks in the slice. This occurs after the apply and refresh of a Config test are run.
// All errors by state checks in this slice are aggregated, reported, and will result in a test failure.
type ConfigStateChecks []statecheck.StateCheck

// ParallelTest performs an acceptance test on a resource, allowing concurrency
// with other ParallelTest. The number of concurrent tests is controlled by the
// "go test" command -parallel flag.
Expand Down
20 changes: 20 additions & 0 deletions helper/resource/testing_new_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,26 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint
}
}

// Run post-apply, post-refresh state checks
if len(step.ConfigStateChecks) > 0 {
var state *tfjson.State

err = runProviderCommand(ctx, t, func() error {
var err error
state, err = wd.State(ctx)
return err
}, wd, providers)

if err != nil {
return fmt.Errorf("Error retrieving post-apply, post-refresh state: %w", err)
}

err = runStateChecks(ctx, t, state, step.ConfigStateChecks)
if err != nil {
return fmt.Errorf("Post-apply refresh state check(s) failed:\n%w", err)
}
}

// check if plan is empty
if !planIsEmpty(plan, helper.TerraformVersion()) && !step.ExpectNonEmptyPlan {
var stdout string
Expand Down
124 changes: 124 additions & 0 deletions helper/resource/testing_new_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"

"github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider"
"github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver"
"github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource"
Expand Down Expand Up @@ -717,3 +718,126 @@ func Test_ConfigPlanChecks_PostApplyPostRefresh_Errors(t *testing.T) {
},
})
}

func Test_ConfigStateChecks_Called(t *testing.T) {
t.Parallel()

spy1 := &stateCheckSpy{}
spy2 := &stateCheckSpy{}
UnitTest(t, TestCase{
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_0_0), // ProtoV6ProviderFactories
},
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"test": providerserver.NewProviderServer(testprovider.Provider{
Resources: map[string]testprovider.Resource{
"test_resource": {
CreateResponse: &resource.CreateResponse{
NewState: tftypes.NewValue(
tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"id": tftypes.String,
},
},
map[string]tftypes.Value{
"id": tftypes.NewValue(tftypes.String, "test"),
},
),
},
SchemaResponse: &resource.SchemaResponse{
Schema: &tfprotov6.Schema{
Block: &tfprotov6.SchemaBlock{
Attributes: []*tfprotov6.SchemaAttribute{
{
Name: "id",
Type: tftypes.String,
Computed: true,
},
},
},
},
},
},
},
}),
},
Steps: []TestStep{
{
Config: `resource "test_resource" "test" {}`,
ConfigStateChecks: ConfigStateChecks{
spy1,
spy2,
},
},
},
})

if !spy1.called {
t.Error("expected ConfigStateChecks spy1 to be called at least once")
}

if !spy2.called {
t.Error("expected ConfigStateChecks spy2 to be called at least once")
}
}

func Test_ConfigStateChecks_Errors(t *testing.T) {
t.Parallel()

spy1 := &stateCheckSpy{}
spy2 := &stateCheckSpy{
err: errors.New("spy2 check failed"),
}
spy3 := &stateCheckSpy{
err: errors.New("spy3 check failed"),
}
UnitTest(t, TestCase{
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_0_0), // ProtoV6ProviderFactories
},
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"test": providerserver.NewProviderServer(testprovider.Provider{
Resources: map[string]testprovider.Resource{
"test_resource": {
CreateResponse: &resource.CreateResponse{
NewState: tftypes.NewValue(
tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"id": tftypes.String,
},
},
map[string]tftypes.Value{
"id": tftypes.NewValue(tftypes.String, "test"),
},
),
},
SchemaResponse: &resource.SchemaResponse{
Schema: &tfprotov6.Schema{
Block: &tfprotov6.SchemaBlock{
Attributes: []*tfprotov6.SchemaAttribute{
{
Name: "id",
Type: tftypes.String,
Computed: true,
},
},
},
},
},
},
},
}),
},
Steps: []TestStep{
{
Config: `resource "test_resource" "test" {}`,
ConfigStateChecks: ConfigStateChecks{
spy1,
spy2,
spy3,
},
ExpectError: regexp.MustCompile(`.*?(spy2 check failed)\n.*?(spy3 check failed)`),
},
},
})
}
8 changes: 7 additions & 1 deletion helper/resource/teststep_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
// testStepValidateRequest contains data for the (TestStep).validate() method.
type testStepValidateRequest struct {
// StepConfiguration contains the TestStep configuration derived from
// TestStep.Config or TestStep.ConfigDirectory.
// TestStep.Config, TestStep.ConfigDirectory, or TestStep.ConfigFile.
StepConfiguration teststep.Config

// StepNumber is the index of the TestStep in the TestCase.Steps.
Expand Down Expand Up @@ -235,5 +235,11 @@ func (s TestStep) validate(ctx context.Context, req testStepValidateRequest) err
return err
}

if len(s.ConfigStateChecks) > 0 && req.StepConfiguration == nil {
err := fmt.Errorf("TestStep ConfigStateChecks must only be specified with Config, ConfigDirectory or ConfigFile")
logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err})
return err
}

return nil
}
10 changes: 10 additions & 0 deletions helper/resource/teststep_validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,16 @@ func TestTestStepValidate(t *testing.T) {
testStepValidateRequest: testStepValidateRequest{TestCaseHasProviders: true},
expectedError: errors.New("TestStep ConfigPlanChecks.PostApplyPostRefresh must only be specified with Config"),
},
"configstatechecks-not-config-mode": {
testStep: TestStep{
ConfigStateChecks: ConfigStateChecks{
&stateCheckSpy{},
},
RefreshState: true,
},
testStepValidateRequest: testStepValidateRequest{TestCaseHasProviders: true},
expectedError: errors.New("TestStep ConfigStateChecks must only be specified with Config"),
},
"refreshplanchecks-postrefresh-not-refresh-mode": {
testStep: TestStep{
RefreshPlanChecks: RefreshPlanChecks{
Expand Down
32 changes: 32 additions & 0 deletions knownvalue/null.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package knownvalue

import (
"fmt"
)

var _ Check = nullExact{}

type nullExact struct{}

// CheckValue determines whether the passed value is of nil.
func (v nullExact) CheckValue(other any) error {
if other != nil {
return fmt.Errorf("expected value nil for NullExact check, got: %T", other)
}

return nil
}

// String returns the string representation of nil.
func (v nullExact) String() string {
return "nil"
}

// NullExact returns a Check for asserting equality nil
// and the value passed to the CheckValue method.
func NullExact() nullExact {
return nullExact{}
}
Loading
Loading