terraform: Context.Apply
This commit is contained in:
parent
403876fff3
commit
603ee36d92
|
@ -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.
|
||||
//
|
||||
// The execution plan encapsulates the context and can be stored
|
||||
// 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) {
|
||||
g, err := Graph(&GraphOpts{
|
||||
Config: c.config,
|
||||
|
@ -72,6 +109,10 @@ func (c *Context) Plan(opts *PlanOpts) (*Plan, error) {
|
|||
State: c.state,
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -119,6 +160,87 @@ func (c *Context) Validate() ([]string, []error) {
|
|||
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 {
|
||||
var l sync.Mutex
|
||||
|
||||
|
|
|
@ -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) {
|
||||
c := testConfig(t, "plan-good")
|
||||
p := testProvider("aws")
|
||||
|
@ -366,6 +815,37 @@ func testContext(t *testing.T, opts *ContextOpts) *Context {
|
|||
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(
|
||||
s *ResourceState,
|
||||
c *ResourceConfig) (*ResourceDiff, error) {
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
|
@ -14,434 +13,6 @@ import (
|
|||
// This is the directory where our test fixtures are.
|
||||
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) {
|
||||
rpAWS := new(MockResourceProvider)
|
||||
rpAWS.ResourcesReturn = []ResourceType{
|
||||
|
|
Loading…
Reference in New Issue