helper/resource: id-only refresh testing
This commit is contained in:
parent
29daf8ca83
commit
4f6edf4fe4
|
@ -7,11 +7,14 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/hashicorp/go-getter"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/helper/logging"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
@ -145,6 +148,7 @@ func Test(t TestT, c TestCase) {
|
|||
var state *terraform.State
|
||||
|
||||
// Go through each step and run it
|
||||
var idRefreshCheck *terraform.ResourceState
|
||||
for i, step := range c.Steps {
|
||||
var err error
|
||||
log.Printf("[WARN] Test: Executing step %d", i)
|
||||
|
@ -154,6 +158,36 @@ func Test(t TestT, c TestCase) {
|
|||
"Step %d error: %s", i, err))
|
||||
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
|
||||
|
@ -195,6 +229,65 @@ func UnitTest(t TestT, c TestCase) {
|
|||
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(
|
||||
opts terraform.ContextOpts,
|
||||
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) {
|
||||
destroyCalled := false
|
||||
checkDestroyFn := func(*terraform.State) error {
|
||||
|
|
Loading…
Reference in New Issue