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