forked from hashicorp/terraform
/
state_add.go
374 lines (320 loc) · 10.8 KB
/
state_add.go
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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
package terraform
import "fmt"
// Add adds the item in the state at the given address.
//
// The item can be a ModuleState, ResourceState, or InstanceState. Depending
// on the item type, the address may or may not be valid. For example, a
// module cannot be moved to a resource address, however a resource can be
// moved to a module address (it retains the same name, under that resource).
//
// The item can also be a []*ModuleState, which is the case for nested
// modules. In this case, Add will expect the zero-index to be the top-most
// module to add and will only nest children from there. For semantics, this
// is equivalent to module => module.
//
// The full semantics of Add:
//
// ┌───────────────────┬───────────────────┬───────────────────┐
// │ Module Address │ Resource Address │ Instance Address │
// ┌─────────────────┼───────────────────┼───────────────────┼───────────────────┤
// │ ModuleState │ ✓ │ x │ x │
// ├─────────────────┼───────────────────┼───────────────────┼───────────────────┤
// │ ResourceState │ ✓ │ ✓ │ maybe* │
// ├─────────────────┼───────────────────┼───────────────────┼───────────────────┤
// │ Instance State │ ✓ │ ✓ │ ✓ │
// └─────────────────┴───────────────────┴───────────────────┴───────────────────┘
//
// *maybe - Resources can be added at an instance address only if the resource
// represents a single instance (primary). Example:
// "aws_instance.foo" can be moved to "aws_instance.bar.tainted"
//
func (s *State) Add(fromAddrRaw string, toAddrRaw string, raw interface{}) error {
// Parse the address
toAddr, err := ParseResourceAddress(toAddrRaw)
if err != nil {
return err
}
// Parse the from address
fromAddr, err := ParseResourceAddress(fromAddrRaw)
if err != nil {
return err
}
// Determine the types
from := detectValueAddLoc(raw)
to := detectAddrAddLoc(toAddr)
// Find the function to do this
fromMap, ok := stateAddFuncs[from]
if !ok {
return fmt.Errorf("invalid source to add to state: %T", raw)
}
f, ok := fromMap[to]
if !ok {
return fmt.Errorf("invalid destination: %s (%d)", toAddr, to)
}
// Call the migrator
if err := f(s, fromAddr, toAddr, raw); err != nil {
return err
}
// Prune the state
s.prune()
return nil
}
func stateAddFunc_Module_Module(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error {
// raw can be either *ModuleState or []*ModuleState. The former means
// we're moving just one module. The latter means we're moving a module
// and children.
root := raw
var rest []*ModuleState
if list, ok := raw.([]*ModuleState); ok {
// We need at least one item
if len(list) == 0 {
return fmt.Errorf("module move with no value to: %s", addr)
}
// The first item is always the root
root = list[0]
if len(list) > 1 {
rest = list[1:]
}
}
// Get the actual module state
src := root.(*ModuleState).deepcopy()
// If the target module exists, it is an error
path := append([]string{"root"}, addr.Path...)
if s.ModuleByPath(path) != nil {
return fmt.Errorf("module target is not empty: %s", addr)
}
// Create it and copy our outputs and dependencies
mod := s.AddModule(path)
mod.Outputs = src.Outputs
mod.Dependencies = src.Dependencies
// Go through the resources perform an add for each of those
for k, v := range src.Resources {
resourceKey, err := ParseResourceStateKey(k)
if err != nil {
return err
}
// Update the resource address for this
addrCopy := *addr
addrCopy.Type = resourceKey.Type
addrCopy.Name = resourceKey.Name
addrCopy.Index = resourceKey.Index
addrCopy.Mode = resourceKey.Mode
// Perform an add
if err := s.Add(fromAddr.String(), addrCopy.String(), v); err != nil {
return err
}
}
// Add all the children if we have them
for _, item := range rest {
// If item isn't a descendent of our root, then ignore it
if !src.IsDescendent(item) {
continue
}
// It is! Strip the leading prefix and attach that to our address
extra := item.Path[len(src.Path):]
addrCopy := addr.Copy()
addrCopy.Path = append(addrCopy.Path, extra...)
// Add it
s.Add(fromAddr.String(), addrCopy.String(), item)
}
return nil
}
func stateAddFunc_Resource_Module(
s *State, from, to *ResourceAddress, raw interface{}) error {
// Build the more specific to addr
addr := *to
addr.Type = from.Type
addr.Name = from.Name
return s.Add(from.String(), addr.String(), raw)
}
func stateAddFunc_Resource_Resource(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error {
// raw can be either *ResourceState or []*ResourceState. The former means
// we're moving just one resource. The latter means we're moving a count
// of resources.
if list, ok := raw.([]*ResourceState); ok {
// We need at least one item
if len(list) == 0 {
return fmt.Errorf("resource move with no value to: %s", addr)
}
// If there is an index, this is an error since we can't assign
// a set of resources to a single index
if addr.Index >= 0 && len(list) > 1 {
return fmt.Errorf(
"multiple resources can't be moved to a single index: "+
"%s => %s", fromAddr, addr)
}
// Add each with a specific index
for i, rs := range list {
addrCopy := addr.Copy()
addrCopy.Index = i
if err := s.Add(fromAddr.String(), addrCopy.String(), rs); err != nil {
return err
}
}
return nil
}
src := raw.(*ResourceState).deepcopy()
// Initialize the resource
resourceRaw, exists := stateAddInitAddr(s, addr)
if exists {
return fmt.Errorf("resource exists and not empty: %s", addr)
}
resource := resourceRaw.(*ResourceState)
resource.Type = src.Type
resource.Dependencies = src.Dependencies
resource.Provider = src.Provider
// Move the primary
if src.Primary != nil {
addrCopy := *addr
addrCopy.InstanceType = TypePrimary
addrCopy.InstanceTypeSet = true
if err := s.Add(fromAddr.String(), addrCopy.String(), src.Primary); err != nil {
return err
}
}
// Move all deposed
if len(src.Deposed) > 0 {
resource.Deposed = src.Deposed
}
return nil
}
func stateAddFunc_Instance_Instance(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error {
src := raw.(*InstanceState).DeepCopy()
// Create the instance
instanceRaw, _ := stateAddInitAddr(s, addr)
instance := instanceRaw.(*InstanceState)
// Set it
instance.Set(src)
return nil
}
func stateAddFunc_Instance_Module(
s *State, from, to *ResourceAddress, raw interface{}) error {
addr := *to
addr.Type = from.Type
addr.Name = from.Name
return s.Add(from.String(), addr.String(), raw)
}
func stateAddFunc_Instance_Resource(
s *State, from, to *ResourceAddress, raw interface{}) error {
addr := *to
addr.InstanceType = TypePrimary
addr.InstanceTypeSet = true
return s.Add(from.String(), addr.String(), raw)
}
// stateAddFunc is the type of function for adding an item to a state
type stateAddFunc func(s *State, from, to *ResourceAddress, item interface{}) error
// stateAddFuncs has the full matrix mapping of the state adders.
var stateAddFuncs map[stateAddLoc]map[stateAddLoc]stateAddFunc
func init() {
stateAddFuncs = map[stateAddLoc]map[stateAddLoc]stateAddFunc{
stateAddModule: {
stateAddModule: stateAddFunc_Module_Module,
},
stateAddResource: {
stateAddModule: stateAddFunc_Resource_Module,
stateAddResource: stateAddFunc_Resource_Resource,
},
stateAddInstance: {
stateAddInstance: stateAddFunc_Instance_Instance,
stateAddModule: stateAddFunc_Instance_Module,
stateAddResource: stateAddFunc_Instance_Resource,
},
}
}
// stateAddLoc is an enum to represent the location where state is being
// moved from/to. We use this for quick lookups in a function map.
type stateAddLoc uint
const (
stateAddInvalid stateAddLoc = iota
stateAddModule
stateAddResource
stateAddInstance
)
// detectAddrAddLoc detects the state type for the given address. This
// function is specifically not unit tested since we consider the State.Add
// functionality to be comprehensive enough to cover this.
func detectAddrAddLoc(addr *ResourceAddress) stateAddLoc {
if addr.Name == "" {
return stateAddModule
}
if !addr.InstanceTypeSet {
return stateAddResource
}
return stateAddInstance
}
// detectValueAddLoc determines the stateAddLoc value from the raw value
// that is some State structure.
func detectValueAddLoc(raw interface{}) stateAddLoc {
switch raw.(type) {
case *ModuleState:
return stateAddModule
case []*ModuleState:
return stateAddModule
case *ResourceState:
return stateAddResource
case []*ResourceState:
return stateAddResource
case *InstanceState:
return stateAddInstance
default:
return stateAddInvalid
}
}
// stateAddInitAddr takes a ResourceAddress and creates the non-existing
// resources up to that point, returning the empty (or existing) interface
// at that address.
func stateAddInitAddr(s *State, addr *ResourceAddress) (interface{}, bool) {
addType := detectAddrAddLoc(addr)
// Get the module
path := append([]string{"root"}, addr.Path...)
exists := true
mod := s.ModuleByPath(path)
if mod == nil {
mod = s.AddModule(path)
exists = false
}
if addType == stateAddModule {
return mod, exists
}
// Add the resource
resourceKey := (&ResourceStateKey{
Name: addr.Name,
Type: addr.Type,
Index: addr.Index,
Mode: addr.Mode,
}).String()
exists = true
resource, ok := mod.Resources[resourceKey]
if !ok {
resource = &ResourceState{Type: addr.Type}
resource.init()
mod.Resources[resourceKey] = resource
exists = false
}
if addType == stateAddResource {
return resource, exists
}
// Get the instance
exists = true
instance := &InstanceState{}
switch addr.InstanceType {
case TypePrimary, TypeTainted:
if v := resource.Primary; v != nil {
instance = resource.Primary
} else {
exists = false
}
case TypeDeposed:
idx := addr.Index
if addr.Index < 0 {
idx = 0
}
if len(resource.Deposed) > idx {
instance = resource.Deposed[idx]
} else {
resource.Deposed = append(resource.Deposed, instance)
exists = false
}
}
return instance, exists
}