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.
|
// 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
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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{
|
||||||
|
|
Loading…
Reference in New Issue