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

Modifying underlying logic behind TestStep.Taint to call the terrafom taint command #1031

Merged
merged 3 commits into from Aug 15, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/1031.txt
@@ -0,0 +1,3 @@
```release-note:enhancement
helper/resource: `TestStep.Taint` now calls `terraform taint` command ([#1030](https://github.com/hashicorp/terraform-plugin-sdk/issues/1030))
bendbennett marked this conversation as resolved.
Show resolved Hide resolved
```
17 changes: 5 additions & 12 deletions helper/resource/testing_config.go
Expand Up @@ -2,31 +2,24 @@ package resource

import (
"context"
"errors"
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest"
)

func testStepTaint(ctx context.Context, state *terraform.State, step TestStep) error {
func testStepTaint(ctx context.Context, step TestStep, wd *plugintest.WorkingDir) error {
if len(step.Taint) == 0 {
return nil
}

logging.HelperResourceTrace(ctx, fmt.Sprintf("Using TestStep Taint: %v", step.Taint))

for _, p := range step.Taint {
m := state.RootModule()
if m == nil {
return errors.New("no state")
err := wd.Taint(ctx, p)
if err != nil {
return fmt.Errorf("error tainting resource: %s", err)
}
rs, ok := m.Resources[p]
if !ok {
return fmt.Errorf("resource %q not found in state", p)
}
logging.HelperResourceWarn(ctx, fmt.Sprintf("Explicitly tainting resource %q", p))
rs.Taint()
}
return nil
}
26 changes: 2 additions & 24 deletions helper/resource/testing_new.go
Expand Up @@ -8,7 +8,7 @@ import (

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

"github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging"
"github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest"
Expand Down Expand Up @@ -152,29 +152,7 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest
}

if step.Config != "" && !step.Destroy && len(step.Taint) > 0 {
var state *terraform.State

err := runProviderCommand(ctx, t, func() error {
var err error

state, err = getState(ctx, t, wd)

if err != nil {
return err
}

return nil
}, wd, providers)

if err != nil {
logging.HelperResourceError(ctx,
"TestStep error reading prior state before tainting resources",
map[string]interface{}{logging.KeyError: err},
)
t.Fatalf("TestStep %d/%d error reading prior state before tainting resources: %s", stepNumber, len(c.Steps), err)
}

err = testStepTaint(ctx, state, step)
err := testStepTaint(ctx, step, wd)

if err != nil {
logging.HelperResourceError(ctx,
Expand Down
62 changes: 62 additions & 0 deletions helper/resource/teststep_providers_test.go
Expand Up @@ -10,8 +10,10 @@ import (
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"

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

func TestStepProviderConfig(t *testing.T) {
Expand Down Expand Up @@ -342,6 +344,66 @@ func TestTest_TestStep_ExternalProviders_To_ProviderFactories_StateUpgraders(t *
})
}

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

var idOne, idTwo string

Test(t, TestCase{
ExternalProviders: map[string]ExternalProvider{
"random": {
Source: "registry.terraform.io/hashicorp/random",
VersionConstraint: "3.3.2",
},
},
Steps: []TestStep{
{
Config: `resource "random_string" "test" {
length = 10
}`,
Check: ComposeAggregateTestCheckFunc(
TestCheckResourceAttr("random_string.test", "length", "10"),
ExtractResourceAttr("random_string.test", "id", &idOne),
),
},
{
Taint: []string{"random_string.test"},
Config: `resource "random_string" "test" {
length = 10
}`,
Check: ComposeAggregateTestCheckFunc(
TestCheckResourceAttr("random_string.test", "length", "10"),
ExtractResourceAttr("random_string.test", "id", &idTwo),
),
},
},
bendbennett marked this conversation as resolved.
Show resolved Hide resolved
})

if idOne == idTwo {
t.Errorf("taint is not causing destroy-create cycle, idOne == idTwo: %s == %s", idOne, idTwo)
}
}

func ExtractResourceAttr(resourceName string, attributeName string, attributeValue *string) TestCheckFunc {
bendbennett marked this conversation as resolved.
Show resolved Hide resolved
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]

if !ok {
return fmt.Errorf("resource name %s not found in state", resourceName)
}

attrValue, ok := rs.Primary.Attributes[attributeName]

if !ok {
return fmt.Errorf("attribute %s not found in resource %s state", attributeName, resourceName)
}

*attributeValue = attrValue

return nil
}
}

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

Expand Down
12 changes: 12 additions & 0 deletions internal/plugintest/working_dir.go
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/hashicorp/terraform-exec/tfexec"
tfjson "github.com/hashicorp/terraform-json"

"github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging"
)

Expand Down Expand Up @@ -303,6 +304,17 @@ func (wd *WorkingDir) Import(ctx context.Context, resource, id string) error {
return err
}

// Taint runs terraform taint
func (wd *WorkingDir) Taint(ctx context.Context, address string) error {
logging.HelperResourceTrace(ctx, "Calling Terraform CLI taint command")

err := wd.tf.Taint(context.Background(), address)

logging.HelperResourceTrace(ctx, "Called Terraform CLI import command")
bendbennett marked this conversation as resolved.
Show resolved Hide resolved

return err
}

// Refresh runs terraform refresh
func (wd *WorkingDir) Refresh(ctx context.Context) error {
logging.HelperResourceTrace(ctx, "Calling Terraform CLI refresh command")
Expand Down
24 changes: 2 additions & 22 deletions terraform/state.go
Expand Up @@ -14,8 +14,8 @@ import (
"sync"

"github.com/hashicorp/go-cty/cty"
multierror "github.com/hashicorp/go-multierror"
uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-uuid"
"github.com/mitchellh/copystructure"

"github.com/hashicorp/terraform-plugin-sdk/v2/internal/addrs"
Expand Down Expand Up @@ -1225,26 +1225,6 @@ func (s *ResourceState) Equal(other *ResourceState) bool {
return s.Primary.Equal(other.Primary)
}

// Taint marks a resource as tainted.
func (s *ResourceState) Taint() {
s.Lock()
defer s.Unlock()

if s.Primary != nil {
s.Primary.Tainted = true
}
}

// Untaint unmarks a resource as tainted.
func (s *ResourceState) Untaint() {
s.Lock()
defer s.Unlock()

if s.Primary != nil {
s.Primary.Tainted = false
}
}

func (s *ResourceState) init() {
s.Lock()
defer s.Unlock()
Expand Down
89 changes: 0 additions & 89 deletions terraform/state_test.go
Expand Up @@ -907,95 +907,6 @@ func TestResourceStateEqual(t *testing.T) {
}
}

func TestResourceStateTaint(t *testing.T) {
cases := map[string]struct {
Input *ResourceState
Output *ResourceState
}{
"no primary": {
&ResourceState{},
&ResourceState{},
},

"primary, not tainted": {
&ResourceState{
Primary: &InstanceState{ID: "foo"},
},
&ResourceState{
Primary: &InstanceState{
ID: "foo",
Tainted: true,
},
},
},

"primary, tainted": {
&ResourceState{
Primary: &InstanceState{
ID: "foo",
Tainted: true,
},
},
&ResourceState{
Primary: &InstanceState{
ID: "foo",
Tainted: true,
},
},
},
}

for k, tc := range cases {
tc.Input.Taint()
if !reflect.DeepEqual(tc.Input, tc.Output) {
t.Fatalf(
"Failure: %s\n\nExpected: %#v\n\nGot: %#v",
k, tc.Output, tc.Input)
}
}
}

func TestResourceStateUntaint(t *testing.T) {
cases := map[string]struct {
Input *ResourceState
ExpectedOutput *ResourceState
}{
"no primary, err": {
Input: &ResourceState{},
ExpectedOutput: &ResourceState{},
},

"primary, not tainted": {
Input: &ResourceState{
Primary: &InstanceState{ID: "foo"},
},
ExpectedOutput: &ResourceState{
Primary: &InstanceState{ID: "foo"},
},
},
"primary, tainted": {
Input: &ResourceState{
Primary: &InstanceState{
ID: "foo",
Tainted: true,
},
},
ExpectedOutput: &ResourceState{
Primary: &InstanceState{ID: "foo"},
},
},
}

for k, tc := range cases {
tc.Input.Untaint()
if !reflect.DeepEqual(tc.Input, tc.ExpectedOutput) {
t.Fatalf(
"Failure: %s\n\nExpected: %#v\n\nGot: %#v",
k, tc.ExpectedOutput, tc.Input)
}
}
}

func TestInstanceStateEmpty(t *testing.T) {
cases := map[string]struct {
In *InstanceState
Expand Down