@ -16,6 +16,10 @@ import (
"google.golang.org/protobuf/types/known/anypb"
)
// LoadFromProto produces a [State] object by decoding a raw state map.
//
// This is the primary way to load a "prior state" provided by a caller
// into memory so we can use it in the stack runtime.
func LoadFromProto ( msgs map [ string ] * anypb . Any ) ( * State , error ) {
ret := NewState ( )
ret . inputRaw = msgs
@ -30,47 +34,9 @@ func LoadFromProto(msgs map[string]*anypb.Any) (*State, error) {
}
if ! statekeys . RecognizedType ( key ) {
// There are three different strategies for dealing with
// unrecognized keys, which we recognize based on naming
// conventions of the key types.
switch handling := key . KeyType ( ) . UnrecognizedKeyHandling ( ) ; handling {
case statekeys . FailIfUnrecognized :
// This is for keys whose messages materially change the
// meaning of the state and so cannot be ignored. Keys
// with this treatment are forwards-incompatible (old versions
// of Terraform will fail to load a state containing them) so
// should be added only as a last resort.
return nil , fmt . Errorf ( "state was created by a newer version of Terraform Core (unrecognized tracking key %q)" , rawKey )
case statekeys . PreserveIfUnrecognized :
// This is for keys whose messages can safely be left entirely
// unchanged if applying a plan with a version of Terraform
// that doesn't understand them. Keys in this category should
// typically be standalone and not refer to or depend on any
// other objects in the state, to ensure that removing or
// updating other objects will not cause the preserved message
// to become misleading or invalid.
// We don't need to do anything special with these ones because
// the caller should preserve any object we don't explicitly
// update or delete during the apply phase.
case statekeys . DiscardIfUnrecognized :
// This is for keys which can be discarded when planning or
// applying with an older version of Terraform that doesn't
// understand them. This category is for optional ancillary
// information -- not actually required for correct subsequent
// planning -- especially if it could be recomputed again and
// repopulated if later planning and applying with a newer
// version of Terraform Core.
// For these ones we need to remember their keys so that we
// can emit "delete" messages early in the apply phase to
// actually discard them from the caller's records.
ret . discardUnsupportedKeys . Add ( key )
default :
// Should not get here. The above should be exhaustive.
panic ( fmt . Sprintf ( "unsupported UnrecognizedKeyHandling value %s" , handling ) )
err = handleUnrecognizedKey ( key , ret )
if err != nil {
return nil , err
}
continue
}
@ -80,29 +46,116 @@ func LoadFromProto(msgs map[string]*anypb.Any) (*State, error) {
return nil , fmt . Errorf ( "invalid raw value for raw state key %q: %w" , rawKey , err )
}
switch key := key . ( type ) {
case statekeys . ComponentInstance :
err := handleComponentInstanceMsg ( key , msg , ret )
if err != nil {
return nil , err
}
err = handleProtoMsg ( key , msg , ret )
if err != nil {
return nil , err
}
}
return ret , nil
}
case statekeys . ResourceInstanceObject :
err := handleResourceInstanceObjectMsg ( key , msg , ret )
// LoadFromDirectProto is a variation of the primary entry-point [LoadFromProto]
// which accepts direct messages of the relevant types from the tfstackdata1
// package, rather than the [anypb.Raw] representation thereof.
//
// This is primarily for internal testing purposes, where it's typically more
// convenient to write out a struct literal for one of the message types
// directly rather than having to first serialize it to [anypb.Any] only for
// it to be unserialized again promptly afterwards.
//
// Unlike [LoadFromProto], the state object produced by this function will not
// have any record of the "raw state" it was created from, because this function
// is bypassing the concept of raw state. [State.InputRaw] will therefore
// return an empty map.
//
// Prefer to use [LoadFromProto] when processing user input. This function
// cannot accept [anypb.Any] messages even though the Go compiler can't enforce
// that at compile time.
func LoadFromDirectProto ( msgs map [ statekeys . Key ] protoreflect . ProtoMessage ) ( * State , error ) {
ret := NewState ( )
ret . inputRaw = nil // this doesn't get populated by this entry point
for key , msg := range msgs {
// The following should be equivalent to the similar loop in
// [LoadFromProto] except for skipping the parsing/unmarshalling
// steps since key and msg are already in their in-memory forms.
if ! statekeys . RecognizedType ( key ) {
err := handleUnrecognizedKey ( key , ret )
if err != nil {
return nil , err
}
default :
// Should not get here: the above should be exhaustive for all
// possible key types.
panic ( fmt . Sprintf ( "unsupported state key type %T" , key ) )
continue
}
err := handleProtoMsg ( key , msg , ret )
if err != nil {
return nil , err
}
}
return ret , nil
}
func handleUnrecognizedKey ( key statekeys . Key , state * State ) error {
// There are three different strategies for dealing with
// unrecognized keys, which we recognize based on naming
// conventions of the key types.
switch handling := key . KeyType ( ) . UnrecognizedKeyHandling ( ) ; handling {
case statekeys . FailIfUnrecognized :
// This is for keys whose messages materially change the
// meaning of the state and so cannot be ignored. Keys
// with this treatment are forwards-incompatible (old versions
// of Terraform will fail to load a state containing them) so
// should be added only as a last resort.
return fmt . Errorf ( "state was created by a newer version of Terraform Core (unrecognized tracking key %q)" , statekeys . String ( key ) )
case statekeys . PreserveIfUnrecognized :
// This is for keys whose messages can safely be left entirely
// unchanged if applying a plan with a version of Terraform
// that doesn't understand them. Keys in this category should
// typically be standalone and not refer to or depend on any
// other objects in the state, to ensure that removing or
// updating other objects will not cause the preserved message
// to become misleading or invalid.
// We don't need to do anything special with these ones because
// the caller should preserve any object we don't explicitly
// update or delete during the apply phase.
return nil
case statekeys . DiscardIfUnrecognized :
// This is for keys which can be discarded when planning or
// applying with an older version of Terraform that doesn't
// understand them. This category is for optional ancillary
// information -- not actually required for correct subsequent
// planning -- especially if it could be recomputed again and
// repopulated if later planning and applying with a newer
// version of Terraform Core.
// For these ones we need to remember their keys so that we
// can emit "delete" messages early in the apply phase to
// actually discard them from the caller's records.
state . discardUnsupportedKeys . Add ( key )
return nil
default :
// Should not get here. The above should be exhaustive.
panic ( fmt . Sprintf ( "unsupported UnrecognizedKeyHandling value %s" , handling ) )
}
}
func handleProtoMsg ( key statekeys . Key , msg protoreflect . ProtoMessage , state * State ) error {
switch key := key . ( type ) {
case statekeys . ComponentInstance :
return handleComponentInstanceMsg ( key , msg , state )
case statekeys . ResourceInstanceObject :
return handleResourceInstanceObjectMsg ( key , msg , state )
default :
// Should not get here: the above should be exhaustive for all
// possible key types.
panic ( fmt . Sprintf ( "unsupported state key type %T" , key ) )
}
}
func handleComponentInstanceMsg ( key statekeys . ComponentInstance , msg protoreflect . ProtoMessage , state * State ) error {
// For this particular object type all of the information is in the key,
// for now at least.