helper/resource: id-only refresh testing
This commit is contained in:
parent
29daf8ca83
commit
4f6edf4fe4
|
@ -7,11 +7,14 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/hashicorp/go-getter"
|
"github.com/hashicorp/go-getter"
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/config/module"
|
"github.com/hashicorp/terraform/config/module"
|
||||||
"github.com/hashicorp/terraform/helper/logging"
|
"github.com/hashicorp/terraform/helper/logging"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
@ -145,6 +148,7 @@ func Test(t TestT, c TestCase) {
|
||||||
var state *terraform.State
|
var state *terraform.State
|
||||||
|
|
||||||
// Go through each step and run it
|
// Go through each step and run it
|
||||||
|
var idRefreshCheck *terraform.ResourceState
|
||||||
for i, step := range c.Steps {
|
for i, step := range c.Steps {
|
||||||
var err error
|
var err error
|
||||||
log.Printf("[WARN] Test: Executing step %d", i)
|
log.Printf("[WARN] Test: Executing step %d", i)
|
||||||
|
@ -154,6 +158,36 @@ func Test(t TestT, c TestCase) {
|
||||||
"Step %d error: %s", i, err))
|
"Step %d error: %s", i, err))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we've never checked an id-only refresh and our state isn't
|
||||||
|
// empty, find the first resource and test it.
|
||||||
|
if idRefreshCheck == nil && !state.Empty() {
|
||||||
|
// Find the first non-nil resource in the state
|
||||||
|
for _, m := range state.Modules {
|
||||||
|
if len(m.Resources) > 0 {
|
||||||
|
for _, v := range m.Resources {
|
||||||
|
if v != nil && v.Primary != nil {
|
||||||
|
idRefreshCheck = v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have an instance to check for refreshes, do it
|
||||||
|
// immediately. We do it in the middle of another test
|
||||||
|
// because it shouldn't affect the overall state (refresh
|
||||||
|
// is read-only semantically) and we want to fail early if
|
||||||
|
// this fails. If refresh isn't read-only, then this will have
|
||||||
|
// caught a different bug.
|
||||||
|
if idRefreshCheck != nil {
|
||||||
|
if err := testIDOnlyRefresh(opts, idRefreshCheck); err != nil {
|
||||||
|
t.Error(fmt.Sprintf(
|
||||||
|
"ID-Only refresh test failure: %s", err))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a state, then run the destroy
|
// If we have a state, then run the destroy
|
||||||
|
@ -195,6 +229,65 @@ func UnitTest(t TestT, c TestCase) {
|
||||||
Test(t, c)
|
Test(t, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testIDOnlyRefresh(opts terraform.ContextOpts, r *terraform.ResourceState) error {
|
||||||
|
name := fmt.Sprintf("%s.foo", r.Type)
|
||||||
|
|
||||||
|
// Build the state. The state is just the resource with an ID. There
|
||||||
|
// are no attributes. We only set what is needed to perform a refresh.
|
||||||
|
state := terraform.NewState()
|
||||||
|
state.RootModule().Resources[name] = &terraform.ResourceState{
|
||||||
|
Type: r.Type,
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: r.Primary.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty module
|
||||||
|
mod := module.NewTree("root", &config.Config{})
|
||||||
|
if err := mod.Load(nil, module.GetModeGet); err != nil {
|
||||||
|
return fmt.Errorf("Error loading modules: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the context
|
||||||
|
opts.Module = mod
|
||||||
|
opts.State = state
|
||||||
|
ctx := terraform.NewContext(&opts)
|
||||||
|
if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 {
|
||||||
|
if len(es) > 0 {
|
||||||
|
estrs := make([]string, len(es))
|
||||||
|
for i, e := range es {
|
||||||
|
estrs[i] = e.Error()
|
||||||
|
}
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v",
|
||||||
|
ws, estrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[WARN] Config warnings: %#v", ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh!
|
||||||
|
state, err := ctx.Refresh()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error refreshing: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify attribute equivalence.
|
||||||
|
println(state.String())
|
||||||
|
actual := state.RootModule().Resources[name].Primary.Attributes
|
||||||
|
expected := r.Primary.Attributes
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
// TODO: determine attribute difference
|
||||||
|
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Attributes not equivalent. Top is what we received, bottom is expected."+
|
||||||
|
"\n\n%s\n\n%s",
|
||||||
|
spew.Sdump(actual), spew.Sdump(expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func testStep(
|
func testStep(
|
||||||
opts terraform.ContextOpts,
|
opts terraform.ContextOpts,
|
||||||
state *terraform.State,
|
state *terraform.State,
|
||||||
|
|
|
@ -83,6 +83,51 @@ func TestTest(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTest_idRefresh(t *testing.T) {
|
||||||
|
// Refresh count should be 3:
|
||||||
|
// 1.) initial Ref/Plan/Apply
|
||||||
|
// 2.) post Ref/Plan/Apply for plan-check
|
||||||
|
// 3.) id refresh check
|
||||||
|
var expectedRefresh int32 = 3
|
||||||
|
|
||||||
|
mp := testProvider()
|
||||||
|
mp.DiffReturn = nil
|
||||||
|
|
||||||
|
mp.ApplyReturn = &terraform.InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
}
|
||||||
|
var refreshCount int32
|
||||||
|
mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) {
|
||||||
|
atomic.AddInt32(&refreshCount, 1)
|
||||||
|
if atomic.LoadInt32(&refreshCount) < expectedRefresh {
|
||||||
|
return &terraform.InstanceState{ID: "foo"}, nil
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mt := new(mockT)
|
||||||
|
Test(mt, TestCase{
|
||||||
|
Providers: map[string]terraform.ResourceProvider{
|
||||||
|
"test": mp,
|
||||||
|
},
|
||||||
|
Steps: []TestStep{
|
||||||
|
TestStep{
|
||||||
|
Config: testConfigStr,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if mt.failed() {
|
||||||
|
t.Fatalf("test failed: %s", mt.failMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
// See declaration of expectedRefresh for why that number
|
||||||
|
if refreshCount != expectedRefresh {
|
||||||
|
t.Fatalf("bad refresh count: %d", refreshCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTest_empty(t *testing.T) {
|
func TestTest_empty(t *testing.T) {
|
||||||
destroyCalled := false
|
destroyCalled := false
|
||||||
checkDestroyFn := func(*terraform.State) error {
|
checkDestroyFn := func(*terraform.State) error {
|
||||||
|
|
Loading…
Reference in New Issue