github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/helper/resource/testing_config.go (about)

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