github.com/rhenning/terraform@v0.8.0-beta2/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  		module:     c.module,
    50  		state:      c.state.DeepCopy(),
    51  		targets:    targetRaw.([]string),
    52  		variables:  varRaw.(map[string]interface{}),
    53  
    54  		// NOTE(mitchellh): This is not going to work for shadows that are
    55  		// testing that input results in the proper end state. At the time
    56  		// of writing, input is not used in any state-changing graph
    57  		// walks anyways, so this checks nothing. We set it to this to avoid
    58  		// any panics but even a "nil" value worked here.
    59  		uiInput: new(MockUIInput),
    60  
    61  		// Hardcoded to 4 since parallelism in the shadow doesn't matter
    62  		// a ton since we're doing far less compared to the real side
    63  		// and our operations are MUCH faster.
    64  		parallelSem:         NewSemaphore(4),
    65  		providerInputConfig: providerInputRaw.(map[string]map[string]interface{}),
    66  	}
    67  
    68  	// Create the real context. This is effectively just a copy of
    69  	// the context given except we need to modify some of the values
    70  	// to point to the real side of a shadow so the shadow can compare values.
    71  	real := &Context{
    72  		// The fields below are changed.
    73  		components: componentsReal,
    74  
    75  		// The fields below are direct copies
    76  		destroy: c.destroy,
    77  		diff:    c.diff,
    78  		// diffLock - no copy
    79  		hooks:  c.hooks,
    80  		module: c.module,
    81  		sh:     c.sh,
    82  		state:  c.state,
    83  		// stateLock - no copy
    84  		targets:   c.targets,
    85  		uiInput:   c.uiInput,
    86  		variables: c.variables,
    87  
    88  		// l - no copy
    89  		parallelSem:         c.parallelSem,
    90  		providerInputConfig: c.providerInputConfig,
    91  		runCh:               c.runCh,
    92  		shadowErr:           c.shadowErr,
    93  	}
    94  
    95  	return real, shadow, &shadowContextCloser{
    96  		Components: componentsShadow,
    97  	}
    98  }
    99  
   100  // shadowContextVerify takes the real and shadow context and verifies they
   101  // have equal diffs and states.
   102  func shadowContextVerify(real, shadow *Context) error {
   103  	var result error
   104  
   105  	// The states compared must be pruned so they're minimal/clean
   106  	real.state.prune()
   107  	shadow.state.prune()
   108  
   109  	// Compare the states
   110  	if !real.state.Equal(shadow.state) {
   111  		result = multierror.Append(result, fmt.Errorf(
   112  			"Real and shadow states do not match! "+
   113  				"Real state:\n\n%s\n\n"+
   114  				"Shadow state:\n\n%s\n\n",
   115  			real.state, shadow.state))
   116  	}
   117  
   118  	// Compare the diffs
   119  	if !real.diff.Equal(shadow.diff) {
   120  		result = multierror.Append(result, fmt.Errorf(
   121  			"Real and shadow diffs do not match! "+
   122  				"Real diff:\n\n%s\n\n"+
   123  				"Shadow diff:\n\n%s\n\n",
   124  			real.diff, shadow.diff))
   125  	}
   126  
   127  	return result
   128  }
   129  
   130  // shadowContextCloser is the io.Closer returned by newShadowContext that
   131  // closes all the shadows and returns the results.
   132  type shadowContextCloser struct {
   133  	Components *shadowComponentFactory
   134  }
   135  
   136  // Close closes the shadow context.
   137  func (c *shadowContextCloser) CloseShadow() error {
   138  	return c.Components.CloseShadow()
   139  }
   140  
   141  func (c *shadowContextCloser) ShadowError() error {
   142  	err := c.Components.ShadowError()
   143  	if err == nil {
   144  		return nil
   145  	}
   146  
   147  	// This is a sad edge case: if the configuration contains uuid() at
   148  	// any point, we cannot reason aboyt the shadow execution. Tested
   149  	// with Context2Plan_shadowUuid.
   150  	if strings.Contains(err.Error(), "uuid()") {
   151  		err = nil
   152  	}
   153  
   154  	return err
   155  }