github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/terraform/shadow_context.go (about)

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