github.com/cmalfait/terraform@v0.11.12-beta1/helper/resource/testing_config.go (about)

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