| package terraform |
| |
| import ( |
| "fmt" |
| "log" |
| |
| "github.com/hashicorp/terraform/addrs" |
| "github.com/hashicorp/terraform/configs/configschema" |
| "github.com/hashicorp/terraform/providers" |
| "github.com/hashicorp/terraform/states" |
| "github.com/hashicorp/terraform/tfdiags" |
| ) |
| |
| // UpgradeResourceState will, if necessary, run the provider-defined upgrade |
| // logic against the given state object to make it compliant with the |
| // current schema version. This is a no-op if the given state object is |
| // already at the latest version. |
| // |
| // If any errors occur during upgrade, error diagnostics are returned. In that |
| // case it is not safe to proceed with using the original state object. |
| func UpgradeResourceState(addr addrs.AbsResourceInstance, provider providers.Interface, src *states.ResourceInstanceObjectSrc, currentSchema *configschema.Block, currentVersion uint64) (*states.ResourceInstanceObjectSrc, tfdiags.Diagnostics) { |
| if addr.Resource.Resource.Mode != addrs.ManagedResourceMode { |
| // We only do state upgrading for managed resources. |
| return src, nil |
| } |
| |
| stateIsFlatmap := len(src.AttrsJSON) == 0 |
| |
| providerType := addr.Resource.Resource.DefaultProviderConfig().Type |
| if src.SchemaVersion > currentVersion { |
| log.Printf("[TRACE] UpgradeResourceState: can't downgrade state for %s from version %d to %d", addr, src.SchemaVersion, currentVersion) |
| var diags tfdiags.Diagnostics |
| diags = diags.Append(tfdiags.Sourceless( |
| tfdiags.Error, |
| "Resource instance managed by newer provider version", |
| // This is not a very good error message, but we don't retain enough |
| // information in state to give good feedback on what provider |
| // version might be required here. :( |
| fmt.Sprintf("The current state of %s was created by a newer provider version than is currently selected. Upgrade the %s provider to work with this state.", addr, providerType), |
| )) |
| return nil, diags |
| } |
| |
| // If we get down here then we need to upgrade the state, with the |
| // provider's help. |
| // If this state was originally created by a version of Terraform prior to |
| // v0.12, this also includes translating from legacy flatmap to new-style |
| // representation, since only the provider has enough information to |
| // understand a flatmap built against an older schema. |
| if src.SchemaVersion != currentVersion { |
| log.Printf("[TRACE] UpgradeResourceState: upgrading state for %s from version %d to %d using provider %q", addr, src.SchemaVersion, currentVersion, providerType) |
| } else { |
| log.Printf("[TRACE] UpgradeResourceState: schema version of %s is still %d; calling provider %q for any other minor fixups", addr, currentVersion, providerType) |
| } |
| |
| req := providers.UpgradeResourceStateRequest{ |
| TypeName: addr.Resource.Resource.Type, |
| |
| // TODO: The internal schema version representations are all using |
| // uint64 instead of int64, but unsigned integers aren't friendly |
| // to all protobuf target languages so in practice we use int64 |
| // on the wire. In future we will change all of our internal |
| // representations to int64 too. |
| Version: int64(src.SchemaVersion), |
| } |
| |
| if stateIsFlatmap { |
| req.RawStateFlatmap = src.AttrsFlat |
| } else { |
| req.RawStateJSON = src.AttrsJSON |
| } |
| |
| resp := provider.UpgradeResourceState(req) |
| diags := resp.Diagnostics |
| if diags.HasErrors() { |
| return nil, diags |
| } |
| |
| // After upgrading, the new value must conform to the current schema. When |
| // going over RPC this is actually already ensured by the |
| // marshaling/unmarshaling of the new value, but we'll check it here |
| // anyway for robustness, e.g. for in-process providers. |
| newValue := resp.UpgradedState |
| if errs := newValue.Type().TestConformance(currentSchema.ImpliedType()); len(errs) > 0 { |
| for _, err := range errs { |
| diags = diags.Append(tfdiags.Sourceless( |
| tfdiags.Error, |
| "Invalid resource state upgrade", |
| fmt.Sprintf("The %s provider upgraded the state for %s from a previous version, but produced an invalid result: %s.", providerType, addr, tfdiags.FormatError(err)), |
| )) |
| } |
| return nil, diags |
| } |
| |
| new, err := src.CompleteUpgrade(newValue, currentSchema.ImpliedType(), uint64(currentVersion)) |
| if err != nil { |
| // We already checked for type conformance above, so getting into this |
| // codepath should be rare and is probably a bug somewhere under CompleteUpgrade. |
| diags = diags.Append(tfdiags.Sourceless( |
| tfdiags.Error, |
| "Failed to encode result of resource state upgrade", |
| fmt.Sprintf("Failed to encode state for %s after resource schema upgrade: %s.", addr, tfdiags.FormatError(err)), |
| )) |
| } |
| return new, diags |
| } |