github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/terraform/shadow_context.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/go-multierror"
     7  	"github.com/mitchellh/copystructure"
     8  )
     9  
    10  // newShadowContext creates a new context that will shadow the given context
    11  // when walking the graph. The resulting context should be used _only once_
    12  // for a graph walk.
    13  //
    14  // The returned Shadow should be closed after the graph walk with the
    15  // real context is complete. Errors from the shadow can be retrieved there.
    16  //
    17  // Most importantly, any operations done on the shadow context (the returned
    18  // context) will NEVER affect the real context. All structures are deep
    19  // copied, no real providers or resources are used, etc.
    20  func newShadowContext(c *Context) (*Context, *Context, Shadow) {
    21  	// Copy the targets
    22  	targetRaw, err := copystructure.Copy(c.targets)
    23  	if err != nil {
    24  		panic(err)
    25  	}
    26  
    27  	// Copy the variables
    28  	varRaw, err := copystructure.Copy(c.variables)
    29  	if err != nil {
    30  		panic(err)
    31  	}
    32  
    33  	// Copy the provider inputs
    34  	providerInputRaw, err := copystructure.Copy(c.providerInputConfig)
    35  	if err != nil {
    36  		panic(err)
    37  	}
    38  
    39  	// The factories
    40  	componentsReal, componentsShadow := newShadowComponentFactory(c.components)
    41  
    42  	// Create the shadow
    43  	shadow := &Context{
    44  		components: componentsShadow,
    45  		destroy:    c.destroy,
    46  		diff:       c.diff.DeepCopy(),
    47  		hooks:      nil,
    48  		module:     c.module,
    49  		state:      c.state.DeepCopy(),
    50  		targets:    targetRaw.([]string),
    51  		variables:  varRaw.(map[string]interface{}),
    52  
    53  		// NOTE(mitchellh): This is not going to work for shadows that are
    54  		// testing that input results in the proper end state. At the time
    55  		// of writing, input is not used in any state-changing graph
    56  		// walks anyways, so this checks nothing. We set it to this to avoid
    57  		// any panics but even a "nil" value worked here.
    58  		uiInput: new(MockUIInput),
    59  
    60  		// Hardcoded to 4 since parallelism in the shadow doesn't matter
    61  		// a ton since we're doing far less compared to the real side
    62  		// and our operations are MUCH faster.
    63  		parallelSem:         NewSemaphore(4),
    64  		providerInputConfig: providerInputRaw.(map[string]map[string]interface{}),
    65  	}
    66  
    67  	// Create the real context. This is effectively just a copy of
    68  	// the context given except we need to modify some of the values
    69  	// to point to the real side of a shadow so the shadow can compare values.
    70  	real := &Context{
    71  		// The fields below are changed.
    72  		components: componentsReal,
    73  
    74  		// The fields below are direct copies
    75  		destroy: c.destroy,
    76  		diff:    c.diff,
    77  		// diffLock - no copy
    78  		hooks:  c.hooks,
    79  		module: c.module,
    80  		sh:     c.sh,
    81  		state:  c.state,
    82  		// stateLock - no copy
    83  		targets:   c.targets,
    84  		uiInput:   c.uiInput,
    85  		variables: c.variables,
    86  
    87  		// l - no copy
    88  		parallelSem:         c.parallelSem,
    89  		providerInputConfig: c.providerInputConfig,
    90  		runCh:               c.runCh,
    91  		shadowErr:           c.shadowErr,
    92  	}
    93  
    94  	return real, shadow, &shadowContextCloser{
    95  		Components: componentsShadow,
    96  	}
    97  }
    98  
    99  // shadowContextVerify takes the real and shadow context and verifies they
   100  // have equal diffs and states.
   101  func shadowContextVerify(real, shadow *Context) error {
   102  	var result error
   103  
   104  	// The states compared must be pruned so they're minimal/clean
   105  	real.state.prune()
   106  	shadow.state.prune()
   107  
   108  	// Compare the states
   109  	if !real.state.Equal(shadow.state) {
   110  		result = multierror.Append(result, fmt.Errorf(
   111  			"Real and shadow states do not match! "+
   112  				"Real state:\n\n%s\n\n"+
   113  				"Shadow state:\n\n%s\n\n",
   114  			real.state, shadow.state))
   115  	}
   116  
   117  	// Compare the diffs
   118  	if !real.diff.Equal(shadow.diff) {
   119  		result = multierror.Append(result, fmt.Errorf(
   120  			"Real and shadow diffs do not match! "+
   121  				"Real diff:\n\n%s\n\n"+
   122  				"Shadow diff:\n\n%s\n\n",
   123  			real.diff, shadow.diff))
   124  	}
   125  
   126  	return result
   127  }
   128  
   129  // shadowContextCloser is the io.Closer returned by newShadowContext that
   130  // closes all the shadows and returns the results.
   131  type shadowContextCloser struct {
   132  	Components *shadowComponentFactory
   133  }
   134  
   135  // Close closes the shadow context.
   136  func (c *shadowContextCloser) CloseShadow() error {
   137  	return c.Components.CloseShadow()
   138  }
   139  
   140  func (c *shadowContextCloser) ShadowError() error {
   141  	return c.Components.ShadowError()
   142  }