2016-05-10 05:32:31 +02:00
|
|
|
package resource
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2016-05-21 23:16:02 +02:00
|
|
|
"strings"
|
2016-05-10 05:32:31 +02:00
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/terraform"
|
|
|
|
)
|
|
|
|
|
|
|
|
// testStepConfig runs a config-mode test step
|
|
|
|
func testStepConfig(
|
|
|
|
opts terraform.ContextOpts,
|
|
|
|
state *terraform.State,
|
|
|
|
step TestStep) (*terraform.State, error) {
|
|
|
|
return testStep(opts, state, step)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testStep(
|
|
|
|
opts terraform.ContextOpts,
|
|
|
|
state *terraform.State,
|
|
|
|
step TestStep) (*terraform.State, error) {
|
|
|
|
mod, err := testModule(opts, step)
|
|
|
|
if err != nil {
|
|
|
|
return state, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build the context
|
|
|
|
opts.Module = mod
|
|
|
|
opts.State = state
|
|
|
|
opts.Destroy = step.Destroy
|
|
|
|
ctx, err := terraform.NewContext(&opts)
|
|
|
|
if err != nil {
|
|
|
|
return state, fmt.Errorf("Error initializing context: %s", err)
|
|
|
|
}
|
|
|
|
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 state, 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 state, fmt.Errorf(
|
|
|
|
"Error refreshing: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Plan!
|
|
|
|
if p, err := ctx.Plan(); err != nil {
|
|
|
|
return state, fmt.Errorf(
|
|
|
|
"Error planning: %s", err)
|
|
|
|
} else {
|
|
|
|
log.Printf("[WARN] Test: Step plan: %s", p)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need to keep a copy of the state prior to destroying
|
|
|
|
// such that destroy steps can verify their behaviour in the check
|
|
|
|
// function
|
|
|
|
stateBeforeApplication := state.DeepCopy()
|
|
|
|
|
|
|
|
// Apply!
|
|
|
|
state, err = ctx.Apply()
|
|
|
|
if err != nil {
|
|
|
|
return state, fmt.Errorf("Error applying: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check! Excitement!
|
|
|
|
if step.Check != nil {
|
|
|
|
if step.Destroy {
|
|
|
|
if err := step.Check(stateBeforeApplication); err != nil {
|
|
|
|
return state, fmt.Errorf("Check failed: %s", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if err := step.Check(state); err != nil {
|
|
|
|
return state, fmt.Errorf("Check failed: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, verify that Plan is now empty and we don't have a perpetual diff issue
|
|
|
|
// We do this with TWO plans. One without a refresh.
|
|
|
|
var p *terraform.Plan
|
|
|
|
if p, err = ctx.Plan(); err != nil {
|
|
|
|
return state, fmt.Errorf("Error on follow-up plan: %s", err)
|
|
|
|
}
|
|
|
|
if p.Diff != nil && !p.Diff.Empty() {
|
|
|
|
if step.ExpectNonEmptyPlan {
|
|
|
|
log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p)
|
|
|
|
} else {
|
|
|
|
return state, fmt.Errorf(
|
|
|
|
"After applying this step, the plan was not empty:\n\n%s", p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// And another after a Refresh.
|
2016-05-31 11:13:06 +02:00
|
|
|
if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
|
|
|
|
state, err = ctx.Refresh()
|
|
|
|
if err != nil {
|
|
|
|
return state, fmt.Errorf(
|
|
|
|
"Error on follow-up refresh: %s", err)
|
|
|
|
}
|
2016-05-10 05:32:31 +02:00
|
|
|
}
|
|
|
|
if p, err = ctx.Plan(); err != nil {
|
|
|
|
return state, fmt.Errorf("Error on second follow-up plan: %s", err)
|
|
|
|
}
|
2016-05-21 23:16:02 +02:00
|
|
|
empty := p.Diff == nil || p.Diff.Empty()
|
|
|
|
|
|
|
|
// Data resources are tricky because they legitimately get instantiated
|
|
|
|
// during refresh so that they will be already populated during the
|
|
|
|
// plan walk. Because of this, if we have any data resources in the
|
|
|
|
// config we'll end up wanting to destroy them again here. This is
|
|
|
|
// acceptable and expected, and we'll treat it as "empty" for the
|
|
|
|
// sake of this testing.
|
|
|
|
if step.Destroy {
|
|
|
|
empty = true
|
|
|
|
|
|
|
|
for _, moduleDiff := range p.Diff.Modules {
|
|
|
|
for k, instanceDiff := range moduleDiff.Resources {
|
|
|
|
if !strings.HasPrefix(k, "data.") {
|
|
|
|
empty = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if !instanceDiff.Destroy {
|
|
|
|
empty = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !empty {
|
2016-05-10 05:32:31 +02:00
|
|
|
if step.ExpectNonEmptyPlan {
|
|
|
|
log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p)
|
|
|
|
} else {
|
|
|
|
return state, fmt.Errorf(
|
|
|
|
"After applying this step and refreshing, "+
|
|
|
|
"the plan was not empty:\n\n%s", p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Made it here, but expected a non-empty plan, fail!
|
|
|
|
if step.ExpectNonEmptyPlan && (p.Diff == nil || p.Diff.Empty()) {
|
|
|
|
return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Made it here? Good job test step!
|
|
|
|
return state, nil
|
|
|
|
}
|