Skip to content

Commit

Permalink
all: Add deferred action testing support (plan checks, version check,…
Browse files Browse the repository at this point in the history
… and CLI options) (#331)

* sloppy first commit!

* add version checks and tests

* add changelogs

* update terraform-plugin-go

* spelling fix

* update `terraform-json`

* switch to pointer bool value
  • Loading branch information
austinvalle committed May 17, 2024
1 parent 4c2e5cd commit cb1f2b6
Show file tree
Hide file tree
Showing 19 changed files with 717 additions and 8 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/ENHANCEMENTS-20240503-161709.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: 'helper/resource: Added `(TestCase).AdditionalCLIOptions` with `AllowDeferral`
option for plan and apply commands.'
time: 2024-05-03T16:17:09.64792-04:00
custom:
Issue: "331"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240503-161531.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'plancheck: Added `ExpectDeferredChange` and `ExpectNoDeferredChanges` checks
for experimental deferred action support.'
time: 2024-05-03T16:15:31.03438-04:00
custom:
Issue: "331"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240503-161802.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'tfversion: Added `SkipIfNotPrerelease` version check for testing experimental
features of prerelease Terraform builds.'
time: 2024-05-03T16:18:02.132794-04:00
custom:
Issue: "331"
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/hashicorp/logutils v1.0.0
github.com/hashicorp/terraform-exec v0.21.0
github.com/hashicorp/terraform-json v0.22.1
github.com/hashicorp/terraform-plugin-go v0.22.2
github.com/hashicorp/terraform-plugin-go v0.23.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0
github.com/mitchellh/go-testing-interface v1.14.1
Expand Down Expand Up @@ -56,5 +56,5 @@ require (
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.33.0 // indirect
google.golang.org/protobuf v1.34.0 // indirect
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW
github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg=
github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec=
github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A=
github.com/hashicorp/terraform-plugin-go v0.22.2 h1:5o8uveu6eZUf5J7xGPV0eY0TPXg3qpmwX9sce03Bxnc=
github.com/hashicorp/terraform-plugin-go v0.22.2/go.mod h1:drq8Snexp9HsbFZddvyLHN6LuWHHndSQg+gV+FPkcIM=
github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co=
github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 h1:qHprzXy/As0rxedphECBEQAh3R4yp6pKksKHcqZx5G8=
Expand Down Expand Up @@ -200,8 +200,8 @@ google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
26 changes: 26 additions & 0 deletions helper/resource/additional_cli_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package resource

// AdditionalCLIOptions allows an intentionally limited set of options to be passed
// to the Terraform CLI when executing test steps.
type AdditionalCLIOptions struct {
// Apply represents options to be passed to the `terraform apply` command.
Apply ApplyOptions

// Plan represents options to be passed to the `terraform plan` command.
Plan PlanOptions
}

// ApplyOptions represents options to be passed to the `terraform apply` command.
type ApplyOptions struct {
// AllowDeferral will pass the experimental `-allow-deferral` flag to the apply command.
AllowDeferral bool
}

// PlanOptions represents options to be passed to the `terraform plan` command.
type PlanOptions struct {
// AllowDeferral will pass the experimental `-allow-deferral` flag to the plan command.
AllowDeferral bool
}
4 changes: 4 additions & 0 deletions helper/resource/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,10 @@ type TestCase struct {
// set to "1", to persist any working directory files. Otherwise, this directory is
// automatically cleaned up at the end of the TestCase.
WorkingDir string

// AdditionalCLIOptions allows an intentionally limited set of options to be passed
// to the Terraform CLI when executing test steps.
AdditionalCLIOptions *AdditionalCLIOptions
}

// ExternalProvider holds information about third-party providers that should
Expand Down
23 changes: 22 additions & 1 deletion helper/resource/testing_new_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint
if step.Destroy {
opts = append(opts, tfexec.Destroy(true))
}

if c.AdditionalCLIOptions != nil && c.AdditionalCLIOptions.Plan.AllowDeferral {
opts = append(opts, tfexec.AllowDeferral(true))
}

return wd.CreatePlan(ctx, opts...)
}, wd, providers)
if err != nil {
Expand Down Expand Up @@ -168,7 +173,13 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint

// Apply the diff, creating real resources
err = runProviderCommand(ctx, t, func() error {
return wd.Apply(ctx)
var opts []tfexec.ApplyOption

if c.AdditionalCLIOptions != nil && c.AdditionalCLIOptions.Apply.AllowDeferral {
opts = append(opts, tfexec.AllowDeferral(true))
}

return wd.Apply(ctx, opts...)
}, wd, providers)
if err != nil {
if step.Destroy {
Expand Down Expand Up @@ -238,6 +249,11 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint
if step.Destroy {
opts = append(opts, tfexec.Destroy(true))
}

if c.AdditionalCLIOptions != nil && c.AdditionalCLIOptions.Plan.AllowDeferral {
opts = append(opts, tfexec.AllowDeferral(true))
}

return wd.CreatePlan(ctx, opts...)
}, wd, providers)
if err != nil {
Expand Down Expand Up @@ -302,6 +318,11 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint
opts = append(opts, tfexec.Refresh(false))
}
}

if c.AdditionalCLIOptions != nil && c.AdditionalCLIOptions.Plan.AllowDeferral {
opts = append(opts, tfexec.AllowDeferral(true))
}

