Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c7669b4
initial implementation w/ tests
austinvalle Feb 27, 2023
10de5f0
initial pre-apply assertions for RFC
austinvalle Feb 28, 2023
e611868
Merge branch 'main' into av/diff-changes
austinvalle Feb 28, 2023
c096c30
comment save
austinvalle Mar 1, 2023
72d89cc
Merge branch 'main' into av/diff-changes
austinvalle Mar 6, 2023
973cd57
RFC feedback, more impl
austinvalle Mar 6, 2023
3fbc68c
added tests for each set of plan asserts
austinvalle Mar 7, 2023
6168af1
add skeleton doc pages
austinvalle Mar 7, 2023
76bb8c2
small refactoring
austinvalle Mar 7, 2023
5311af5
updated comments
austinvalle Mar 7, 2023
d072074
add expect empty + non-empty plan asserts
austinvalle Mar 9, 2023
189dd5e
Merge branch 'main' into av/diff-changes
austinvalle Mar 9, 2023
90a5466
Merge branch 'main' into av/diff-changes
austinvalle Mar 14, 2023
4c37f36
refactored to name PlanCheck + follow req/resp model
austinvalle Mar 16, 2023
525012e
added tests for skip functionality
austinvalle Mar 16, 2023
26b4631
Merge branch 'main' into av/diff-changes
austinvalle Mar 17, 2023
ebfe825
moved plancheck package to root
austinvalle Mar 17, 2023
67a06f5
moved plancheck tests to new testing pkg
austinvalle Mar 17, 2023
bcef2c4
added context to plancheck
austinvalle Mar 17, 2023
9246bb3
replaced go-multierror with err func
austinvalle Mar 17, 2023
f1dd07c
moved skip to string
austinvalle Mar 20, 2023
b671d41
remove skip message
austinvalle Mar 20, 2023
d1be48c
removed name params from interface
austinvalle Mar 20, 2023
9959f28
added validation steps
austinvalle Mar 20, 2023
db10c90
add doc for package
austinvalle Mar 20, 2023
6c72878
moved plancheck interface under plancheck
austinvalle Mar 20, 2023
42a63a9
add initial docs for plancheck package
austinvalle Mar 20, 2023
e2060b6
add initial documentation
austinvalle Mar 20, 2023
6b8d872
remove deprecation message
austinvalle Mar 21, 2023
c3aefc4
add detailed docs to actions
austinvalle Mar 21, 2023
7024258
Merge branch 'main' into av/diff-changes
austinvalle Mar 21, 2023
e511999
added plan check doc page
austinvalle Mar 21, 2023
ebbafa8
added changelogs
austinvalle Mar 21, 2023
dbacdcf
remove nest
austinvalle Mar 22, 2023
10b8529
remove import aliases from docs
austinvalle Mar 22, 2023
6ea297a
added table with built-ins
austinvalle Mar 22, 2023
5dc04ce
swap nav + add section to point to plan checks
austinvalle Mar 22, 2023
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/ENHANCEMENTS-20230321-174020.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: 'helper/resource: Added plan check functionality to config and refresh modes
with new fields `TestStep.ConfigPlanChecks` and `TestStep.RefreshPlanChecks`'
time: 2023-03-21T17:40:20.521786-04:00
custom:
Issue: "63"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20230321-173506.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'plancheck: Introduced new `plancheck` package with interface and built-in plan
check functionality'
time: 2023-03-21T17:35:06.650327-04:00
custom:
Issue: "63"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20230321-173639.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'plancheck: Added `ExpectResourceAction` built-in plan check, which asserts
that a given resource will have a specific resource change type in the plan'
time: 2023-03-21T17:36:39.391477-04:00
custom:
Issue: "63"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20230321-173726.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'plancheck: Added `ExpectEmptyPlan` built-in plan check, which asserts that
there are no resource changes in the plan'
time: 2023-03-21T17:37:26.076041-04:00
custom:
Issue: "63"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20230321-173805.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'plancheck: Added `ExpectNonEmptyPlan` built-in plan check, which asserts that
there is at least one resource change in the plan'
time: 2023-03-21T17:38:05.815307-04:00
custom:
Issue: "63"
29 changes: 29 additions & 0 deletions helper/resource/plan_checks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package resource

import (
"context"

tfjson "github.com/hashicorp/terraform-json"
"github.com/hashicorp/terraform-plugin-testing/internal/errorshim"
"github.com/hashicorp/terraform-plugin-testing/plancheck"
"github.com/mitchellh/go-testing-interface"
)

func runPlanChecks(ctx context.Context, t testing.T, plan *tfjson.Plan, planChecks []plancheck.PlanCheck) error {
t.Helper()

var result error

for _, planCheck := range planChecks {
resp := plancheck.CheckPlanResponse{}
planCheck.CheckPlan(ctx, plancheck.CheckPlanRequest{Plan: plan}, &resp)

if resp.Error != nil {
// TODO: Once Go 1.20 is the minimum supported version for this module, replace with `errors.Join` function
// - https://github.com/hashicorp/terraform-plugin-testing/issues/99
result = errorshim.Join(result, resp.Error)
}
}

return result
}
19 changes: 19 additions & 0 deletions helper/resource/plan_checks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package resource

