aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/terraform/shadow_resource_provisioner.go
blob: 60a49088962330b9e348d16cec1a276d639446d3 (plain) (blame)
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
package terraform

import (
	"fmt"
	"io"
	"log"
	"sync"

	"github.com/hashicorp/go-multierror"
	"github.com/hashicorp/terraform/helper/shadow"
)

// shadowResourceProvisioner implements ResourceProvisioner for the shadow
// eval context defined in eval_context_shadow.go.
//
// This is used to verify behavior with a real provisioner. This shouldn't
// be used directly.
type shadowResourceProvisioner interface {
	ResourceProvisioner
	Shadow
}

// newShadowResourceProvisioner creates a new shadowed ResourceProvisioner.
func newShadowResourceProvisioner(
	p ResourceProvisioner) (ResourceProvisioner, shadowResourceProvisioner) {
	// Create the shared data
	shared := shadowResourceProvisionerShared{
		Validate: shadow.ComparedValue{
			Func: shadowResourceProvisionerValidateCompare,
		},
	}

	// Create the real provisioner that does actual work
	real := &shadowResourceProvisionerReal{
		ResourceProvisioner: p,
		Shared:              &shared,
	}

	// Create the shadow that watches the real value
	shadow := &shadowResourceProvisionerShadow{
		Shared: &shared,
	}

	return real, shadow
}

// shadowResourceProvisionerReal is the real resource provisioner. Function calls
// to this will perform real work. This records the parameters and return
// values and call order for the shadow to reproduce.
type shadowResourceProvisionerReal struct {
	ResourceProvisioner

	Shared *shadowResourceProvisionerShared
}

func (p *shadowResourceProvisionerReal) Close() error {
	var result error
	if c, ok := p.ResourceProvisioner.(ResourceProvisionerCloser); ok {
		result = c.Close()
	}

	p.Shared.CloseErr.SetValue(result)
	return result
}

func (p *shadowResourceProvisionerReal) Validate(c *ResourceConfig) ([]string, []error) {
	warns, errs := p.ResourceProvisioner.Validate(c)
	p.Shared.Validate.SetValue(&shadowResourceProvisionerValidate{
		Config:     c,
		ResultWarn: warns,
		ResultErr:  errs,
	})

	return warns, errs
}

func (p *shadowResourceProvisionerReal) Apply(
	output UIOutput, s *InstanceState, c *ResourceConfig) error {
	err := p.ResourceProvisioner.Apply(output, s, c)

	// Write the result, grab a lock for writing. This should nver
	// block long since the operations below don't block.
	p.Shared.ApplyLock.Lock()
	defer p.Shared.ApplyLock.Unlock()

	key := s.ID
	raw, ok := p.Shared.Apply.ValueOk(key)
	if !ok {
		// Setup a new value
		raw = &shadow.ComparedValue{
			Func: shadowResourceProvisionerApplyCompare,
		}

		// Set it
		p.Shared.Apply.SetValue(key, raw)
	}

	compareVal, ok := raw.(*shadow.ComparedValue)
	if !ok {
		// Just log and return so that we don't cause the real side
		// any side effects.
		log.Printf("[ERROR] unknown value in 'apply': %#v", raw)
		return err
	}

	// Write the resulting value
	compareVal.SetValue(&shadowResourceProvisionerApply{
		Config:    c,
		ResultErr: err,
	})

	return err
}

func (p *shadowResourceProvisionerReal) Stop() error {
	return p.ResourceProvisioner.Stop()
}

// shadowResourceProvisionerShadow is the shadow resource provisioner. Function
// calls never affect real resources. This is paired with the "real" side
// which must be called properly to enable recording.
type shadowResourceProvisionerShadow struct {
	Shared *shadowResourceProvisionerShared

	Error     error // Error is the list of errors from the shadow
	ErrorLock sync.Mutex
}

