terraform: Context.Apply

This commit is contained in:
Mitchell Hashimoto 2014-07-03 11:04:04 -07:00
parent 403876fff3
commit 603ee36d92
3 changed files with 602 additions and 429 deletions

View File

@ -52,10 +52,47 @@ func NewContext(opts *ContextOpts) *Context {
} }
} }
// Apply applies the changes represented by this context and returns
// the resulting state.
//
// In addition to returning the resulting state, this context is updated
// with the latest state.
func (c *Context) Apply() (*State, error) {
g, err := Graph(&GraphOpts{
Config: c.config,
Diff: c.diff,
Providers: c.providers,
State: c.state,
})
if err != nil {
return nil, err
}
// Create our result. Make sure we preserve the prior states
s := new(State)
s.init()
if c.state != nil {
for k, v := range c.state.Resources {
s.Resources[k] = v
}
}
// Walk
err = g.Walk(c.applyWalkFn(s))
// Update our state, even if we have an error, for partial updates
c.state = s
return s, err
}
// Plan generates an execution plan for the given context. // Plan generates an execution plan for the given context.
// //
// The execution plan encapsulates the context and can be stored // The execution plan encapsulates the context and can be stored
// in order to reinstantiate a context later for Apply. // in order to reinstantiate a context later for Apply.
//
// Plan also updates the diff of this context to be the diff generated
// by the plan, so Apply can be called after.
func (c *Context) Plan(opts *PlanOpts) (*Plan, error) { func (c *Context) Plan(opts *PlanOpts) (*Plan, error) {
g, err := Graph(&GraphOpts{ g, err := Graph(&GraphOpts{
Config: c.config, Config: c.config,
@ -72,6 +109,10 @@ func (c *Context) Plan(opts *PlanOpts) (*Plan, error) {
State: c.state, State: c.state,
} }
err = g.Walk(c.planWalkFn(p, opts)) err = g.Walk(c.planWalkFn(p, opts))
// Update the diff so that our context is up-to-date
c.diff = p.Diff
return p, err return p, err
} }
@ -119,6 +160,87 @@ func (c *Context) Validate() ([]string, []error) {
return nil, errs return nil, errs
} }
func (c *Context) applyWalkFn(result *State) depgraph.WalkFunc {
var l sync.Mutex
// Initialize the result
result.init()
cb := func(r *Resource) (map[string]string, error) {
diff := r.Diff
if diff.Empty() {
return r.Vars(), nil
}
if !diff.Destroy {
var err error
diff, err = r.Provider.Diff(r.State, r.Config)
if err != nil {
return nil, err
}
}
// TODO(mitchellh): we need to verify the diff doesn't change
// anything and that the diff has no computed values (pre-computed)
for _, h := range c.hooks {
handleHook(h.PreApply(r.Id, r.State, diff))
}
// With the completed diff, apply!
log.Printf("[DEBUG] %s: Executing Apply", r.Id)
rs, err := r.Provider.Apply(r.State, diff)
if err != nil {
return nil, err
}
// Make sure the result is instantiated
if rs == nil {
rs = new(ResourceState)
}
// Force the resource state type to be our type
rs.Type = r.State.Type
var errs []error
for ak, av := range rs.Attributes {
// If the value is the unknown variable value, then it is an error.
// In this case we record the error and remove it from the state
if av == config.UnknownVariableValue {
errs = append(errs, fmt.Errorf(
"Attribute with unknown value: %s", ak))
delete(rs.Attributes, ak)
}
}
// Update the resulting diff
l.Lock()
if rs.ID == "" {
delete(result.Resources, r.Id)
} else {
result.Resources[r.Id] = rs
}
l.Unlock()
// Update the state for the resource itself
r.State = rs
for _, h := range c.hooks {
handleHook(h.PostApply(r.Id, r.State))
}
// Determine the new state and update variables
err = nil
if len(errs) > 0 {
err = &multierror.Error{Errors: errs}
}
return r.Vars(), err
}
return c.genericWalkFn(c.variables, cb)
}
func (c *Context) planWalkFn(result *Plan, opts *PlanOpts) depgraph.WalkFunc { func (c *Context) planWalkFn(result *Plan, opts *PlanOpts) depgraph.WalkFunc {
var l sync.Mutex var l sync.Mutex

View File

@ -52,6 +52,455 @@ func TestContextValidate_requiredVar(t *testing.T) {
} }
} }
func TestContextApply(t *testing.T) {
c := testConfig(t, "apply-good")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
ctx := testContext(t, &ContextOpts{
Config: c,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
state, err := ctx.Apply()
if err != nil {
t.Fatalf("err: %s", err)
}
if len(state.Resources) < 2 {
t.Fatalf("bad: %#v", state.Resources)
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
/*
func TestContextApply_cancel(t *testing.T) {
stopped := false
stopCh := make(chan struct{})
stopReplyCh := make(chan struct{})
rpAWS := new(MockResourceProvider)
rpAWS.ResourcesReturn = []ResourceType{
ResourceType{Name: "aws_instance"},
}
rpAWS.DiffFn = func(*ResourceState, *ResourceConfig) (*ResourceDiff, error) {
return &ResourceDiff{
Attributes: map[string]*ResourceAttrDiff{
"num": &ResourceAttrDiff{
New: "bar",
},
},
}, nil
}
rpAWS.ApplyFn = func(*ResourceState, *ResourceDiff) (*ResourceState, error) {
if !stopped {
stopped = true
close(stopCh)
<-stopReplyCh
}
return &ResourceState{
ID: "foo",
Attributes: map[string]string{
"num": "2",
},
}, nil
}
c := testConfig(t, "apply-cancel")
tf := testTerraform2(t, &Config{
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(rpAWS),
},
})
p, err := tf.Plan(&PlanOpts{Config: c})
if err != nil {
t.Fatalf("err: %s", err)
}
// Start the Apply in a goroutine
stateCh := make(chan *State)
go func() {
state, err := tf.Apply(p)
if err != nil {
panic(err)
}
stateCh <- state
}()
// Start a goroutine so we can inject exactly when we stop
s := tf.stopHook.ref()
go func() {
defer tf.stopHook.unref(s)
<-tf.stopHook.ch
close(stopReplyCh)
tf.stopHook.stoppedCh <- struct{}{}
}()
<-stopCh
tf.Stop()
state := <-stateCh
if len(state.Resources) != 1 {
t.Fatalf("bad: %#v", state.Resources)
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyCancelStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
*/
func TestContextApply_compute(t *testing.T) {
c := testConfig(t, "apply-compute")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
ctx := testContext(t, &ContextOpts{
Config: c,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
ctx.variables = map[string]string{"value": "1"}
state, err := ctx.Apply()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyComputeStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
func TestContextApply_destroy(t *testing.T) {
c := testConfig(t, "apply-destroy")
h := new(HookRecordApplyOrder)
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
ctx := testContext(t, &ContextOpts{
Config: c,
Hooks: []Hook{h},
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})
// First plan and apply a create operation
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
if _, err := ctx.Apply(); err != nil {
t.Fatalf("err: %s", err)
}
// Next, plan and apply a destroy operation
if _, err := ctx.Plan(&PlanOpts{Destroy: true}); err != nil {
t.Fatalf("err: %s", err)
}
h.Active = true
state, err := ctx.Apply()
if err != nil {
t.Fatalf("err: %s", err)
}
// Test that things were destroyed
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyDestroyStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
// Test that things were destroyed _in the right order_
expected2 := []string{"aws_instance.bar", "aws_instance.foo"}
actual2 := h.IDs
if !reflect.DeepEqual(actual2, expected2) {
t.Fatalf("bad: %#v", actual2)
}
}
func TestContextApply_destroyOrphan(t *testing.T) {
c := testConfig(t, "apply-error")
p := testProvider("aws")
s := &State{
Resources: map[string]*ResourceState{
"aws_instance.baz": &ResourceState{
ID: "bar",
Type: "aws_instance",
},
},
}
ctx := testContext(t, &ContextOpts{
Config: c,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
State: s,
})
p.ApplyFn = func(*ResourceState, *ResourceDiff) (*ResourceState, error) {
return nil, nil
}
p.DiffFn = func(*ResourceState, *ResourceConfig) (*ResourceDiff, error) {
return &ResourceDiff{
Attributes: map[string]*ResourceAttrDiff{
"num": &ResourceAttrDiff{
New: "bar",
},
},
}, nil
}
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
state, err := ctx.Apply()
if err != nil {
t.Fatalf("err: %s", err)
}
if len(state.Resources) != 0 {
t.Fatalf("bad: %#v", state.Resources)
}
}
func TestContextApply_error(t *testing.T) {
errored := false
c := testConfig(t, "apply-error")
p := testProvider("aws")
ctx := testContext(t, &ContextOpts{
Config: c,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})
p.ApplyFn = func(*ResourceState, *ResourceDiff) (*ResourceState, error) {
if errored {
return nil, fmt.Errorf("error")
}
errored = true
return &ResourceState{
ID: "foo",
Attributes: map[string]string{
"num": "2",
},
}, nil
}
p.DiffFn = func(*ResourceState, *ResourceConfig) (*ResourceDiff, error) {
return &ResourceDiff{
Attributes: map[string]*ResourceAttrDiff{
"num": &ResourceAttrDiff{
New: "bar",
},
},
}, nil
}
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
state, err := ctx.Apply()
if err == nil {
t.Fatal("should have error")
}
if len(state.Resources) != 1 {
t.Fatalf("bad: %#v", state.Resources)
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyErrorStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
func TestContextApply_errorPartial(t *testing.T) {
errored := false
c := testConfig(t, "apply-error")
p := testProvider("aws")
s := &State{
Resources: map[string]*ResourceState{
"aws_instance.bar": &ResourceState{
ID: "bar",
Type: "aws_instance",
},
},
}
ctx := testContext(t, &ContextOpts{
Config: c,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
State: s,
})
p.ApplyFn = func(*ResourceState, *ResourceDiff) (*ResourceState, error) {
if errored {
return nil, fmt.Errorf("error")
}
errored = true
return &ResourceState{
ID: "foo",
Attributes: map[string]string{
"num": "2",
},
}, nil
}
p.DiffFn = func(*ResourceState, *ResourceConfig) (*ResourceDiff, error) {
return &ResourceDiff{
Attributes: map[string]*ResourceAttrDiff{
"num": &ResourceAttrDiff{
New: "bar",
},
},
}, nil
}
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
state, err := ctx.Apply()
if err == nil {
t.Fatal("should have error")
}
if len(state.Resources) != 2 {
t.Fatalf("bad: %#v", state.Resources)
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyErrorPartialStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
func TestContextApply_hook(t *testing.T) {
c := testConfig(t, "apply-good")
h := new(MockHook)
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
ctx := testContext(t, &ContextOpts{
Config: c,
Hooks: []Hook{h},
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
if _, err := ctx.Apply(); err != nil {
t.Fatalf("err: %s", err)
}
if !h.PreApplyCalled {
t.Fatal("should be called")
}
if !h.PostApplyCalled {
t.Fatal("should be called")
}
}
func TestContextApply_unknownAttribute(t *testing.T) {
c := testConfig(t, "apply-unknown")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
ctx := testContext(t, &ContextOpts{
Config: c,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
state, err := ctx.Apply()
if err == nil {
t.Fatal("should error")
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyUnknownAttrStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
func TestContextApply_vars(t *testing.T) {
c := testConfig(t, "apply-vars")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
ctx := testContext(t, &ContextOpts{
Config: c,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
Variables: map[string]string{
"foo": "bar",
},
})
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
state, err := ctx.Apply()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyVarsStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
func TestContextPlan(t *testing.T) { func TestContextPlan(t *testing.T) {
c := testConfig(t, "plan-good") c := testConfig(t, "plan-good")
p := testProvider("aws") p := testProvider("aws")
@ -366,6 +815,37 @@ func testContext(t *testing.T, opts *ContextOpts) *Context {
return NewContext(opts) return NewContext(opts)
} }
func testApplyFn(
s *ResourceState,
d *ResourceDiff) (*ResourceState, error) {
if d.Destroy {
return nil, nil
}
id := "foo"
if idAttr, ok := d.Attributes["id"]; ok && !idAttr.NewComputed {
id = idAttr.New
}
result := &ResourceState{
ID: id,
}
if d != nil {
result = result.MergeDiff(d)
}
if depAttr, ok := d.Attributes["dep"]; ok {
result.Dependencies = []ResourceDependency{
ResourceDependency{
ID: depAttr.New,
},
}
}
return result, nil
}
func testDiffFn( func testDiffFn(
s *ResourceState, s *ResourceState,
c *ResourceConfig) (*ResourceDiff, error) { c *ResourceConfig) (*ResourceDiff, error) {

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings"
"sync" "sync"
"testing" "testing"
@ -14,434 +13,6 @@ import (
// This is the directory where our test fixtures are. // This is the directory where our test fixtures are.
const fixtureDir = "./test-fixtures" const fixtureDir = "./test-fixtures"
func TestTerraformApply(t *testing.T) {
c := testConfig(t, "apply-good")
tf := testTerraform2(t, nil)
p, err := tf.Plan(&PlanOpts{Config: c})
if err != nil {
t.Fatalf("err: %s", err)
}
state, err := tf.Apply(p)
if err != nil {
t.Fatalf("err: %s", err)
}
if len(state.Resources) < 2 {
t.Fatalf("bad: %#v", state.Resources)
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
func TestTerraformApply_cancel(t *testing.T) {
stopped := false
stopCh := make(chan struct{})
stopReplyCh := make(chan struct{})
rpAWS := new(MockResourceProvider)
rpAWS.ResourcesReturn = []ResourceType{
ResourceType{Name: "aws_instance"},
}
rpAWS.DiffFn = func(*ResourceState, *ResourceConfig) (*ResourceDiff, error) {
return &ResourceDiff{
Attributes: map[string]*ResourceAttrDiff{
"num": &ResourceAttrDiff{
New: "bar",
},
},
}, nil
}
rpAWS.ApplyFn = func(*ResourceState, *ResourceDiff) (*ResourceState, error) {
if !stopped {
stopped = true
close(stopCh)
<-stopReplyCh
}
return &ResourceState{
ID: "foo",
Attributes: map[string]string{
"num": "2",
},
}, nil
}
c := testConfig(t, "apply-cancel")
tf := testTerraform2(t, &Config{
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(rpAWS),
},
})
p, err := tf.Plan(&PlanOpts{Config: c})
if err != nil {
t.Fatalf("err: %s", err)
}
// Start the Apply in a goroutine
stateCh := make(chan *State)
go func() {
state, err := tf.Apply(p)
if err != nil {
panic(err)
}
stateCh <- state
}()
// Start a goroutine so we can inject exactly when we stop
s := tf.stopHook.ref()
go func() {
defer tf.stopHook.unref(s)
<-tf.stopHook.ch
close(stopReplyCh)
tf.stopHook.stoppedCh <- struct{}{}
}()
<-stopCh
tf.Stop()
state := <-stateCh
if len(state.Resources) != 1 {
t.Fatalf("bad: %#v", state.Resources)
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyCancelStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
func TestTerraformApply_compute(t *testing.T) {
// This tests that computed variables are properly re-diffed
// to get the value prior to application (Apply).
c := testConfig(t, "apply-compute")
tf := testTerraform2(t, nil)
p, err := tf.Plan(&PlanOpts{Config: c})
if err != nil {
t.Fatalf("err: %s", err)
}
p.Vars["value"] = "1"
state, err := tf.Apply(p)
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyComputeStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
func TestTerraformApply_destroy(t *testing.T) {
h := new(HookRecordApplyOrder)
// First, apply the good configuration, build it
c := testConfig(t, "apply-destroy")
tf := testTerraform2(t, &Config{
Hooks: []Hook{h},
})
p, err := tf.Plan(&PlanOpts{Config: c})
if err != nil {
t.Fatalf("err: %s", err)
}
state, err := tf.Apply(p)
if err != nil {
t.Fatalf("err: %s", err)
}
// Next, plan and apply a destroy operation
p, err = tf.Plan(&PlanOpts{
Config: new(config.Config),
State: state,
Destroy: true,
})
if err != nil {
t.Fatalf("err: %s", err)
}
h.Active = true
state, err = tf.Apply(p)
if err != nil {
t.Fatalf("err: %s", err)
}
// Test that things were destroyed
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyDestroyStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
// Test that things were destroyed _in the right order_
expected2 := []string{"aws_instance.bar", "aws_instance.foo"}
actual2 := h.IDs
if !reflect.DeepEqual(actual2, expected2) {
t.Fatalf("bad: %#v", actual2)
}
}
func TestTerraformApply_destroyOrphan(t *testing.T) {
rpAWS := new(MockResourceProvider)
rpAWS.ResourcesReturn = []ResourceType{
ResourceType{Name: "aws_instance"},
}
rpAWS.DiffFn = func(*ResourceState, *ResourceConfig) (*ResourceDiff, error) {
return &ResourceDiff{
Attributes: map[string]*ResourceAttrDiff{
"num": &ResourceAttrDiff{
New: "bar",
},
},
}, nil
}
rpAWS.ApplyFn = func(*ResourceState, *ResourceDiff) (*ResourceState, error) {
return nil, nil
}
c := testConfig(t, "apply-error")
tf := testTerraform2(t, &Config{
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(rpAWS),
},
})
s := &State{
Resources: map[string]*ResourceState{
"aws_instance.baz": &ResourceState{
ID: "bar",
Type: "aws_instance",
},
},
}
p, err := tf.Plan(&PlanOpts{Config: c, State: s})
if err != nil {
t.Fatalf("err: %s", err)
}
state, err := tf.Apply(p)
if err != nil {
t.Fatalf("err: %s", err)
}
if len(state.Resources) != 0 {
t.Fatalf("bad: %#v", state.Resources)
}
}
func TestTerraformApply_error(t *testing.T) {
errored := false
rpAWS := new(MockResourceProvider)
rpAWS.ResourcesReturn = []ResourceType{
ResourceType{Name: "aws_instance"},
}
rpAWS.DiffFn = func(*ResourceState, *ResourceConfig) (*ResourceDiff, error) {
return &ResourceDiff{
Attributes: map[string]*ResourceAttrDiff{
"num": &ResourceAttrDiff{
New: "bar",
},
},
}, nil
}
rpAWS.ApplyFn = func(*ResourceState, *ResourceDiff) (*ResourceState, error) {
if errored {
return nil, fmt.Errorf("error")
}
errored = true
return &ResourceState{
ID: "foo",
Attributes: map[string]string{
"num": "2",
},
}, nil
}
c := testConfig(t, "apply-error")
tf := testTerraform2(t, &Config{
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(rpAWS),
},
})
p, err := tf.Plan(&PlanOpts{Config: c})
if err != nil {
t.Fatalf("err: %s", err)
}
state, err := tf.Apply(p)
if err == nil {
t.Fatal("should have error")
}
if len(state.Resources) != 1 {
t.Fatalf("bad: %#v", state.Resources)
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyErrorStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
func TestTerraformApply_errorPartial(t *testing.T) {
errored := false
rpAWS := new(MockResourceProvider)
rpAWS.ResourcesReturn = []ResourceType{
ResourceType{Name: "aws_instance"},
}
rpAWS.DiffFn = func(*ResourceState, *ResourceConfig) (*ResourceDiff, error) {
return &ResourceDiff{
Attributes: map[string]*ResourceAttrDiff{
"num": &ResourceAttrDiff{
New: "bar",
},
},
}, nil
}
rpAWS.ApplyFn = func(*ResourceState, *ResourceDiff) (*ResourceState, error) {
if errored {
return nil, fmt.Errorf("error")
}
errored = true
return &ResourceState{
ID: "foo",
Attributes: map[string]string{
"num": "2",
},
}, nil
}
c := testConfig(t, "apply-error")
tf := testTerraform2(t, &Config{
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(rpAWS),
},
})
s := &State{
Resources: map[string]*ResourceState{
"aws_instance.bar": &ResourceState{
ID: "bar",
Type: "aws_instance",
},
},
}
p, err := tf.Plan(&PlanOpts{Config: c, State: s})
if err != nil {
t.Fatalf("err: %s", err)
}
state, err := tf.Apply(p)
if err == nil {
t.Fatal("should have error")
}
if len(state.Resources) != 2 {
t.Fatalf("bad: %#v", state.Resources)
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyErrorPartialStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
func TestTerraformApply_hook(t *testing.T) {
c := testConfig(t, "apply-good")
h := new(MockHook)
tf := testTerraform2(t, &Config{
Hooks: []Hook{h},
})
p, err := tf.Plan(&PlanOpts{Config: c})
if err != nil {
t.Fatalf("err: %s", err)
}
if _, err := tf.Apply(p); err != nil {
t.Fatalf("err: %s", err)
}
if !h.PreApplyCalled {
t.Fatal("should be called")
}
if !h.PostApplyCalled {
t.Fatal("should be called")
}
}
func TestTerraformApply_unknownAttribute(t *testing.T) {
c := testConfig(t, "apply-unknown")
tf := testTerraform2(t, nil)
p, err := tf.Plan(&PlanOpts{Config: c})
if err != nil {
t.Fatalf("err: %s", err)
}
state, err := tf.Apply(p)
if err == nil {
t.Fatal("should error")
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyUnknownAttrStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
func TestTerraformApply_vars(t *testing.T) {
c := testConfig(t, "apply-vars")
tf := testTerraform2(t, nil)
p, err := tf.Plan(&PlanOpts{
Config: c,
Vars: map[string]string{"foo": "baz"},
})
if err != nil {
t.Fatalf("err: %s", err)
}
// Explicitly set the "foo" variable
p.Vars["foo"] = "bar"
state, err := tf.Apply(p)
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyVarsStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
func TestTerraformRefresh(t *testing.T) { func TestTerraformRefresh(t *testing.T) {
rpAWS := new(MockResourceProvider) rpAWS := new(MockResourceProvider)
rpAWS.ResourcesReturn = []ResourceType{ rpAWS.ResourcesReturn = []ResourceType{