@@ -788,11 +788,43 @@ func (s *GRPCProviderServer) ConfigureProvider(ctx context.Context, req *tfproto
788788
789789func (s * GRPCProviderServer ) ReadResource (ctx context.Context , req * tfprotov5.ReadResourceRequest ) (* tfprotov5.ReadResourceResponse , error ) {
790790 ctx = logging .InitContext (ctx )
791+ readFollowingImport := false
792+
793+ ReqPrivate := req .Private
794+
795+ if ReqPrivate != nil {
796+ // unmarshal the private data
797+ if len (ReqPrivate ) > 0 {
798+ newReqPrivate := make (map [string ]interface {})
799+ if err := json .Unmarshal (ReqPrivate , & newReqPrivate ); err != nil {
800+ return nil , err
801+ }
802+ // This internal private field is set on a resource during ImportResourceState to help framework determine if
803+ // the resource has been recently imported. We only need to read this once, so we immediately clear it after.
804+ if _ , ok := newReqPrivate [terraform .ImportBeforeReadMetaKey ]; ok {
805+ readFollowingImport = true
806+ delete (newReqPrivate , terraform .ImportBeforeReadMetaKey )
807+
808+ if len (newReqPrivate ) == 0 {
809+ // if there are no other private data, set the private data to nil
810+ ReqPrivate = nil
811+ } else {
812+ // set the new private data without the import key
813+ bytes , err := json .Marshal (newReqPrivate )
814+ if err != nil {
815+ return nil , err
816+ }
817+ ReqPrivate = bytes
818+ }
819+ }
820+ }
821+ }
822+
791823 resp := & tfprotov5.ReadResourceResponse {
792824 // helper/schema did previously handle private data during refresh, but
793825 // core is now going to expect this to be maintained in order to
794826 // persist it in the state.
795- Private : req . Private ,
827+ Private : ReqPrivate ,
796828 }
797829
798830 res , ok := s .provider .ResourcesMap [req .TypeName ]
@@ -832,7 +864,7 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re
832864 }
833865 instanceState .RawState = stateVal
834866
835- // TODO: is there a more elegant way to do this? this requires us to look for the identity schema block again
867+ var currentIdentityVal cty. Value
836868 if req .CurrentIdentity != nil && req .CurrentIdentity .IdentityData != nil {
837869
838870 // convert req.CurrentIdentity to flat map identity structure
@@ -843,20 +875,20 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re
843875 return resp , nil
844876 }
845877
846- identityVal , err : = msgpack .Unmarshal (req .CurrentIdentity .IdentityData .MsgPack , identityBlock .ImpliedType ())
878+ currentIdentityVal , err = msgpack .Unmarshal (req .CurrentIdentity .IdentityData .MsgPack , identityBlock .ImpliedType ())
847879 if err != nil {
848880 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
849881 return resp , nil
850882 }
851883 // Step 2: Turn cty.Value into flatmap representation
852- identityAttrs := hcl2shim .FlatmapValueFromHCL2 (identityVal )
884+ identityAttrs := hcl2shim .FlatmapValueFromHCL2 (currentIdentityVal )
853885 // Step 3: Well, set it in the instanceState
854886 instanceState .Identity = identityAttrs
855887 }
856888
857889 private := make (map [string ]interface {})
858- if len (req . Private ) > 0 {
859- if err := json .Unmarshal (req . Private , & private ); err != nil {
890+ if len (ReqPrivate ) > 0 {
891+ if err := json .Unmarshal (ReqPrivate , & private ); err != nil {
860892 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
861893 return resp , nil
862894 }
@@ -929,6 +961,15 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re
929961 return resp , nil
930962 }
931963
964+ // If we're refreshing the resource state (excluding a recently imported resource), validate that the new identity isn't changing
965+ if ! readFollowingImport && ! currentIdentityVal .IsNull () && ! currentIdentityVal .RawEquals (newIdentityVal ) {
966+ resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , fmt .Errorf ("Unexpected Identity Change: %s" , "During the read operation, the Terraform Provider unexpectedly returned a different identity then the previously stored one.\n \n " +
967+ "This is always a problem with the provider and should be reported to the provider developer.\n \n " +
968+ fmt .Sprintf ("Current Identity: %s\n \n " , currentIdentityVal .GoString ())+
969+ fmt .Sprintf ("New Identity: %s" , newIdentityVal .GoString ())))
970+ return resp , nil
971+ }
972+
932973 newIdentityMP , err := msgpack .Marshal (newIdentityVal , identityBlock .ImpliedType ())
933974 if err != nil {
934975 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
@@ -1052,6 +1093,7 @@ func (s *GRPCProviderServer) PlanResourceChange(ctx context.Context, req *tfprot
10521093 // turn the proposed state into a legacy configuration
10531094 cfg := terraform .NewResourceConfigShimmed (proposedNewStateVal , schemaBlock )
10541095
1096+ var priorIdentityVal cty.Value
10551097 // add identity data to priorState
10561098 if req .PriorIdentity != nil && req .PriorIdentity .IdentityData != nil {
10571099 // convert req.PriorIdentity to flat map identity structure
@@ -1062,13 +1104,13 @@ func (s *GRPCProviderServer) PlanResourceChange(ctx context.Context, req *tfprot
10621104 return resp , nil
10631105 }
10641106
1065- identityVal , err : = msgpack .Unmarshal (req .PriorIdentity .IdentityData .MsgPack , identityBlock .ImpliedType ())
1107+ priorIdentityVal , err = msgpack .Unmarshal (req .PriorIdentity .IdentityData .MsgPack , identityBlock .ImpliedType ())
10661108 if err != nil {
10671109 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
10681110 return resp , nil
10691111 }
10701112 // Step 2: Turn cty.Value into flatmap representation
1071- identityAttrs := hcl2shim .FlatmapValueFromHCL2 (identityVal )
1113+ identityAttrs := hcl2shim .FlatmapValueFromHCL2 (priorIdentityVal )
10721114 // Step 3: Well, set it in the priorState
10731115 priorState .Identity = identityAttrs
10741116 }
@@ -1257,21 +1299,34 @@ func (s *GRPCProviderServer) PlanResourceChange(ctx context.Context, req *tfprot
12571299 return resp , nil
12581300 }
12591301
1260- newIdentityVal , err := hcl2shim .HCL2ValueFromFlatmap (diff .Identity , identityBlock .ImpliedType ())
1302+ plannedIdentityVal , err := hcl2shim .HCL2ValueFromFlatmap (diff .Identity , identityBlock .ImpliedType ())
12611303 if err != nil {
12621304 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
12631305 return resp , nil
12641306 }
12651307
1266- newIdentityMP , err := msgpack .Marshal (newIdentityVal , identityBlock .ImpliedType ())
1308+ // If we're updating or deleting and we already have an identity stored, validate that the planned identity isn't changing
1309+ if ! create && ! priorIdentityVal .IsNull () && ! priorIdentityVal .RawEquals (plannedIdentityVal ) {
1310+ resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , fmt .Errorf (
1311+ "Unexpected Identity Change: During the planning operation, the Terraform Provider unexpectedly returned a different identity than the previously stored one.\n \n " +
1312+ "This is always a problem with the provider and should be reported to the provider developer.\n \n " +
1313+ "Prior Identity: %s\n \n Planned Identity: %s" ,
1314+ priorIdentityVal .GoString (),
1315+ plannedIdentityVal .GoString (),
1316+ ))
1317+
1318+ return resp , nil
1319+ }
1320+
1321+ plannedIdentityMP , err := msgpack .Marshal (plannedIdentityVal , identityBlock .ImpliedType ())
12671322 if err != nil {
12681323 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
12691324 return resp , nil
12701325 }
12711326
12721327 resp .PlannedIdentity = & tfprotov5.ResourceIdentityData {
12731328 IdentityData : & tfprotov5.DynamicValue {
1274- MsgPack : newIdentityMP ,
1329+ MsgPack : plannedIdentityMP ,
12751330 },
12761331 }
12771332 }
@@ -1299,6 +1354,8 @@ func (s *GRPCProviderServer) ApplyResourceChange(ctx context.Context, req *tfpro
12991354 return resp , nil
13001355 }
13011356
1357+ create := priorStateVal .IsNull ()
1358+
13021359 plannedStateVal , err := msgpack .Unmarshal (req .PlannedState .MsgPack , schemaBlock .ImpliedType ())
13031360 if err != nil {
13041361 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
@@ -1325,6 +1382,7 @@ func (s *GRPCProviderServer) ApplyResourceChange(ctx context.Context, req *tfpro
13251382 }
13261383 }
13271384
1385+ var plannedIdentityVal cty.Value
13281386 // add identity data to priorState
13291387 if req .PlannedIdentity != nil && req .PlannedIdentity .IdentityData != nil {
13301388 // convert req.PriorIdentity to flat map identity structure
@@ -1335,13 +1393,13 @@ func (s *GRPCProviderServer) ApplyResourceChange(ctx context.Context, req *tfpro
13351393 return resp , nil
13361394 }
13371395
1338- identityVal , err : = msgpack .Unmarshal (req .PlannedIdentity .IdentityData .MsgPack , identityBlock .ImpliedType ())
1396+ plannedIdentityVal , err = msgpack .Unmarshal (req .PlannedIdentity .IdentityData .MsgPack , identityBlock .ImpliedType ())
13391397 if err != nil {
13401398 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
13411399 return resp , nil
13421400 }
13431401 // Step 2: Turn cty.Value into flatmap representation
1344- identityAttrs := hcl2shim .FlatmapValueFromHCL2 (identityVal )
1402+ identityAttrs := hcl2shim .FlatmapValueFromHCL2 (plannedIdentityVal )
13451403 // Step 3: Well, set it in the priorState
13461404 priorState .Identity = identityAttrs
13471405 }
@@ -1489,6 +1547,18 @@ func (s *GRPCProviderServer) ApplyResourceChange(ctx context.Context, req *tfpro
14891547 return resp , nil
14901548 }
14911549
1550+ if ! create && ! plannedIdentityVal .IsNull () && ! plannedIdentityVal .RawEquals (newIdentityVal ) {
1551+ resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , fmt .Errorf (
1552+ "Unexpected Identity Change: During the update operation, the Terraform Provider unexpectedly returned a different identity than the previously stored one.\n \n " +
1553+ "This is always a problem with the provider and should be reported to the provider developer.\n \n " +
1554+ "Planned Identity: %s\n \n New Identity: %s" ,
1555+ plannedIdentityVal .GoString (),
1556+ newIdentityVal .GoString (),
1557+ ))
1558+
1559+ return resp , nil
1560+ }
1561+
14921562 newIdentityMP , err := msgpack .Marshal (newIdentityVal , identityBlock .ImpliedType ())
14931563 if err != nil {
14941564 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
@@ -1636,6 +1706,10 @@ func (s *GRPCProviderServer) ImportResourceState(ctx context.Context, req *tfpro
16361706 return resp , nil
16371707 }
16381708
1709+ // Set an internal private field that will get sent alongside the imported resource. This will be cleared by
1710+ // the following ReadResource RPC and is primarily used to control validation of resource identities during refresh.
1711+ is .Meta [terraform .ImportBeforeReadMetaKey ] = true
1712+
16391713 meta , err := json .Marshal (is .Meta )
16401714 if err != nil {
16411715 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
@@ -2262,7 +2336,7 @@ func (s *GRPCProviderServer) upgradeJSONIdentity(ctx context.Context, version in
22622336
22632337 for _ , upgrader := range res .Identity .IdentityUpgraders {
22642338 if version != upgrader .Version {
2265- continue
2339+ continue // TODO: can we replace this with an error (as we'll require all versions to be upgraded)
22662340 }
22672341
22682342 m , err = upgrader .Upgrade (ctx , m , s .provider .Meta ())
0 commit comments