type shadowResourceProvisionerShared struct {
	// NOTE: Anytime a value is added here, be sure to add it to
	// the Close() method so that it is closed.

	CloseErr  shadow.Value
	Validate  shadow.ComparedValue
	Apply     shadow.KeyedValue
	ApplyLock sync.Mutex // For writing only
}

func (p *shadowResourceProvisionerShared) Close() error {
	closers := []io.Closer{
		&p.CloseErr,
	}

	for _, c := range closers {
		// This should never happen, but we don't panic because a panic
		// could affect the real behavior of Terraform and a shadow should
		// never be able to do that.
		if err := c.Close(); err != nil {
			return err
		}
	}

	return nil
}

func (p *shadowResourceProvisionerShadow) CloseShadow() error {
	err := p.Shared.Close()
	if err != nil {
		err = fmt.Errorf("close error: %s", err)
	}

	return err
}

func (p *shadowResourceProvisionerShadow) ShadowError() error {
	return p.Error
}

func (p *shadowResourceProvisionerShadow) Close() error {
	v := p.Shared.CloseErr.Value()
	if v == nil {
		return nil
	}

	return v.(error)
}

func (p *shadowResourceProvisionerShadow) Validate(c *ResourceConfig) ([]string, []error) {
	// Get the result of the validate call
	raw := p.Shared.Validate.Value(c)
	if raw == nil {
		return nil, nil
	}

	result, ok := raw.(*shadowResourceProvisionerValidate)
	if !ok {
		p.ErrorLock.Lock()
		defer p.ErrorLock.Unlock()
		p.Error = multierror.Append(p.Error, fmt.Errorf(
			"Unknown 'validate' shadow value: %#v", raw))
		return nil, nil
	}

	// We don't need to compare configurations because we key on the
	// configuration so just return right away.
	return result.ResultWarn, result.ResultErr
}

func (p *shadowResourceProvisionerShadow) Apply(
	output UIOutput, s *InstanceState, c *ResourceConfig) error {
	// Get the value based on the key
	key := s.ID
	raw := p.Shared.Apply.Value(key)
	if raw == nil {
		return nil
	}

	compareVal, ok := raw.(*shadow.ComparedValue)
	if !ok {
		p.ErrorLock.Lock()
		defer p.ErrorLock.Unlock()
		p.Error = multierror.Append(p.Error, fmt.Errorf(
			"Unknown 'apply' shadow value: %#v", raw))
		return nil
	}

	// With the compared value, we compare against our config
	raw = compareVal.Value(c)
	if raw == nil {
		return nil
	}

	result, ok := raw.(*shadowResourceProvisionerApply)
	if !ok {
		p.ErrorLock.Lock()
		defer p.ErrorLock.Unlock()
		p.Error = multierror.Append(p.Error, fmt.Errorf(
			"Unknown 'apply' shadow value: %#v", raw))
		return nil
	}

	return result.ResultErr
}

func (p *shadowResourceProvisionerShadow) Stop() error {
	// For the shadow, we always just return nil since a Stop indicates
	// that we were interrupted and shadows are disabled during interrupts
	// anyways.
	return nil
}

// The structs for the various function calls are put below. These structs
// are used to carry call information across the real/shadow boundaries.

type shadowResourceProvisionerValidate struct {
	Config     *ResourceConfig
	ResultWarn []string
	ResultErr  []error
}

type shadowResourceProvisionerApply struct {
	Config    *ResourceConfig
	ResultErr error
}

func shadowResourceProvisionerValidateCompare(k, v interface{}) bool {
	c, ok := k.(*ResourceConfig)
	if !ok {
		return false
	}

	result, ok := v.(*shadowResourceProvisionerValidate)
	if !ok {
		return false
	}

	return c.Equal(result.Config)
}

func shadowResourceProvisionerApplyCompare(k, v interface{}) bool {
	c, ok := k.(*ResourceConfig)
	if !ok {
		return false
	}

	result, ok := v.(*shadowResourceProvisionerApply)
	if !ok {
		return false
	}

	return c.Equal(result.Config)
}