]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/states/statefile/version3_upgrade.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / states / statefile / version3_upgrade.go
1 package statefile
2
3 import (
4 "encoding/json"
5 "fmt"
6 "strconv"
7 "strings"
8
9 "github.com/zclconf/go-cty/cty"
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 upgradeStateV3ToV4(old *stateV3) (*stateV4, error) {
18
19 if old.Serial < 0 {
20 // The new format is using uint64 here, which should be fine for any
21 // real state (we only used positive integers in practice) but we'll
22 // catch this explicitly here to avoid weird behavior if a state file
23 // has been tampered with in some way.
24 return nil, fmt.Errorf("state has serial less than zero, which is invalid")
25 }
26
27 new := &stateV4{
28 TerraformVersion: old.TFVersion,
29 Serial: uint64(old.Serial),
30 Lineage: old.Lineage,
31 RootOutputs: map[string]outputStateV4{},
32 Resources: []resourceStateV4{},
33 }
34
35 if new.TerraformVersion == "" {
36 // Older formats considered this to be optional, but now it's required
37 // and so we'll stub it out with something that's definitely older
38 // than the version that really created this state.
39 new.TerraformVersion = "0.0.0"
40 }
41
42 for _, msOld := range old.Modules {
43 if len(msOld.Path) < 1 || msOld.Path[0] != "root" {
44 return nil, fmt.Errorf("state contains invalid module path %#v", msOld.Path)
45 }
46
47 // Convert legacy-style module address into our newer address type.
48 // Since these old formats are only generated by versions of Terraform
49 // that don't support count and for_each on modules, we can just assume
50 // all of the modules are unkeyed.
51 moduleAddr := make(addrs.ModuleInstance, len(msOld.Path)-1)
52 for i, name := range msOld.Path[1:] {
53 moduleAddr[i] = addrs.ModuleInstanceStep{
54 Name: name,
55 InstanceKey: addrs.NoKey,
56 }
57 }
58
59 // In a v3 state file, a "resource state" is actually an instance
60 // state, so we need to fill in a missing level of heirarchy here
61 // by lazily creating resource states as we encounter them.
62 // We'll track them in here, keyed on the string representation of
63 // the resource address.
64 resourceStates := map[string]*resourceStateV4{}
65
66 for legacyAddr, rsOld := range msOld.Resources {
67 instAddr, err := parseLegacyResourceAddress(legacyAddr)
68 if err != nil {
69 return nil, err
70 }
71
72 resAddr := instAddr.Resource
73 rs, exists := resourceStates[resAddr.String()]
74 if !exists {
75 var modeStr string
76 switch resAddr.Mode {
77 case addrs.ManagedResourceMode:
78 modeStr = "managed"
79 case addrs.DataResourceMode:
80 modeStr = "data"
81 default:
82 return nil, fmt.Errorf("state contains resource %s with an unsupported resource mode", resAddr)
83 }
84
85 // In state versions prior to 4 we allowed each instance of a
86 // resource to have its own provider configuration address,
87 // which makes no real sense in practice because providers
88 // are associated with resources in the configuration. We
89 // elevate that to the resource level during this upgrade,
90 // implicitly taking the provider address of the first instance
91 // we encounter for each resource. While this is lossy in
92 // theory, in practice there is no reason for these values to
93 // differ between instances.
94 var providerAddr addrs.AbsProviderConfig
95 oldProviderAddr := rsOld.Provider
96 if strings.Contains(oldProviderAddr, "provider.") {
97 // Smells like a new-style provider address, but we'll test it.
98 var diags tfdiags.Diagnostics
99 providerAddr, diags = addrs.ParseAbsProviderConfigStr(oldProviderAddr)
100 if diags.HasErrors() {
101 return nil, diags.Err()
102 }
103 } else {
104 // Smells like an old-style module-local provider address,
105 // which we'll need to migrate. We'll assume it's referring
106 // to the same module the resource is in, which might be
107 // incorrect but it'll get fixed up next time any updates
108 // are made to an instance.
109 if oldProviderAddr != "" {
110 localAddr, diags := addrs.ParseProviderConfigCompactStr(oldProviderAddr)
111 if diags.HasErrors() {
112 return nil, diags.Err()
113 }
114 providerAddr = localAddr.Absolute(moduleAddr)
115 } else {
116 providerAddr = resAddr.DefaultProviderConfig().Absolute(moduleAddr)
117 }
118 }
119
120 rs = &resourceStateV4{
121 Module: moduleAddr.String(),
122 Mode: modeStr,
123 Type: resAddr.Type,
124 Name: resAddr.Name,
125 Instances: []instanceObjectStateV4{},
126 ProviderConfig: providerAddr.String(),
127 }
128 resourceStates[resAddr.String()] = rs
129 }
130
131 // Now we'll deal with the instance itself, which may either be
132 // the first instance in a resource we just created or an additional
133 // instance for a resource added on a prior loop.
134 instKey := instAddr.Key
135 if isOld := rsOld.Primary; isOld != nil {
136 isNew, err := upgradeInstanceObjectV3ToV4(rsOld, isOld, instKey, states.NotDeposed)
137 if err != nil {
138 return nil, fmt.Errorf("failed to migrate primary generation of %s: %s", instAddr, err)
139 }
140 rs.Instances = append(rs.Instances, *isNew)
141 }
142 for i, isOld := range rsOld.Deposed {
143 // When we migrate old instances we'll use sequential deposed
144 // keys just so that the upgrade result is deterministic. New
145 // deposed keys allocated moving forward will be pseudorandomly
146 // selected, but we check for collisions and so these
147 // non-random ones won't hurt.
148 deposedKey := states.DeposedKey(fmt.Sprintf("%08x", i+1))
149 isNew, err := upgradeInstanceObjectV3ToV4(rsOld, isOld, instKey, deposedKey)
150 if err != nil {
151 return nil, fmt.Errorf("failed to migrate deposed generation index %d of %s: %s", i, instAddr, err)
152 }
153 rs.Instances = append(rs.Instances, *isNew)
154 }
155
156 if instKey != addrs.NoKey && rs.EachMode == "" {
157 rs.EachMode = "list"
158 }
159 }
160
161 for _, rs := range resourceStates {
162 new.Resources = append(new.Resources, *rs)
163 }
164
165 if len(msOld.Path) == 1 && msOld.Path[0] == "root" {
166 // We'll migrate the outputs for this module too, then.
167 for name, oldOS := range msOld.Outputs {
168 newOS := outputStateV4{
169 Sensitive: oldOS.Sensitive,
170 }
171
172 valRaw := oldOS.Value
173 valSrc, err := json.Marshal(valRaw)
174 if err != nil {
175 // Should never happen, because this value came from JSON
176 // in the first place and so we're just round-tripping here.
177 return nil, fmt.Errorf("failed to serialize output %q value as JSON: %s", name, err)
178 }
179
180 // The "type" field in state V2 wasn't really that useful
181 // since it was only able to capture string vs. list vs. map.
182 // For this reason, during upgrade we'll just discard it
183 // altogether and use cty's idea of the implied type of
184 // turning our old value into JSON.
185 ty, err := ctyjson.ImpliedType(valSrc)
186 if err != nil {
187 // REALLY should never happen, because we literally just
188 // encoded this as JSON above!
189 return nil, fmt.Errorf("failed to parse output %q value from JSON: %s", name, err)
190 }
191
192 // ImpliedType tends to produce structural types, but since older
193 // version of Terraform didn't support those a collection type
194 // is probably what was intended, so we'll see if we can
195 // interpret our value as one.
196 ty = simplifyImpliedValueType(ty)
197
198 tySrc, err := ctyjson.MarshalType(ty)
199 if err != nil {
200 return nil, fmt.Errorf("failed to serialize output %q type as JSON: %s", name, err)
201 }
202
203 newOS.ValueRaw = json.RawMessage(valSrc)
204 newOS.ValueTypeRaw = json.RawMessage(tySrc)
205
206 new.RootOutputs[name] = newOS
207 }
208 }
209 }
210
211 new.normalize()
212
213 return new, nil
214 }
215
216 func upgradeInstanceObjectV3ToV4(rsOld *resourceStateV2, isOld *instanceStateV2, instKey addrs.InstanceKey, deposedKey states.DeposedKey) (*instanceObjectStateV4, error) {
217
218 // Schema versions were, in prior formats, a private concern of the provider
219 // SDK, and not a first-class concept in the state format. Here we're
220 // sniffing for the pre-0.12 SDK's way of representing schema versions
221 // and promoting it to our first-class field if we find it. We'll ignore
222 // it if it doesn't look like what the SDK would've written. If this
223 // sniffing fails then we'll assume schema version 0.
224 var schemaVersion uint64
225 migratedSchemaVersion := false
226 if raw, exists := isOld.Meta["schema_version"]; exists {
227 switch tv := raw.(type) {
228 case string:
229 v, err := strconv.ParseUint(tv, 10, 64)
230 if err == nil {
231 schemaVersion = v
232 migratedSchemaVersion = true
233 }
234 case int:
235 schemaVersion = uint64(tv)
236 migratedSchemaVersion = true
237 case float64:
238 schemaVersion = uint64(tv)
239 migratedSchemaVersion = true
240 }
241 }
242
243 private := map[string]interface{}{}
244 for k, v := range isOld.Meta {
245 if k == "schema_version" && migratedSchemaVersion {
246 // We're gonna promote this into our first-class schema version field
247 continue
248 }
249 private[k] = v
250 }
251 var privateJSON []byte
252 if len(private) != 0 {
253 var err error
254 privateJSON, err = json.Marshal(private)
255 if err != nil {
256 // This shouldn't happen, because the Meta values all came from JSON
257 // originally anyway.
258 return nil, fmt.Errorf("cannot serialize private instance object data: %s", err)
259 }
260 }
261
262 var status string
263 if isOld.Tainted {
264 status = "tainted"
265 }
266
267 var instKeyRaw interface{}
268 switch tk := instKey.(type) {
269 case addrs.IntKey:
270 instKeyRaw = int(tk)
271 case addrs.StringKey:
272 instKeyRaw = string(tk)
273 default:
274 if instKeyRaw != nil {
275 return nil, fmt.Errorf("insupported instance key: %#v", instKey)
276 }
277 }
278
279 var attributes map[string]string
280 if isOld.Attributes != nil {
281 attributes = make(map[string]string, len(isOld.Attributes))
282 for k, v := range isOld.Attributes {
283 attributes[k] = v
284 }
285 }
286 if isOld.ID != "" {
287 // As a special case, if we don't already have an "id" attribute and
288 // yet there's a non-empty first-class ID on the old object then we'll
289 // create a synthetic id attribute to avoid losing that first-class id.
290 // In practice this generally arises only in tests where state literals
291 // are hand-written in a non-standard way; real code prior to 0.12
292 // would always force the first-class ID to be copied into the
293 // id attribute before storing.
294 if attributes == nil {
295 attributes = make(map[string]string, len(isOld.Attributes))
296 }
297 if idVal := attributes["id"]; idVal == "" {
298 attributes["id"] = isOld.ID
299 }
300 }
301
302 dependencies := make([]string, len(rsOld.Dependencies))
303 for i, v := range rsOld.Dependencies {
304 dependencies[i] = parseLegacyDependency(v)
305 }
306
307 return &instanceObjectStateV4{
308 IndexKey: instKeyRaw,
309 Status: status,
310 Deposed: string(deposedKey),
311 AttributesFlat: attributes,
312 Dependencies: dependencies,
313 SchemaVersion: schemaVersion,
314 PrivateRaw: privateJSON,
315 }, nil
316 }
317
318 // parseLegacyResourceAddress parses the different identifier format used
319 // state formats before version 4, like "instance.name.0".
320 func parseLegacyResourceAddress(s string) (addrs.ResourceInstance, error) {
321 var ret addrs.ResourceInstance
322
323 // Split based on ".". Every resource address should have at least two
324 // elements (type and name).
325 parts := strings.Split(s, ".")
326 if len(parts) < 2 || len(parts) > 4 {
327 return ret, fmt.Errorf("invalid internal resource address format: %s", s)
328 }
329
330 // Data resource if we have at least 3 parts and the first one is data
331 ret.Resource.Mode = addrs.ManagedResourceMode
332 if len(parts) > 2 && parts[0] == "data" {
333 ret.Resource.Mode = addrs.DataResourceMode
334 parts = parts[1:]
335 }
336
337 // If we're not a data resource and we have more than 3, then it is an error
338 if len(parts) > 3 && ret.Resource.Mode != addrs.DataResourceMode {
339 return ret, fmt.Errorf("invalid internal resource address format: %s", s)
340 }
341
342 // Build the parts of the resource address that are guaranteed to exist
343 ret.Resource.Type = parts[0]
344 ret.Resource.Name = parts[1]
345 ret.Key = addrs.NoKey
346
347 // If we have more parts, then we have an index. Parse that.
348 if len(parts) > 2 {
349 idx, err := strconv.ParseInt(parts[2], 0, 0)
350 if err != nil {
351 return ret, fmt.Errorf("error parsing resource address %q: %s", s, err)
352 }
353
354 ret.Key = addrs.IntKey(idx)
355 }
356
357 return ret, nil
358 }
359
360 // simplifyImpliedValueType attempts to heuristically simplify a value type
361 // derived from a legacy stored output value into something simpler that
362 // is closer to what would've fitted into the pre-v0.12 value type system.
363 func simplifyImpliedValueType(ty cty.Type) cty.Type {
364 switch {
365 case ty.IsTupleType():
366 // If all of the element types are the same then we'll make this
367 // a list instead. This is very likely to be true, since prior versions
368 // of Terraform did not officially support mixed-type collections.
369
370 if ty.Equals(cty.EmptyTuple) {
371 // Don't know what the element type would be, then.
372 return ty
373 }
374
375 etys := ty.TupleElementTypes()
376 ety := etys[0]
377 for _, other := range etys[1:] {
378 if !other.Equals(ety) {
379 // inconsistent types
380 return ty
381 }
382 }
383 ety = simplifyImpliedValueType(ety)
384 return cty.List(ety)
385
386 case ty.IsObjectType():
387 // If all of the attribute types are the same then we'll make this
388 // a map instead. This is very likely to be true, since prior versions
389 // of Terraform did not officially support mixed-type collections.
390
391 if ty.Equals(cty.EmptyObject) {
392 // Don't know what the element type would be, then.
393 return ty
394 }
395
396 atys := ty.AttributeTypes()
397 var ety cty.Type
398 for _, other := range atys {
399 if ety == cty.NilType {
400 ety = other
401 continue
402 }
403 if !other.Equals(ety) {
404 // inconsistent types
405 return ty
406 }
407 }
408 ety = simplifyImpliedValueType(ety)
409 return cty.Map(ety)
410
411 default:
412 // No other normalizations are possible
413 return ty
414 }
415 }
416
417 func parseLegacyDependency(s string) string {
418 parts := strings.Split(s, ".")
419 ret := parts[0]
420 for _, part := range parts[1:] {
421 if part == "*" {
422 break
423 }
424 if i, err := strconv.Atoi(part); err == nil {
425 ret = ret + fmt.Sprintf("[%d]", i)
426 break
427 }
428 ret = ret + "." + part
429 }
430 return ret
431 }