summaryrefslogtreecommitdiff
path: root/vendor/github.com/hashicorp/terraform/terraform/eval_state.go
blob: 1f67e3d86b46157df85478cdbfcfd8d102117542 (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
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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
package terraform

import (
	"fmt"
)

// EvalReadState is an EvalNode implementation that reads the
// primary InstanceState for a specific resource out of the state.
type EvalReadState struct {
	Name   string
	Output **InstanceState
}

func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) {
	return readInstanceFromState(ctx, n.Name, n.Output, func(rs *ResourceState) (*InstanceState, error) {
		return rs.Primary, nil
	})
}

// EvalReadStateDeposed is an EvalNode implementation that reads the
// deposed InstanceState for a specific resource out of the state
type EvalReadStateDeposed struct {
	Name   string
	Output **InstanceState
	// Index indicates which instance in the Deposed list to target, or -1 for
	// the last item.
	Index int
}

func (n *EvalReadStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
	return readInstanceFromState(ctx, n.Name, n.Output, func(rs *ResourceState) (*InstanceState, error) {
		// Get the index. If it is negative, then we get the last one
		idx := n.Index
		if idx < 0 {
			idx = len(rs.Deposed) - 1
		}
		if idx >= 0 && idx < len(rs.Deposed) {
			return rs.Deposed[idx], nil
		} else {
			return nil, fmt.Errorf("bad deposed index: %d, for resource: %#v", idx, rs)
		}
	})
}

// Does the bulk of the work for the various flavors of ReadState eval nodes.
// Each node just provides a reader function to get from the ResourceState to the
// InstanceState, and this takes care of all the plumbing.
func readInstanceFromState(
	ctx EvalContext,
	resourceName string,
	output **InstanceState,
	readerFn func(*ResourceState) (*InstanceState, error),
) (*InstanceState, error) {
	state, lock := ctx.State()

	// Get a read lock so we can access this instance
	lock.RLock()
	defer lock.RUnlock()

	// Look for the module state. If we don't have one, then it doesn't matter.
	mod := state.ModuleByPath(ctx.Path())
	if mod == nil {
		return nil, nil
	}

	// Look for the resource state. If we don't have one, then it is okay.
	rs := mod.Resources[resourceName]
	if rs == nil {
		return nil, nil
	}

	// Use the delegate function to get the instance state from the resource state
	is, err := readerFn(rs)
	if err != nil {
		return nil, err
	}

	// Write the result to the output pointer
	if output != nil {
		*output = is
	}

	return is, nil
}

// EvalRequireState is an EvalNode implementation that early exits
// if the state doesn't have an ID.
type EvalRequireState struct {
	State **InstanceState
}

func (n *EvalRequireState) Eval(ctx EvalContext) (interface{}, error) {
	if n.State == nil {
		return nil, EvalEarlyExitError{}
	}

	state := *n.State
	if state == nil || state.ID == "" {
		return nil, EvalEarlyExitError{}
	}

	return nil, nil
}

// EvalUpdateStateHook is an EvalNode implementation that calls the
// PostStateUpdate hook with the current state.
type EvalUpdateStateHook struct{}

func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) {
	state, lock := ctx.State()

	// Get a full lock. Even calling something like WriteState can modify
	// (prune) the state, so we need the full lock.
	lock.Lock()
	defer lock.Unlock()

	// Call the hook
	err := ctx.Hook(func(h Hook) (HookAction, error) {
		return h.PostStateUpdate(state)
	})
	if err != nil {
		return nil, err
	}

	return nil, nil
}

// EvalWriteState is an EvalNode implementation that writes the
// primary InstanceState for a specific resource into the state.
type EvalWriteState struct {
	Name         string
	ResourceType string
	Provider     string
	Dependencies []string
	State        **InstanceState
}

func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
	return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Provider, n.Dependencies,
		func(rs *ResourceState) error {
			rs.Primary = *n.State
			return nil
		},
	)
}

// EvalWriteStateDeposed is an EvalNode implementation that writes
// an InstanceState out to the Deposed list of a resource in the state.
type EvalWriteStateDeposed struct {
	Name         string
	ResourceType string
	Provider     string
	Dependencies []string
	State        **InstanceState
	// Index indicates which instance in the Deposed list to target, or -1 to append.
	Index int
}

