Skip to content

Commit

Permalink
helper/resource: Add ability to pre-taint resources
Browse files Browse the repository at this point in the history
This adds the Taint field to the acceptance testing framework, allowing
the ability to pre-taint resources at the beginning of a particular
TestStep. This can be useful for when an explicit ForceNew is required
for a specific resource for troubleshooting things like diff mismatches,
etc.

The field accepts resource addresses as a list of strings. To keep
things simple for the time being, only addresses in the root module are
accepted. If we ever want to expand this past that, I'd be almost
inclined to add some facilities to the core terraform package to help
translate actual module resource addresses (ie:
module.foo.module.bar.some_resource.baz) into the correct state, versus
the current convention in some acceptance testing facilities that take
the module address as a list of strings (ie: []string{"root", "foo",
"bar"}).
  • Loading branch information
vancluever committed May 25, 2018
1 parent 170a153 commit 3505769
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 0 deletions.
9 changes: 9 additions & 0 deletions helper/resource/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 266,15 @@ type TestStep struct {
// below.
PreConfig func()

// Taint is a list of resource addresses to taint prior to the execution of
// the step. Be sure to only include this at a step where the referenced
// address will be present in state, as it will fail the test if the resource
// is missing.
//
// This option is ignored on ImportState tests, and currently only works for
// resources in the root module path.
Taint []string

//---------------------------------------------------------------
// Test modes. One of the following groups of settings must be
// set to determine what the test step will do. Ideally we would've
Expand Down
25 changes: 25 additions & 0 deletions helper/resource/testing_config.go
Original file line number Diff line number Diff line change
@@ -1,6 1,7 @@
package resource

import (
"errors"
"fmt"
"log"
"strings"
Expand All @@ -21,6 22,14 @@ func testStep(
opts terraform.ContextOpts,
state *terraform.State,
step TestStep) (*terraform.State, error) {
// Pre-taint any resources that have been defined in Taint, as long as this
// is not a destroy step.
if !step.Destroy {
if err := testStepTaint(state, step); err != nil {
return state, err
}
}

mod, err := testModule(opts, step)
if err != nil {
return state, err
Expand Down Expand Up @@ -154,3 163,19 @@ func testStep(
// Made it here? Good job test step!
return state, nil
}

func testStepTaint(state *terraform.State, step TestStep) error {
for _, p := range step.Taint {
m := state.RootModule()
if m == nil {
return errors.New("no state")
}
rs, ok := m.Resources[p]
if !ok {
return fmt.Errorf("resource %q not found in state", p)
}
log.Printf("[WARN] Test: Explicitly tainting resource %q", p)
rs.Taint()
}
return nil
}
79 changes: 79 additions & 0 deletions helper/resource/testing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 911,85 @@ func mockSweeperFunc(s string) error {
return nil
}

func TestTest_Taint(t *testing.T) {
mp := testProvider()
mp.DiffFn = func(
_ *terraform.InstanceInfo,
state *terraform.InstanceState,
_ *terraform.ResourceConfig,
) (*terraform.InstanceDiff, error) {
return &terraform.InstanceDiff{
DestroyTainted: state.Tainted,
}, nil
}

mp.ApplyFn = func(
info *terraform.InstanceInfo,
state *terraform.InstanceState,
diff *terraform.InstanceDiff,
) (*terraform.InstanceState, error) {
var id string
switch {
case diff.Destroy && !diff.DestroyTainted:
return nil, nil
case diff.DestroyTainted:
id = "tainted"
default:
id = "not_tainted"
}

return &terraform.InstanceState{
ID: id,
}, nil
}

mp.RefreshFn = func(
_ *terraform.InstanceInfo,
state *terraform.InstanceState,
) (*terraform.InstanceState, error) {
return state, nil
}

mt := new(mockT)
Test(mt, TestCase{
Providers: map[string]terraform.ResourceProvider{
"test": mp,
},
Steps: []TestStep{
TestStep{
Config: testConfigStr,
Check: func(s *terraform.State) error {
rs := s.RootModule().Resources["test_instance.foo"]
if rs.Primary.ID != "not_tainted" {
return fmt.Errorf("expected not_tainted, got %s", rs.Primary.ID)
}
return nil
},
},
TestStep{
Taint: []string{"test_instance.foo"},
Config: testConfigStr,
Check: func(s *terraform.State) error {
rs := s.RootModule().Resources["test_instance.foo"]
if rs.Primary.ID != "tainted" {
return fmt.Errorf("expected tainted, got %s", rs.Primary.ID)
}
return nil
},
},
TestStep{
Taint: []string{"test_instance.fooo"},
Config: testConfigStr,
ExpectError: regexp.MustCompile("resource \"test_instance.fooo\" not found in state"),
},
},
})

if mt.failed() {
t.Fatalf("test failure: %s", mt.failMessage())
}
}

const testConfigStr = `
resource "test_instance" "foo" {}
`
Expand Down

0 comments on commit 3505769

Please sign in to comment.