return wd.CreatePlan(ctx, opts...)
}, wd, providers)
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion internal/plugintest/working_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,9 @@ func (wd *WorkingDir) CreatePlan(ctx context.Context, opts ...tfexec.PlanOption)
// successfully and the saved plan has not been cleared in the meantime then
// this will apply the saved plan. Otherwise, it will implicitly create a new
// plan and apply it.
func (wd *WorkingDir) Apply(ctx context.Context) error {
func (wd *WorkingDir) Apply(ctx context.Context, opts ...tfexec.ApplyOption) error {
args := []tfexec.ApplyOption{tfexec.Reattach(wd.reattachInfo), tfexec.Refresh(false)}
args = append(args, opts...)
if wd.HasSavedPlan() {
args = append(args, tfexec.DirOrPlan(PlanFileName))
}
Expand Down
13 changes: 13 additions & 0 deletions internal/testing/testsdk/providerserver/providerserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ type ProviderServer struct {
Provider provider.Provider
}

func (s ProviderServer) CallFunction(ctx context.Context, req *tfprotov6.CallFunctionRequest) (*tfprotov6.CallFunctionResponse, error) {
return &tfprotov6.CallFunctionResponse{}, nil
}

func (s ProviderServer) GetFunctions(ctx context.Context, req *tfprotov6.GetFunctionsRequest) (*tfprotov6.GetFunctionsResponse, error) {
return &tfprotov6.GetFunctionsResponse{}, nil
}

func (s ProviderServer) MoveResourceState(ctx context.Context, req *tfprotov6.MoveResourceStateRequest) (*tfprotov6.MoveResourceStateResponse, error) {
return &tfprotov6.MoveResourceStateResponse{}, nil
}

func (s ProviderServer) GetMetadata(ctx context.Context, request *tfprotov6.GetMetadataRequest) (*tfprotov6.GetMetadataResponse, error) {
resp := &tfprotov6.GetMetadataResponse{
ServerCapabilities: &tfprotov6.ServerCapabilities{
Expand Down Expand Up @@ -448,6 +460,7 @@ func (s ProviderServer) PlanResourceChange(ctx context.Context, req *tfprotov6.P

resp.Diagnostics = planResp.Diagnostics
resp.RequiresReplace = planResp.RequiresReplace
resp.Deferred = planResp.Deferred

if len(resp.Diagnostics) > 0 {
return resp, nil
Expand Down
14 changes: 14 additions & 0 deletions internal/testing/testsdk/providerserver/providerserver_protov5.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ type Protov5ProviderServer struct {
Provider provider.Protov5Provider
}

// CallFunction implements tfprotov5.ProviderServer.
func (s Protov5ProviderServer) CallFunction(ctx context.Context, req *tfprotov5.CallFunctionRequest) (*tfprotov5.CallFunctionResponse, error) {
return &tfprotov5.CallFunctionResponse{}, nil
}

// GetFunctions implements tfprotov5.ProviderServer.
func (s Protov5ProviderServer) GetFunctions(ctx context.Context, req *tfprotov5.GetFunctionsRequest) (*tfprotov5.GetFunctionsResponse, error) {
return &tfprotov5.GetFunctionsResponse{}, nil
}

func (s Protov5ProviderServer) MoveResourceState(ctx context.Context, req *tfprotov5.MoveResourceStateRequest) (*tfprotov5.MoveResourceStateResponse, error) {
return &tfprotov5.MoveResourceStateResponse{}, nil
}

func (s Protov5ProviderServer) GetMetadata(ctx context.Context, request *tfprotov5.GetMetadataRequest) (*tfprotov5.GetMetadataResponse, error) {
return &tfprotov5.GetMetadataResponse{}, nil
}
Expand Down
1 change: 1 addition & 0 deletions internal/testing/testsdk/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type PlanChangeRequest struct {
}

type PlanChangeResponse struct {
Deferred *tfprotov6.Deferred
Diagnostics []*tfprotov6.Diagnostic
PlannedState tftypes.Value
RequiresReplace []*tftypes.AttributePath
Expand Down
21 changes: 21 additions & 0 deletions plancheck/deferred_reason.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package plancheck

// DeferredReason is a string stored in the plan file which indicates why Terraform
// is deferring a change for a resource.
type DeferredReason string

const (
// DeferredReasonResourceConfigUnknown is used to indicate that the resource configuration
// is partially unknown and the real values need to be known before the change can be planned.
DeferredReasonResourceConfigUnknown DeferredReason = "resource_config_unknown"

// DeferredReasonProviderConfigUnknown is used to indicate that the provider configuration
// is partially unknown and the real values need to be known before the change can be planned.
DeferredReasonProviderConfigUnknown DeferredReason = "provider_config_unknown"

// DeferredReasonAbsentPrereq is used to indicate that a hard dependency has not been satisfied.
DeferredReasonAbsentPrereq DeferredReason = "absent_prereq"
)
49 changes: 49 additions & 0 deletions plancheck/expect_deferred_change.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package plancheck

import (
"context"
"fmt"
)

var _ PlanCheck = expectDeferredChange{}

type expectDeferredChange struct {
resourceAddress string
reason DeferredReason
}

// CheckPlan implements the plan check logic.
func (e expectDeferredChange) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) {
foundResource := false

for _, dc := range req.Plan.DeferredChanges {
if dc.ResourceChange == nil || e.resourceAddress != dc.ResourceChange.Address {
continue
}

if e.reason != DeferredReason(dc.Reason) {
resp.Error = fmt.Errorf("'%s' - expected %q, got deferred reason: %q", dc.ResourceChange.Address, e.reason, dc.Reason)
return
}

foundResource = true
break
}

if !foundResource {
resp.Error = fmt.Errorf("%s - No deferred changes found for resource", e.resourceAddress)
return
}
}

// ExpectDeferredChange returns a plan check that asserts that a given resource will have a
// deferred change in the plan with the given reason.
func ExpectDeferredChange(resourceAddress string, reason DeferredReason) PlanCheck {
return expectDeferredChange{
resourceAddress: resourceAddress,
reason: reason,
}
}
Loading

0 comments on commit cb1f2b6

Please sign in to comment.