func (n *EvalWriteStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
	return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Provider, n.Dependencies,
		func(rs *ResourceState) error {
			if n.Index == -1 {
				rs.Deposed = append(rs.Deposed, *n.State)
			} else {
				rs.Deposed[n.Index] = *n.State
			}
			return nil
		},
	)
}

// Pulls together the common tasks of the EvalWriteState nodes.  All the args
// are passed directly down from the EvalNode along with a `writer` function
// which is yielded the *ResourceState and is responsible for writing an
// InstanceState to the proper field in the ResourceState.
func writeInstanceToState(
	ctx EvalContext,
	resourceName string,
	resourceType string,
	provider string,
	dependencies []string,
	writerFn func(*ResourceState) error,
) (*InstanceState, error) {
	state, lock := ctx.State()
	if state == nil {
		return nil, fmt.Errorf("cannot write state to nil state")
	}

	// Get a write lock so we can access this instance
	lock.Lock()
	defer lock.Unlock()

	// Look for the module state. If we don't have one, create it.
	mod := state.ModuleByPath(ctx.Path())
	if mod == nil {
		mod = state.AddModule(ctx.Path())
	}

	// Look for the resource state.
	rs := mod.Resources[resourceName]
	if rs == nil {
		rs = &ResourceState{}
		rs.init()
		mod.Resources[resourceName] = rs
	}
	rs.Type = resourceType
	rs.Dependencies = dependencies
	rs.Provider = provider

	if err := writerFn(rs); err != nil {
		return nil, err
	}

	return nil, nil
}

// EvalClearPrimaryState is an EvalNode implementation that clears the primary
// instance from a resource state.
type EvalClearPrimaryState struct {
	Name string
}

func (n *EvalClearPrimaryState) Eval(ctx EvalContext) (interface{}, error) {
	state, lock := ctx.State()

	// Get a read lock so we can access this instance
	lock.RLock()
	defer lock.RUnlock()

	// Look for the module state. If we don't have one, then it doesn't matter.
	mod := state.ModuleByPath(ctx.Path())
	if mod == nil {
		return nil, nil
	}

	// Look for the resource state. If we don't have one, then it is okay.
	rs := mod.Resources[n.Name]
	if rs == nil {
		return nil, nil
	}

	// Clear primary from the resource state
	rs.Primary = nil

	return nil, nil
}

// EvalDeposeState is an EvalNode implementation that takes the primary
// out of a state and makes it Deposed. This is done at the beginning of
// create-before-destroy calls so that the create can create while preserving
// the old state of the to-be-destroyed resource.
type EvalDeposeState struct {
	Name string
}

// TODO: test
func (n *EvalDeposeState) Eval(ctx EvalContext) (interface{}, error) {
	state, lock := ctx.State()

	// Get a read lock so we can access this instance
	lock.RLock()
	defer lock.RUnlock()

	// Look for the module state. If we don't have one, then it doesn't matter.
	mod := state.ModuleByPath(ctx.Path())
	if mod == nil {
		return nil, nil
	}

	// Look for the resource state. If we don't have one, then it is okay.
	rs := mod.Resources[n.Name]
	if rs == nil {
		return nil, nil
	}

	// If we don't have a primary, we have nothing to depose
	if rs.Primary == nil {
		return nil, nil
	}

	// Depose
	rs.Deposed = append(rs.Deposed, rs.Primary)
	rs.Primary = nil

	return nil, nil
}

// EvalUndeposeState is an EvalNode implementation that reads the
// InstanceState for a specific resource out of the state.
type EvalUndeposeState struct {
	Name  string
	State **InstanceState
}

// TODO: test
func (n *EvalUndeposeState) Eval(ctx EvalContext) (interface{}, error) {
	state, lock := ctx.State()

	// Get a read lock so we can access this instance
	lock.RLock()
	defer lock.RUnlock()

	// Look for the module state. If we don't have one, then it doesn't matter.
	mod := state.ModuleByPath(ctx.Path())
	if mod == nil {
		return nil, nil
	}

	// Look for the resource state. If we don't have one, then it is okay.
	rs := mod.Resources[n.Name]
	if rs == nil {
		return nil, nil
	}

	// If we don't have any desposed resource, then we don't have anything to do
	if len(rs.Deposed) == 0 {
		return nil, nil
	}

	// Undepose
	idx := len(rs.Deposed) - 1
	rs.Primary = rs.Deposed[idx]
	rs.Deposed[idx] = *n.State

	return nil, nil
}