summaryrefslogtreecommitdiff
path: root/vendor/github.com/hashicorp/terraform/terraform/shadow_context.go
blob: 5588af252c4dd1b69cedd578acc8893c3e1fb3f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package terraform

import (
	"fmt"
	"strings"

	"github.com/hashicorp/go-multierror"
	"github.com/mitchellh/copystructure"
)

// newShadowContext creates a new context that will shadow the given context
// when walking the graph. The resulting context should be used _only once_
// for a graph walk.
//
// The returned Shadow should be closed after the graph walk with the
// real context is complete. Errors from the shadow can be retrieved there.
//
// Most importantly, any operations done on the shadow context (the returned
// context) will NEVER affect the real context. All structures are deep
// copied, no real providers or resources are used, etc.
func newShadowContext(c *Context) (*Context, *Context, Shadow) {
	// Copy the targets
	targetRaw, err := copystructure.Copy(c.targets)
	if err != nil {
		panic(err)
	}

	// Copy the variables
	varRaw, err := copystructure.Copy(c.variables)
	if err != nil {
		panic(err)
	}

	// Copy the provider inputs
	providerInputRaw, err := copystructure.Copy(c.providerInputConfig)
	if err != nil {
		panic(err)
	}

	// The factories
	componentsReal, componentsShadow := newShadowComponentFactory(c.components)

	// Create the shadow
	shadow := &Context{
		components: componentsShadow,
		destroy:    c.destroy,
		diff:       c.diff.DeepCopy(),
		hooks:      nil,
		meta:       c.meta,
		module:     c.module,
		state:      c.state.DeepCopy(),
		targets:    targetRaw.([]string),
		variables:  varRaw.(map[string]interface{}),

		// NOTE(mitchellh): This is not going to work for shadows that are
		// testing that input results in the proper end state. At the time
		// of writing, input is not used in any state-changing graph
		// walks anyways, so this checks nothing. We set it to this to avoid
		// any panics but even a "nil" value worked here.
		uiInput: new(MockUIInput),

		// Hardcoded to 4 since parallelism in the shadow doesn't matter
		// a ton since we're doing far less compared to the real side
		// and our operations are MUCH faster.
		parallelSem:         NewSemaphore(4),
		providerInputConfig: providerInputRaw.(map[string]map[string]interface{}),
	}

	// Create the real context. This is effectively just a copy of
	// the context given except we need to modify some of the values
	// to point to the real side of a shadow so the shadow can compare values.
	real := &Context{
		// The fields below are changed.
		components: componentsReal,

		// The fields below are direct copies
		destroy: c.destroy,
		diff:    c.diff,
		// diffLock - no copy
		hooks:  c.hooks,
		meta:   c.meta,
		module: c.module,
		sh:     c.sh,
		state:  c.state,
		// stateLock - no copy
		targets:   c.targets,
		uiInput:   c.uiInput,
		variables: c.variables,

		// l - no copy
		parallelSem:         c.parallelSem,
		providerInputConfig: c.providerInputConfig,
		runContext:          c.runContext,
		runContextCancel:    c.runContextCancel,
		shadowErr:           c.shadowErr,
	}

	return real, shadow, &shadowContextCloser{
		Components: componentsShadow,
	}
}

// shadowContextVerify takes the real and shadow context and verifies they
// have equal diffs and states.
func shadowContextVerify(real, shadow *Context) error {
	var result error

	// The states compared must be pruned so they're minimal/clean
	real.state.prune()
	shadow.state.prune()

	// Compare the states
	if !real.state.Equal(shadow.state) {
		result = multierror.Append(result, fmt.Errorf(
			"Real and shadow states do not match! "+
				"Real state:\n\n%s\n\n"+
				"Shadow state:\n\n%s\n\n",
			real.state, shadow.state))
	}

	// Compare the diffs
	if !real.diff.Equal(shadow.diff) {
		result = multierror.Append(result, fmt.Errorf(
			"Real and shadow diffs do not match! "+
				"Real diff:\n\n%s\n\n"+
				"Shadow diff:\n\n%s\n\n",
			real.diff, shadow.diff))
	}

	return result
}

// shadowContextCloser is the io.Closer returned by newShadowContext that
// closes all the shadows and returns the results.
type shadowContextCloser struct {
	Components *shadowComponentFactory
}

// Close closes the shadow context.
func (c *shadowContextCloser) CloseShadow() error {
	return c.Components.CloseShadow()
}

func (c *shadowContextCloser) ShadowError() error {
	err := c.Components.ShadowError()
	if err == nil {
		return nil
	}

	// This is a sad edge case: if the configuration contains uuid() at
	// any point, we cannot reason aboyt the shadow execution. Tested
	// with Context2Plan_shadowUuid.
	if strings.Contains(err.Error(), "uuid()") {
		err = nil
	}

	return err
}