github.com/ben-turner/terraform@v0.11.8-0.20180503104400-0cc9e050ecd4/helper/resource/testing_config.go (about)

     1  package resource
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/errwrap"
     9  	"github.com/hashicorp/terraform/terraform"
    10  )
    11  
    12  // testStepConfig runs a config-mode test step
    13  func testStepConfig(
    14  	opts terraform.ContextOpts,
    15  	state *terraform.State,
    16  	step TestStep) (*terraform.State, error) {
    17  	return testStep(opts, state, step)
    18  }
    19  
    20  func testStep(
    21  	opts terraform.ContextOpts,
    22  	state *terraform.State,
    23  	step TestStep) (*terraform.State, error) {
    24  	mod, err := testModule(opts, step)
    25  	if err != nil {
    26  		return state, err
    27  	}
    28  
    29  	// Build the context
    30  	opts.Module = mod
    31  	opts.State = state
    32  	opts.Destroy = step.Destroy
    33  	ctx, err := terraform.NewContext(&opts)
    34  	if err != nil {
    35  		return state, fmt.Errorf("Error initializing context: %s", err)
    36  	}
    37  	if diags := ctx.Validate(); len(diags) > 0 {
    38  		if diags.HasErrors() {
    39  			return nil, errwrap.Wrapf("config is invalid: {{err}}", diags.Err())
    40  		}
    41  
    42  		log.Printf("[WARN] Config warnings:\n%s", diags)
    43  	}
    44  
    45  	// Refresh!
    46  	state, err = ctx.Refresh()
    47  	if err != nil {
    48  		return state, fmt.Errorf(
    49  			"Error refreshing: %s", err)
    50  	}
    51  
    52  	// If this step is a PlanOnly step, skip over this first Plan and subsequent
    53  	// Apply, and use the follow up Plan that checks for perpetual diffs
    54  	if !step.PlanOnly {
    55  		// Plan!
    56  		if p, err := ctx.Plan(); err != nil {
    57  			return state, fmt.Errorf(
    58  				"Error planning: %s", err)
    59  		} else {
    60  			log.Printf("[WARN] Test: Step plan: %s", p)
    61  		}
    62  
    63  		// We need to keep a copy of the state prior to destroying
    64  		// such that destroy steps can verify their behaviour in the check
    65  		// function
    66  		stateBeforeApplication := state.DeepCopy()
    67  
    68  		// Apply!
    69  		state, err = ctx.Apply()
    70  		if err != nil {
    71  			return state, fmt.Errorf("Error applying: %s", err)
    72  		}
    73  
    74  		// Check! Excitement!
    75  		if step.Check != nil {
    76  			if step.Destroy {
    77  				if err := step.Check(stateBeforeApplication); err != nil {
    78  					return state, fmt.Errorf("Check failed: %s", err)
    79  				}
    80  			} else {
    81  				if err := step.Check(state); err != nil {
    82  					return state, fmt.Errorf("Check failed: %s", err)
    83  				}
    84  			}
    85  		}
    86  	}
    87  
    88  	// Now, verify that Plan is now empty and we don't have a perpetual diff issue
    89  	// We do this with TWO plans. One without a refresh.
    90  	var p *terraform.Plan
    91  	if p, err = ctx.Plan(); err != nil {
    92  		return state, fmt.Errorf("Error on follow-up plan: %s", err)
    93  	}
    94  	if p.Diff != nil && !p.Diff.Empty() {
    95  		if step.ExpectNonEmptyPlan {
    96  			log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p)
    97  		} else {
    98  			return state, fmt.Errorf(
    99  				"After applying this step, the plan was not empty:\n\n%s", p)
   100  		}
   101  	}
   102  
   103  	// And another after a Refresh.
   104  	if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
   105  		state, err = ctx.Refresh()
   106  		if err != nil {
   107  			return state, fmt.Errorf(
   108  				"Error on follow-up refresh: %s", err)
   109  		}
   110  	}
   111  	if p, err = ctx.Plan(); err != nil {
   112  		return state, fmt.Errorf("Error on second follow-up plan: %s", err)
   113  	}
   114  	empty := p.Diff == nil || p.Diff.Empty()
   115  
   116  	// Data resources are tricky because they legitimately get instantiated
   117  	// during refresh so that they will be already populated during the
   118  	// plan walk. Because of this, if we have any data resources in the
   119  	// config we'll end up wanting to destroy them again here. This is
   120  	// acceptable and expected, and we'll treat it as "empty" for the
   121  	// sake of this testing.
   122  	if step.Destroy {
   123  		empty = true
   124  
   125  		for _, moduleDiff := range p.Diff.Modules {
   126  			for k, instanceDiff := range moduleDiff.Resources {
   127  				if !strings.HasPrefix(k, "data.") {
   128  					empty = false
   129  					break
   130  				}
   131  
   132  				if !instanceDiff.Destroy {
   133  					empty = false
   134  				}
   135  			}
   136  		}
   137  	}
   138  
   139  	if !empty {
   140  		if step.ExpectNonEmptyPlan {
   141  			log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p)
   142  		} else {
   143  			return state, fmt.Errorf(
   144  				"After applying this step and refreshing, "+
   145  					"the plan was not empty:\n\n%s", p)
   146  		}
   147  	}
   148  
   149  	// Made it here, but expected a non-empty plan, fail!
   150  	if step.ExpectNonEmptyPlan && (p.Diff == nil || p.Diff.Empty()) {
   151  		return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!")
   152  	}
   153  
   154  	// Made it here? Good job test step!
   155  	return state, nil
   156  }