Skip to content

Commit f5ba712

Browse files
authored
identity: Update change validation to ensure empty identities (all null attributes) are not validated (#1527)
* add test repro first * add fix
1 parent ef9dd9f commit f5ba712

File tree

3 files changed

+397
-21
lines changed

3 files changed

+397
-21
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: BUG FIXES
2+
body: 'all: Prevent identity change validation from raising an error when prior identity is empty (all attributes are null)'
3+
time: 2025-09-22T09:19:42.075962-04:00
4+
custom:
5+
Issue: "1527"

helper/schema/grpc_provider.go

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -968,15 +968,7 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re
968968
return resp, nil
969969
}
970970

971-
isFullyNull := true
972-
for _, v := range newIdentityVal.AsValueMap() {
973-
if !v.IsNull() {
974-
isFullyNull = false
975-
break
976-
}
977-
}
978-
979-
if isFullyNull {
971+
if isCtyObjectNullOrEmpty(newIdentityVal) {
980972
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf(
981973
"Missing Resource Identity After Read: The Terraform provider unexpectedly returned no resource identity after having no errors in the resource read. "+
982974
"This is always a problem with the provider and should be reported to the provider developer",
@@ -985,7 +977,7 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re
985977
}
986978

987979
// If we're refreshing the resource state (excluding a recently imported resource), validate that the new identity isn't changing
988-
if !res.ResourceBehavior.MutableIdentity && !readFollowingImport && !currentIdentityVal.IsNull() && !currentIdentityVal.RawEquals(newIdentityVal) {
980+
if !res.ResourceBehavior.MutableIdentity && !readFollowingImport && !isCtyObjectNullOrEmpty(currentIdentityVal) && !currentIdentityVal.RawEquals(newIdentityVal) {
989981
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"+
990982
"This is always a problem with the provider and should be reported to the provider developer.\n\n"+
991983
fmt.Sprintf("Current Identity: %s\n\n", currentIdentityVal.GoString())+
@@ -1327,7 +1319,7 @@ func (s *GRPCProviderServer) PlanResourceChange(ctx context.Context, req *tfprot
13271319
}
13281320

13291321
// If we're updating or deleting and we already have an identity stored, validate that the planned identity isn't changing
1330-
if !res.ResourceBehavior.MutableIdentity && !create && !priorIdentityVal.IsNull() && !priorIdentityVal.RawEquals(plannedIdentityVal) {
1322+
if !res.ResourceBehavior.MutableIdentity && !create && !isCtyObjectNullOrEmpty(priorIdentityVal) && !priorIdentityVal.RawEquals(plannedIdentityVal) {
13311323
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf(
13321324
"Unexpected Identity Change: During the planning operation, the Terraform Provider unexpectedly returned a different identity than the previously stored one.\n\n"+
13331325
"This is always a problem with the provider and should be reported to the provider developer.\n\n"+
@@ -1567,15 +1559,7 @@ func (s *GRPCProviderServer) ApplyResourceChange(ctx context.Context, req *tfpro
15671559
return resp, nil
15681560
}
15691561

1570-
isFullyNull := true
1571-
for _, v := range newIdentityVal.AsValueMap() {
1572-
if !v.IsNull() {
1573-
isFullyNull = false
1574-
break
1575-
}
1576-
}
1577-
1578-
if isFullyNull {
1562+
if isCtyObjectNullOrEmpty(newIdentityVal) {
15791563
op := "Create"
15801564
if !create {
15811565
op = "Update"
@@ -1589,7 +1573,7 @@ func (s *GRPCProviderServer) ApplyResourceChange(ctx context.Context, req *tfpro
15891573
return resp, nil
15901574
}
15911575

1592-
if !res.ResourceBehavior.MutableIdentity && !create && !plannedIdentityVal.IsNull() && !plannedIdentityVal.RawEquals(newIdentityVal) {
1576+
if !res.ResourceBehavior.MutableIdentity && !create && !isCtyObjectNullOrEmpty(plannedIdentityVal) && !plannedIdentityVal.RawEquals(newIdentityVal) {
15931577
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf(
15941578
"Unexpected Identity Change: During the update operation, the Terraform Provider unexpectedly returned a different identity than the previously stored one.\n\n"+
15951579
"This is always a problem with the provider and should be reported to the provider developer.\n\n"+
@@ -2494,3 +2478,18 @@ func (s *GRPCProviderServer) upgradeJSONIdentity(ctx context.Context, version in
24942478

24952479
return m, nil
24962480
}
2481+
2482+
// isCtyObjectNullOrEmpty is a helper function that checks if a given cty object is null or if all it's immediate children are null (empty)
2483+
func isCtyObjectNullOrEmpty(val cty.Value) bool {
2484+
if val.IsNull() {
2485+
return true
2486+
}
2487+
2488+
for _, v := range val.AsValueMap() {
2489+
if !v.IsNull() {
2490+
return false
2491+
}
2492+
}
2493+
2494+
return true
2495+
}

0 commit comments

Comments
 (0)