Skip to content

Commit 989becd

Browse files
bendbennettbflad
andauthored
Modifying underlying logic behind TestStep.Taint to call the terrafom taint command (#1031)
* Modifying underlying logic behind TestStep.Taint to call the terraform taint command (#1030) * Apply suggestions from code review Co-authored-by: Brian Flad <[email protected]> * Adding time import for test (#1030) Co-authored-by: Brian Flad <[email protected]>
1 parent 1fdf154 commit 989becd

File tree

7 files changed

+96
-147
lines changed

7 files changed

+96
-147
lines changed

.changelog/1031.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
helper/resource: Fixed `TestStep` type `Taint` field usage to properly recreate resources
3+
```

helper/resource/testing_config.go

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,24 @@ package resource
22

33
import (
44
"context"
5-
"errors"
65
"fmt"
76

87
"github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging"
9-
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest"
109
)
1110

12-
func testStepTaint(ctx context.Context, state *terraform.State, step TestStep) error {
11+
func testStepTaint(ctx context.Context, step TestStep, wd *plugintest.WorkingDir) error {
1312
if len(step.Taint) == 0 {
1413
return nil
1514
}
1615

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

1918
for _, p := range step.Taint {
20-
m := state.RootModule()
21-
if m == nil {
22-
return errors.New("no state")
19+
err := wd.Taint(ctx, p)
20+
if err != nil {
21+
return fmt.Errorf("error tainting resource: %s", err)
2322
}
24-
rs, ok := m.Resources[p]
25-
if !ok {
26-
return fmt.Errorf("resource %q not found in state", p)
27-
}
28-
logging.HelperResourceWarn(ctx, fmt.Sprintf("Explicitly tainting resource %q", p))
29-
rs.Taint()
3023
}
3124
return nil
3225
}

helper/resource/testing_new.go

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
"github.com/davecgh/go-spew/spew"
1010
tfjson "github.com/hashicorp/terraform-json"
11-
testing "github.com/mitchellh/go-testing-interface"
11+
"github.com/mitchellh/go-testing-interface"
1212

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

154154
if step.Config != "" && !step.Destroy && len(step.Taint) > 0 {
155-
var state *terraform.State
156-
157-
err := runProviderCommand(ctx, t, func() error {
158-
var err error
159-
160-
state, err = getState(ctx, t, wd)
161-
162-
if err != nil {
163-
return err
164-
}
165-
166-
return nil
167-
}, wd, providers)
168-
169-
if err != nil {
170-
logging.HelperResourceError(ctx,
171-
"TestStep error reading prior state before tainting resources",
172-
map[string]interface{}{logging.KeyError: err},
173-
)
174-
t.Fatalf("TestStep %d/%d error reading prior state before tainting resources: %s", stepNumber, len(c.Steps), err)
175-
}
176-
177-
err = testStepTaint(ctx, state, step)
155+
err := testStepTaint(ctx, step, wd)
178156

179157
if err != nil {
180158
logging.HelperResourceError(ctx,

helper/resource/teststep_providers_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import (
55
"fmt"
66
"strings"
77
"testing"
8+
"time"
89

910
"github.com/google/go-cmp/cmp"
1011
"github.com/hashicorp/go-cty/cty"
1112
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
1213
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
14+
1315
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1416
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
17+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
1518
)
1619

1720
func TestStepProviderConfig(t *testing.T) {
@@ -342,6 +345,75 @@ func TestTest_TestStep_ExternalProviders_To_ProviderFactories_StateUpgraders(t *
342345
})
343346
}
344347

348+
func TestTest_TestStep_Taint(t *testing.T) {
349+
t.Parallel()
350+
351+
var idOne, idTwo string
352+
353+
Test(t, TestCase{
354+
ProviderFactories: map[string]func() (*schema.Provider, error){
355+
"random": func() (*schema.Provider, error) { //nolint:unparam // required signature
356+
return &schema.Provider{
357+
ResourcesMap: map[string]*schema.Resource{
358+
"random_id": {
359+
CreateContext: func(_ context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics {
360+
d.SetId(time.Now().String())
361+
return nil
362+
},
363+
DeleteContext: func(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
364+
return nil
365+
},
366+
ReadContext: func(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
367+
return nil
368+
},
369+
Schema: map[string]*schema.Schema{},
370+
},
371+
},
372+
}, nil
373+
},
374+
},
375+
Steps: []TestStep{
376+
{
377+
Config: `resource "random_id" "test" {}`,
378+
Check: ComposeAggregateTestCheckFunc(
379+
extractResourceAttr("random_id.test", "id", &idOne),
380+
),
381+
},
382+
{
383+
Taint: []string{"random_id.test"},
384+
Config: `resource "random_id" "test" {}`,
385+
Check: ComposeAggregateTestCheckFunc(
386+
extractResourceAttr("random_id.test", "id", &idTwo),
387+
),
388+
},
389+
},
390+
})
391+
392+
if idOne == idTwo {
393+
t.Errorf("taint is not causing destroy-create cycle, idOne == idTwo: %s == %s", idOne, idTwo)
394+
}
395+
}
396+
397+
func extractResourceAttr(resourceName string, attributeName string, attributeValue *string) TestCheckFunc {
398+
return func(s *terraform.State) error {
399+
rs, ok := s.RootModule().Resources[resourceName]
400+
401+
if !ok {
402+
return fmt.Errorf("resource name %s not found in state", resourceName)
403+
}
404+
405+
attrValue, ok := rs.Primary.Attributes[attributeName]
406+
407+
if !ok {
408+
return fmt.Errorf("attribute %s not found in resource %s state", attributeName, resourceName)
409+
}
410+
411+
*attributeValue = attrValue
412+
413+
return nil
414+
}
415+
}
416+
345417
func TestTest_TestStep_ProtoV5ProviderFactories(t *testing.T) {
346418
t.Parallel()
347419

internal/plugintest/working_dir.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/hashicorp/terraform-exec/tfexec"
1313
tfjson "github.com/hashicorp/terraform-json"
14+
1415
"github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging"
1516
)
1617

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

307+
// Taint runs terraform taint
308+
func (wd *WorkingDir) Taint(ctx context.Context, address string) error {
309+
logging.HelperResourceTrace(ctx, "Calling Terraform CLI taint command")
310+
311+
err := wd.tf.Taint(context.Background(), address)
312+
313+
logging.HelperResourceTrace(ctx, "Called Terraform CLI taint command")
314+
315+
return err
316+
}
317+
306318
// Refresh runs terraform refresh
307319
func (wd *WorkingDir) Refresh(ctx context.Context) error {
308320
logging.HelperResourceTrace(ctx, "Calling Terraform CLI refresh command")

terraform/state.go

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import (
1414
"sync"
1515

1616
"github.com/hashicorp/go-cty/cty"
17-
multierror "github.com/hashicorp/go-multierror"
18-
uuid "github.com/hashicorp/go-uuid"
17+
"github.com/hashicorp/go-multierror"
18+
"github.com/hashicorp/go-uuid"
1919
"github.com/mitchellh/copystructure"
2020

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

1228-
// Taint marks a resource as tainted.
1229-
func (s *ResourceState) Taint() {
1230-
s.Lock()
1231-
defer s.Unlock()
1232-
1233-
if s.Primary != nil {
1234-
s.Primary.Tainted = true
1235-
}
1236-
}
1237-
1238-
// Untaint unmarks a resource as tainted.
1239-
func (s *ResourceState) Untaint() {
1240-
s.Lock()
1241-
defer s.Unlock()
1242-
1243-
if s.Primary != nil {
1244-
s.Primary.Tainted = false
1245-
}
1246-
}
1247-
12481228
func (s *ResourceState) init() {
12491229
s.Lock()
12501230
defer s.Unlock()

terraform/state_test.go

Lines changed: 0 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -907,95 +907,6 @@ func TestResourceStateEqual(t *testing.T) {
907907
}
908908
}
909909

910-
func TestResourceStateTaint(t *testing.T) {
911-
cases := map[string]struct {
912-
Input *ResourceState
913-
Output *ResourceState
914-
}{
915-
"no primary": {
916-
&ResourceState{},
917-
&ResourceState{},
918-
},
919-
920-
"primary, not tainted": {
921-
&ResourceState{
922-
Primary: &InstanceState{ID: "foo"},
923-
},
924-
&ResourceState{
925-
Primary: &InstanceState{
926-
ID: "foo",
927-
Tainted: true,
928-
},
929-
},
930-
},
931-
932-
"primary, tainted": {
933-
&ResourceState{
934-
Primary: &InstanceState{
935-
ID: "foo",
936-
Tainted: true,
937-
},
938-
},
939-
&ResourceState{
940-
Primary: &InstanceState{
941-
ID: "foo",
942-
Tainted: true,
943-
},
944-
},
945-
},
946-
}
947-
948-
for k, tc := range cases {
949-
tc.Input.Taint()
950-
if !reflect.DeepEqual(tc.Input, tc.Output) {
951-
t.Fatalf(
952-
"Failure: %s\n\nExpected: %#v\n\nGot: %#v",
953-
k, tc.Output, tc.Input)
954-
}
955-
}
956-
}
957-
958-
func TestResourceStateUntaint(t *testing.T) {
959-
cases := map[string]struct {
960-
Input *ResourceState
961-
ExpectedOutput *ResourceState
962-
}{
963-
"no primary, err": {
964-
Input: &ResourceState{},
965-
ExpectedOutput: &ResourceState{},
966-
},
967-
968-
"primary, not tainted": {
969-
Input: &ResourceState{
970-
Primary: &InstanceState{ID: "foo"},
971-
},
972-
ExpectedOutput: &ResourceState{
973-
Primary: &InstanceState{ID: "foo"},
974-
},
975-
},
976-
"primary, tainted": {
977-
Input: &ResourceState{
978-
Primary: &InstanceState{
979-
ID: "foo",
980-
Tainted: true,
981-
},
982-
},
983-
ExpectedOutput: &ResourceState{
984-
Primary: &InstanceState{ID: "foo"},
985-
},
986-
},
987-
}
988-
989-
for k, tc := range cases {
990-
tc.Input.Untaint()
991-
if !reflect.DeepEqual(tc.Input, tc.ExpectedOutput) {
992-
t.Fatalf(
993-
"Failure: %s\n\nExpected: %#v\n\nGot: %#v",
994-
k, tc.ExpectedOutput, tc.Input)
995-
}
996-
}
997-
}
998-
999910
func TestInstanceStateEmpty(t *testing.T) {
1000911
cases := map[string]struct {
1001912
In *InstanceState

0 commit comments

Comments
 (0)