import (
"context"

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

var _ plancheck.PlanCheck = &planCheckSpy{}

type planCheckSpy struct {
err error
called bool
}

func (s *planCheckSpy) CheckPlan(ctx context.Context, req plancheck.CheckPlanRequest, resp *plancheck.CheckPlanResponse) {
s.called = true
resp.Error = s.err
}
37 changes: 37 additions & 0 deletions helper/resource/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/hashicorp/terraform-plugin-testing/plancheck"
"github.com/hashicorp/terraform-plugin-testing/terraform"

"github.com/hashicorp/terraform-plugin-testing/internal/addrs"
Expand Down Expand Up @@ -510,6 +511,20 @@ type TestStep struct {
// test to pass.
ExpectError *regexp.Regexp

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

// RefreshPlanChecks allows assertions to be made against the plan file at different points of a Refresh test using a plan check.
// Custom plan checks can be created by implementing the [PlanCheck] interface, or by using a PlanCheck implementation from the provided [plancheck] package
//
// [PlanCheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#PlanCheck
// [plancheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck
RefreshPlanChecks RefreshPlanChecks

// 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 @@ -679,6 +694,28 @@ type TestStep struct {
ExternalProviders map[string]ExternalProvider
}

// ConfigPlanChecks defines the different points in a Config TestStep when plan checks can be run.
type ConfigPlanChecks struct {
// PreApply runs all plan checks in the slice. This occurs before the apply of a Config test is run. This slice cannot be populated
// with TestStep.PlanOnly, as there is no PreApply plan run with that flag set. All errors by plan checks in this slice are aggregated, reported, and will result in a test failure.
PreApply []plancheck.PlanCheck

// PostApplyPreRefresh runs all plan checks in the slice. This occurs after the apply and before the refresh of a Config test is run.
// All errors by plan checks in this slice are aggregated, reported, and will result in a test failure.
PostApplyPreRefresh []plancheck.PlanCheck

// PostApplyPostRefresh runs all plan checks in the slice. This occurs after the apply and refresh of a Config test are run.
// All errors by plan checks in this slice are aggregated, reported, and will result in a test failure.
PostApplyPostRefresh []plancheck.PlanCheck
}

// RefreshPlanChecks defines the different points in a Refresh TestStep when plan checks can be run.
type RefreshPlanChecks struct {
// PostRefresh runs all plan checks in the slice. This occurs after the refresh of the Refresh test is run.
// All errors by plan checks in this slice are aggregated, reported, and will result in a test failure.
PostRefresh []plancheck.PlanCheck
}

// 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
34 changes: 34 additions & 0 deletions helper/resource/testing_new_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint
return fmt.Errorf("Error running pre-apply plan: %w", err)
}

// Run pre-apply plan checks
if len(step.ConfigPlanChecks.PreApply) > 0 {
var plan *tfjson.Plan
err = runProviderCommand(ctx, t, func() error {
var err error
plan, err = wd.SavedPlan(ctx)
return err
}, wd, providers)
if err != nil {
return fmt.Errorf("Error retrieving pre-apply plan: %w", err)
}

err = runPlanChecks(ctx, t, plan, step.ConfigPlanChecks.PreApply)
if err != nil {
return fmt.Errorf("Pre-apply plan check(s) failed:\n%w", err)
}
}

// We need to keep a copy of the state prior to destroying such
// that the destroy steps can verify their behavior in the
// check function
Expand Down Expand Up @@ -131,6 +149,14 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint
return fmt.Errorf("Error retrieving post-apply plan: %w", err)
}

// Run post-apply, pre-refresh plan checks
if len(step.ConfigPlanChecks.PostApplyPreRefresh) > 0 {
err = runPlanChecks(ctx, t, plan, step.ConfigPlanChecks.PostApplyPreRefresh)
if err != nil {
return fmt.Errorf("Post-apply, pre-refresh plan check(s) failed:\n%w", err)
}
}

if !planIsEmpty(plan) && !step.ExpectNonEmptyPlan {
var stdout string
err = runProviderCommand(ctx, t, func() error {
Expand Down Expand Up @@ -174,6 +200,14 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint
return fmt.Errorf("Error retrieving second post-apply plan: %w", err)
}

// Run post-apply, post-refresh plan checks
if len(step.ConfigPlanChecks.PostApplyPostRefresh) > 0 {
err = runPlanChecks(ctx, t, plan, step.ConfigPlanChecks.PostApplyPostRefresh)
if err != nil {
return fmt.Errorf("Post-apply, post-refresh plan check(s) failed:\n%w", err)
}
}

// check if plan is empty
if !planIsEmpty(plan) && !step.ExpectNonEmptyPlan {
var stdout string
Expand Down
Loading