github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/eval_state_upgrade.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
     8  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
     9  	"github.com/hashicorp/terraform-plugin-sdk/internal/providers"
    10  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
    11  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    12  )
    13  
    14  // UpgradeResourceState will, if necessary, run the provider-defined upgrade
    15  // logic against the given state object to make it compliant with the
    16  // current schema version. This is a no-op if the given state object is
    17  // already at the latest version.
    18  //
    19  // If any errors occur during upgrade, error diagnostics are returned. In that
    20  // case it is not safe to proceed with using the original state object.
    21  func UpgradeResourceState(addr addrs.AbsResourceInstance, provider providers.Interface, src *states.ResourceInstanceObjectSrc, currentSchema *configschema.Block, currentVersion uint64) (*states.ResourceInstanceObjectSrc, tfdiags.Diagnostics) {
    22  	if addr.Resource.Resource.Mode != addrs.ManagedResourceMode {
    23  		// We only do state upgrading for managed resources.
    24  		return src, nil
    25  	}
    26  
    27  	stateIsFlatmap := len(src.AttrsJSON) == 0
    28  
    29  	providerType := addr.Resource.Resource.DefaultProviderConfig().Type
    30  	if src.SchemaVersion > currentVersion {
    31  		log.Printf("[TRACE] UpgradeResourceState: can't downgrade state for %s from version %d to %d", addr, src.SchemaVersion, currentVersion)
    32  		var diags tfdiags.Diagnostics
    33  		diags = diags.Append(tfdiags.Sourceless(
    34  			tfdiags.Error,
    35  			"Resource instance managed by newer provider version",
    36  			// This is not a very good error message, but we don't retain enough
    37  			// information in state to give good feedback on what provider
    38  			// version might be required here. :(
    39  			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),
    40  		))
    41  		return nil, diags
    42  	}
    43  
    44  	// If we get down here then we need to upgrade the state, with the
    45  	// provider's help.
    46  	// If this state was originally created by a version of Terraform prior to
    47  	// v0.12, this also includes translating from legacy flatmap to new-style
    48  	// representation, since only the provider has enough information to
    49  	// understand a flatmap built against an older schema.
    50  	if src.SchemaVersion != currentVersion {
    51  		log.Printf("[TRACE] UpgradeResourceState: upgrading state for %s from version %d to %d using provider %q", addr, src.SchemaVersion, currentVersion, providerType)
    52  	} else {
    53  		log.Printf("[TRACE] UpgradeResourceState: schema version of %s is still %d; calling provider %q for any other minor fixups", addr, currentVersion, providerType)
    54  	}
    55  
    56  	req := providers.UpgradeResourceStateRequest{
    57  		TypeName: addr.Resource.Resource.Type,
    58  
    59  		// TODO: The internal schema version representations are all using
    60  		// uint64 instead of int64, but unsigned integers aren't friendly
    61  		// to all protobuf target languages so in practice we use int64
    62  		// on the wire. In future we will change all of our internal
    63  		// representations to int64 too.
    64  		Version: int64(src.SchemaVersion),
    65  	}
    66  
    67  	if stateIsFlatmap {
    68  		req.RawStateFlatmap = src.AttrsFlat
    69  	} else {
    70  		req.RawStateJSON = src.AttrsJSON
    71  	}
    72  
    73  	resp := provider.UpgradeResourceState(req)
    74  	diags := resp.Diagnostics
    75  	if diags.HasErrors() {
    76  		return nil, diags
    77  	}
    78  
    79  	// After upgrading, the new value must conform to the current schema. When
    80  	// going over RPC this is actually already ensured by the
    81  	// marshaling/unmarshaling of the new value, but we'll check it here
    82  	// anyway for robustness, e.g. for in-process providers.
    83  	newValue := resp.UpgradedState
    84  	if errs := newValue.Type().TestConformance(currentSchema.ImpliedType()); len(errs) > 0 {
    85  		for _, err := range errs {
    86  			diags = diags.Append(tfdiags.Sourceless(
    87  				tfdiags.Error,
    88  				"Invalid resource state upgrade",
    89  				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)),
    90  			))
    91  		}
    92  		return nil, diags
    93  	}
    94  
    95  	new, err := src.CompleteUpgrade(newValue, currentSchema.ImpliedType(), uint64(currentVersion))
    96  	if err != nil {
    97  		// We already checked for type conformance above, so getting into this
    98  		// codepath should be rare and is probably a bug somewhere under CompleteUpgrade.
    99  		diags = diags.Append(tfdiags.Sourceless(
   100  			tfdiags.Error,
   101  			"Failed to encode result of resource state upgrade",
   102  			fmt.Sprintf("Failed to encode state for %s after resource schema upgrade: %s.", addr, tfdiags.FormatError(err)),
   103  		))
   104  	}
   105  	return new, diags
   106  }