]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/states/statefile/version4.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / states / statefile / version4.go
1 package statefile
2
3 import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "sort"
8
9 version "github.com/hashicorp/go-version"
10 ctyjson "github.com/zclconf/go-cty/cty/json"
11
12 "github.com/hashicorp/terraform/addrs"
13 "github.com/hashicorp/terraform/states"
14 "github.com/hashicorp/terraform/tfdiags"
15 )
16
17 func readStateV4(src []byte) (*File, tfdiags.Diagnostics) {
18 var diags tfdiags.Diagnostics
19 sV4 := &stateV4{}
20 err := json.Unmarshal(src, sV4)
21 if err != nil {
22 diags = diags.Append(jsonUnmarshalDiags(err))
23 return nil, diags
24 }
25
26 file, prepDiags := prepareStateV4(sV4)
27 diags = diags.Append(prepDiags)
28 return file, diags
29 }
30
31 func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) {
32 var diags tfdiags.Diagnostics
33
34 var tfVersion *version.Version
35 if sV4.TerraformVersion != "" {
36 var err error
37 tfVersion, err = version.NewVersion(sV4.TerraformVersion)
38 if err != nil {
39 diags = diags.Append(tfdiags.Sourceless(
40 tfdiags.Error,
41 "Invalid Terraform version string",
42 fmt.Sprintf("State file claims to have been written by Terraform version %q, which is not a valid version string.", sV4.TerraformVersion),
43 ))
44 }
45 }
46
47 file := &File{
48 TerraformVersion: tfVersion,
49 Serial: sV4.Serial,
50 Lineage: sV4.Lineage,
51 }
52
53 state := states.NewState()
54
55 for _, rsV4 := range sV4.Resources {
56 rAddr := addrs.Resource{
57 Type: rsV4.Type,
58 Name: rsV4.Name,
59 }
60 switch rsV4.Mode {
61 case "managed":
62 rAddr.Mode = addrs.ManagedResourceMode
63 case "data":
64 rAddr.Mode = addrs.DataResourceMode
65 default:
66 diags = diags.Append(tfdiags.Sourceless(
67 tfdiags.Error,
68 "Invalid resource mode in state",
69 fmt.Sprintf("State contains a resource with mode %q (%q %q) which is not supported.", rsV4.Mode, rAddr.Type, rAddr.Name),
70 ))
71 continue
72 }
73
74 moduleAddr := addrs.RootModuleInstance
75 if rsV4.Module != "" {
76 var addrDiags tfdiags.Diagnostics
77 moduleAddr, addrDiags = addrs.ParseModuleInstanceStr(rsV4.Module)
78 diags = diags.Append(addrDiags)
79 if addrDiags.HasErrors() {
80 continue
81 }
82 }
83
84 providerAddr, addrDiags := addrs.ParseAbsProviderConfigStr(rsV4.ProviderConfig)
85 diags.Append(addrDiags)
86 if addrDiags.HasErrors() {
87 continue
88 }
89
90 var eachMode states.EachMode
91 switch rsV4.EachMode {
92 case "":
93 eachMode = states.NoEach
94 case "list":
95 eachMode = states.EachList
96 case "map":
97 eachMode = states.EachMap
98 default:
99 diags = diags.Append(tfdiags.Sourceless(
100 tfdiags.Error,
101 "Invalid resource metadata in state",
102 fmt.Sprintf("Resource %s has invalid \"each\" value %q in state.", rAddr.Absolute(moduleAddr), eachMode),
103 ))
104 continue
105 }
106
107 ms := state.EnsureModule(moduleAddr)
108
109 // Ensure the resource container object is present in the state.
110 ms.SetResourceMeta(rAddr, eachMode, providerAddr)
111
112 for _, isV4 := range rsV4.Instances {
113 keyRaw := isV4.IndexKey
114 var key addrs.InstanceKey
115 switch tk := keyRaw.(type) {
116 case int:
117 key = addrs.IntKey(tk)
118 case float64:
119 // Since JSON only has one number type, reading from encoding/json
120 // gives us a float64 here even if the number is whole.
121 // float64 has a smaller integer range than int, but in practice
122 // we rarely have more than a few tens of instances and so
123 // it's unlikely that we'll exhaust the 52 bits in a float64.
124 key = addrs.IntKey(int(tk))
125 case string:
126 key = addrs.StringKey(tk)
127 default:
128 if keyRaw != nil {
129 diags = diags.Append(tfdiags.Sourceless(
130 tfdiags.Error,
131 "Invalid resource instance metadata in state",
132 fmt.Sprintf("Resource %s has an instance with the invalid instance key %#v.", rAddr.Absolute(moduleAddr), keyRaw),
133 ))
134 continue
135 }
136 key = addrs.NoKey
137 }
138
139 instAddr := rAddr.Instance(key)
140
141 obj := &states.ResourceInstanceObjectSrc{
142 SchemaVersion: isV4.SchemaVersion,
143 }
144
145 {
146 // Instance attributes
147 switch {
148 case isV4.AttributesRaw != nil:
149 obj.AttrsJSON = isV4.AttributesRaw
150 case isV4.AttributesFlat != nil:
151 obj.AttrsFlat = isV4.AttributesFlat
152 default:
153 // This is odd, but we'll accept it and just treat the
154 // object has being empty. In practice this should arise
155 // only from the contrived sort of state objects we tend
156 // to hand-write inline in tests.
157 obj.AttrsJSON = []byte{'{', '}'}
158 }
159 }
160
161 {
162 // Status
163 raw := isV4.Status
164 switch raw {
165 case "":
166 obj.Status = states.ObjectReady
167 case "tainted":
168 obj.Status = states.ObjectTainted
169 default:
170 diags = diags.Append(tfdiags.Sourceless(
171 tfdiags.Error,
172 "Invalid resource instance metadata in state",
173 fmt.Sprintf("Instance %s has invalid status %q.", instAddr.Absolute(moduleAddr), raw),
174 ))
175 continue
176 }
177 }
178
179 if raw := isV4.PrivateRaw; len(raw) > 0 {
180 obj.Private = raw
181 }
182
183 {
184 depsRaw := isV4.Dependencies
185 deps := make([]addrs.Referenceable, 0, len(depsRaw))
186 for _, depRaw := range depsRaw {
187 ref, refDiags := addrs.ParseRefStr(depRaw)
188 diags = diags.Append(refDiags)
189 if refDiags.HasErrors() {
190 continue
191 }
192 if len(ref.Remaining) != 0 {
193 diags = diags.Append(tfdiags.Sourceless(
194 tfdiags.Error,
195 "Invalid resource instance metadata in state",
196 fmt.Sprintf("Instance %s declares dependency on %q, which is not a reference to a dependable object.", instAddr.Absolute(moduleAddr), depRaw),
197 ))
198 }
199 if ref.Subject == nil {
200 // Should never happen
201 panic(fmt.Sprintf("parsing dependency %q for instance %s returned a nil address", depRaw, instAddr.Absolute(moduleAddr)))
202 }
203 deps = append(deps, ref.Subject)
204 }
205 obj.Dependencies = deps
206 }
207
208 switch {
209 case isV4.Deposed != "":
210 dk := states.DeposedKey(isV4.Deposed)
211 if len(dk) != 8 {
212 diags = diags.Append(tfdiags.Sourceless(
213 tfdiags.Error,
214 "Invalid resource instance metadata in state",
215 fmt.Sprintf("Instance %s has an object with deposed key %q, which is not correctly formatted.", instAddr.Absolute(moduleAddr), isV4.Deposed),
216 ))
217 continue
218 }
219 is := ms.ResourceInstance(instAddr)
220 if is.HasDeposed(dk) {
221 diags = diags.Append(tfdiags.Sourceless(
222 tfdiags.Error,
223 "Duplicate resource instance in state",
224 fmt.Sprintf("Instance %s deposed object %q appears multiple times in the state file.", instAddr.Absolute(moduleAddr), dk),
225 ))
226 continue
227 }
228
229 ms.SetResourceInstanceDeposed(instAddr, dk, obj, providerAddr)
230 default:
231 is := ms.ResourceInstance(instAddr)
232 if is.HasCurrent() {
233 diags = diags.Append(tfdiags.Sourceless(
234 tfdiags.Error,
235 "Duplicate resource instance in state",
236 fmt.Sprintf("Instance %s appears multiple times in the state file.", instAddr.Absolute(moduleAddr)),
237 ))
238 continue
239 }
240
241 ms.SetResourceInstanceCurrent(instAddr, obj, providerAddr)
242 }
243 }
244
245 // We repeat this after creating the instances because
246 // SetResourceInstanceCurrent automatically resets this metadata based
247 // on the incoming objects. That behavior is useful when we're making
248 // piecemeal updates to the state during an apply, but when we're
249 // reading the state file we want to reflect its contents exactly.
250 ms.SetResourceMeta(rAddr, eachMode, providerAddr)
251 }
252
253 // The root module is special in that we persist its attributes and thus
254 // need to reload them now. (For descendent modules we just re-calculate
255 // them based on the latest configuration on each run.)
256 {
257 rootModule := state.RootModule()
258 for name, fos := range sV4.RootOutputs {
259 os := &states.OutputValue{}
260 os.Sensitive = fos.Sensitive
261
262 ty, err := ctyjson.UnmarshalType([]byte(fos.ValueTypeRaw))
263 if err != nil {
264 diags = diags.Append(tfdiags.Sourceless(
265 tfdiags.Error,
266 "Invalid output value type in state",
267 fmt.Sprintf("The state file has an invalid type specification for output %q: %s.", name, err),
268 ))
269 continue
270 }
271
272 val, err := ctyjson.Unmarshal([]byte(fos.ValueRaw), ty)
273 if err != nil {
274 diags = diags.Append(tfdiags.Sourceless(
275 tfdiags.Error,
276 "Invalid output value saved in state",
277 fmt.Sprintf("The state file has an invalid value for output %q: %s.", name, err),
278 ))
279 continue
280 }
281
282 os.Value = val
283 rootModule.OutputValues[name] = os
284 }
285 }
286
287 file.State = state
288 return file, diags
289 }
290
291 func writeStateV4(file *File, w io.Writer) tfdiags.Diagnostics {
292 // Here we'll convert back from the "File" representation to our
293 // stateV4 struct representation and write that.
294 //
295 // While we support legacy state formats for reading, we only support the
296 // latest for writing and so if a V5 is added in future then this function
297 // should be deleted and replaced with a writeStateV5, even though the
298 // read/prepare V4 functions above would stick around.
299
300 var diags tfdiags.Diagnostics
301 if file == nil || file.State == nil {
302 panic("attempt to write nil state to file")
303 }
304
305 var terraformVersion string
306 if file.TerraformVersion != nil {
307 terraformVersion = file.TerraformVersion.String()
308 }
309
310 sV4 := &stateV4{
311 TerraformVersion: terraformVersion,
312 Serial: file.Serial,
313 Lineage: file.Lineage,
314 RootOutputs: map[string]outputStateV4{},
315 Resources: []resourceStateV4{},
316 }
317
318 for name, os := range file.State.RootModule().OutputValues {
319 src, err := ctyjson.Marshal(os.Value, os.Value.Type())
320 if err != nil {
321 diags = diags.Append(tfdiags.Sourceless(
322 tfdiags.Error,
323 "Failed to serialize output value in state",
324 fmt.Sprintf("An error occured while serializing output value %q: %s.", name, err),
325 ))
326 continue
327 }
328
329 typeSrc, err := ctyjson.MarshalType(os.Value.Type())
330 if err != nil {
331 diags = diags.Append(tfdiags.Sourceless(
332 tfdiags.Error,
333 "Failed to serialize output value in state",
334 fmt.Sprintf("An error occured while serializing the type of output value %q: %s.", name, err),
335 ))
336 continue
337 }
338
339 sV4.RootOutputs[name] = outputStateV4{
340 Sensitive: os.Sensitive,
341 ValueRaw: json.RawMessage(src),
342 ValueTypeRaw: json.RawMessage(typeSrc),
343 }
344 }
345
346 for _, ms := range file.State.Modules {
347 moduleAddr := ms.Addr
348 for _, rs := range ms.Resources {
349 resourceAddr := rs.Addr
350
351 var mode string
352 switch resourceAddr.Mode {
353 case addrs.ManagedResourceMode:
354 mode = "managed"
355 case addrs.DataResourceMode:
356 mode = "data"
357 default:
358 diags = diags.Append(tfdiags.Sourceless(
359 tfdiags.Error,
360 "Failed to serialize resource in state",
361 fmt.Sprintf("Resource %s has mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), resourceAddr.Mode),
362 ))
363 continue
364 }
365
366 var eachMode string
367 switch rs.EachMode {
368 case states.NoEach:
369 eachMode = ""
370 case states.EachList:
371 eachMode = "list"
372 case states.EachMap:
373 eachMode = "map"
374 default:
375 diags = diags.Append(tfdiags.Sourceless(
376 tfdiags.Error,
377 "Failed to serialize resource in state",
378 fmt.Sprintf("Resource %s has \"each\" mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), rs.EachMode),
379 ))
380 continue
381 }
382
383 sV4.Resources = append(sV4.Resources, resourceStateV4{
384 Module: moduleAddr.String(),
385 Mode: mode,
386 Type: resourceAddr.Type,
387 Name: resourceAddr.Name,
388 EachMode: eachMode,
389 ProviderConfig: rs.ProviderConfig.String(),
390 Instances: []instanceObjectStateV4{},
391 })
392 rsV4 := &(sV4.Resources[len(sV4.Resources)-1])
393
394 for key, is := range rs.Instances {
395 if is.HasCurrent() {
396 var objDiags tfdiags.Diagnostics
397 rsV4.Instances, objDiags = appendInstanceObjectStateV4(
398 rs, is, key, is.Current, states.NotDeposed,
399 rsV4.Instances,
400 )
401 diags = diags.Append(objDiags)
402 }
403 for dk, obj := range is.Deposed {
404 var objDiags tfdiags.Diagnostics
405 rsV4.Instances, objDiags = appendInstanceObjectStateV4(
406 rs, is, key, obj, dk,
407 rsV4.Instances,
408 )
409 diags = diags.Append(objDiags)
410 }
411 }
412 }
413 }
414
415 sV4.normalize()
416
417 src, err := json.MarshalIndent(sV4, "", " ")
418 if err != nil {
419 // Shouldn't happen if we do our conversion to *stateV4 correctly above.
420 diags = diags.Append(tfdiags.Sourceless(
421 tfdiags.Error,
422 "Failed to serialize state",
423 fmt.Sprintf("An error occured while serializing the state to save it. This is a bug in Terraform and should be reported: %s.", err),
424 ))
425 return diags
426 }
427 src = append(src, '\n')
428
429 _, err = w.Write(src)
430 if err != nil {
431 diags = diags.Append(tfdiags.Sourceless(
432 tfdiags.Error,
433 "Failed to write state",
434 fmt.Sprintf("An error occured while writing the serialized state: %s.", err),
435 ))
436 return diags
437 }
438
439 return diags
440 }
441
442 func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstance, key addrs.InstanceKey, obj *states.ResourceInstanceObjectSrc, deposed states.DeposedKey, isV4s []instanceObjectStateV4) ([]instanceObjectStateV4, tfdiags.Diagnostics) {
443 var diags tfdiags.Diagnostics
444
445 var status string
446 switch obj.Status {
447 case states.ObjectReady:
448 status = ""
449 case states.ObjectTainted:
450 status = "tainted"
451 default:
452 diags = diags.Append(tfdiags.Sourceless(
453 tfdiags.Error,
454 "Failed to serialize resource instance in state",
455 fmt.Sprintf("Instance %s has status %s, which cannot be saved in state.", rs.Addr.Instance(key), obj.Status),
456 ))
457 }
458
459 var privateRaw []byte
460 if len(obj.Private) > 0 {
461 privateRaw = obj.Private
462 }
463
464 deps := make([]string, len(obj.Dependencies))
465 for i, depAddr := range obj.Dependencies {
466 deps[i] = depAddr.String()
467 }
468
469 var rawKey interface{}
470 switch tk := key.(type) {
471 case addrs.IntKey:
472 rawKey = int(tk)
473 case addrs.StringKey:
474 rawKey = string(tk)
475 default:
476 if key != addrs.NoKey {
477 diags = diags.Append(tfdiags.Sourceless(
478 tfdiags.Error,
479 "Failed to serialize resource instance in state",
480 fmt.Sprintf("Instance %s has an unsupported instance key: %#v.", rs.Addr.Instance(key), key),
481 ))
482 }
483 }
484
485 return append(isV4s, instanceObjectStateV4{
486 IndexKey: rawKey,
487 Deposed: string(deposed),
488 Status: status,
489 SchemaVersion: obj.SchemaVersion,
490 AttributesFlat: obj.AttrsFlat,
491 AttributesRaw: obj.AttrsJSON,
492 PrivateRaw: privateRaw,
493 Dependencies: deps,
494 }), diags
495 }
496
497 type stateV4 struct {
498 Version stateVersionV4 `json:"version"`
499 TerraformVersion string `json:"terraform_version"`
500 Serial uint64 `json:"serial"`
501 Lineage string `json:"lineage"`
502 RootOutputs map[string]outputStateV4 `json:"outputs"`
503 Resources []resourceStateV4 `json:"resources"`
504 }
505
506 // normalize makes some in-place changes to normalize the way items are
507 // stored to ensure that two functionally-equivalent states will be stored
508 // identically.
509 func (s *stateV4) normalize() {
510 sort.Stable(sortResourcesV4(s.Resources))
511 for _, rs := range s.Resources {
512 sort.Stable(sortInstancesV4(rs.Instances))
513 }
514 }
515
516 type outputStateV4 struct {
517 ValueRaw json.RawMessage `json:"value"`
518 ValueTypeRaw json.RawMessage `json:"type"`
519 Sensitive bool `json:"sensitive,omitempty"`
520 }
521
522 type resourceStateV4 struct {
523 Module string `json:"module,omitempty"`
524 Mode string `json:"mode"`
525 Type string `json:"type"`
526 Name string `json:"name"`
527 EachMode string `json:"each,omitempty"`
528 ProviderConfig string `json:"provider"`
529 Instances []instanceObjectStateV4 `json:"instances"`
530 }
531
532 type instanceObjectStateV4 struct {
533 IndexKey interface{} `json:"index_key,omitempty"`
534 Status string `json:"status,omitempty"`
535 Deposed string `json:"deposed,omitempty"`
536
537 SchemaVersion uint64 `json:"schema_version"`
538 AttributesRaw json.RawMessage `json:"attributes,omitempty"`
539 AttributesFlat map[string]string `json:"attributes_flat,omitempty"`
540
541 PrivateRaw []byte `json:"private,omitempty"`
542
543 Dependencies []string `json:"depends_on,omitempty"`
544 }
545
546 // stateVersionV4 is a weird special type we use to produce our hard-coded
547 // "version": 4 in the JSON serialization.
548 type stateVersionV4 struct{}
549
550 func (sv stateVersionV4) MarshalJSON() ([]byte, error) {
551 return []byte{'4'}, nil
552 }
553
554 func (sv stateVersionV4) UnmarshalJSON([]byte) error {
555 // Nothing to do: we already know we're version 4
556 return nil
557 }
558
559 type sortResourcesV4 []resourceStateV4
560
561 func (sr sortResourcesV4) Len() int { return len(sr) }
562 func (sr sortResourcesV4) Swap(i, j int) { sr[i], sr[j] = sr[j], sr[i] }
563 func (sr sortResourcesV4) Less(i, j int) bool {
564 switch {
565 case sr[i].Mode != sr[j].Mode:
566 return sr[i].Mode < sr[j].Mode
567 case sr[i].Type != sr[j].Type:
568 return sr[i].Type < sr[j].Type
569 case sr[i].Name != sr[j].Name:
570 return sr[i].Name < sr[j].Name
571 default:
572 return false
573 }
574 }
575
576 type sortInstancesV4 []instanceObjectStateV4
577
578 func (si sortInstancesV4) Len() int { return len(si) }
579 func (si sortInstancesV4) Swap(i, j int) { si[i], si[j] = si[j], si[i] }
580 func (si sortInstancesV4) Less(i, j int) bool {
581 ki := si[i].IndexKey
582 kj := si[j].IndexKey
583 if ki != kj {
584 if (ki == nil) != (kj == nil) {
585 return ki == nil
586 }
587 if kii, isInt := ki.(int); isInt {
588 if kji, isInt := kj.(int); isInt {
589 return kii < kji
590 }
591 return true
592 }
593 if kis, isStr := ki.(string); isStr {
594 if kjs, isStr := kj.(string); isStr {
595 return kis < kjs
596 }
597 return true
598 }
599 }
600 if si[i].Deposed != si[j].Deposed {
601 return si[i].Deposed < si[j].Deposed
602 }
603 return false
604 }