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

all: Add deferred action testing support (plan checks, version check, and CLI options) #331

Merged
merged 12 commits into from
May 17, 2024
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
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