mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-06 14:13:16 -06:00
84ab00d92e
- This is to allow easier testing of data sources which read data from resources created in the same scope
157 lines
3.9 KiB
Go
157 lines
3.9 KiB
Go
package resource
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
|
|
"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.
|
|
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)
|
|
}
|
|
}
|
|
if p, err = ctx.Plan(); err != nil {
|
|
return state, fmt.Errorf("Error on second follow-up plan: %s", err)
|
|
}
|
|
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 {
|
|
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
|
|
}
|