Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/unreleased/BUG FIXES-20250922-091942.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BUG FIXES
body: 'all: Prevent identity change validation from raising an error when prior identity is empty (all attributes are null)'
time: 2025-09-22T09:19:42.075962-04:00
custom:
Issue: "1527"
41 changes: 20 additions & 21 deletions helper/schema/grpc_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -968,15 +968,7 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re
return resp, nil
}

isFullyNull := true
for _, v := range newIdentityVal.AsValueMap() {
if !v.IsNull() {
isFullyNull = false
break
}
}

if isFullyNull {
if isCtyObjectNullOrEmpty(newIdentityVal) {
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf(
"Missing Resource Identity After Read: The Terraform provider unexpectedly returned no resource identity after having no errors in the resource read. "+
"This is always a problem with the provider and should be reported to the provider developer",
Expand All @@ -985,7 +977,7 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re
}

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

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

isFullyNull := true
for _, v := range newIdentityVal.AsValueMap() {
if !v.IsNull() {
isFullyNull = false
break
}
}

if isFullyNull {
if isCtyObjectNullOrEmpty(newIdentityVal) {
op := "Create"
if !create {
op = "Update"
Expand All @@ -1589,7 +1573,7 @@ func (s *GRPCProviderServer) ApplyResourceChange(ctx context.Context, req *tfpro
return resp, nil
}

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

return m, nil
}

// isCtyObjectNullOrEmpty is a helper function that checks if a given cty object is null or if all it's immediate children are null (empty)
func isCtyObjectNullOrEmpty(val cty.Value) bool {
if val.IsNull() {
return true
}

for _, v := range val.AsValueMap() {
if !v.IsNull() {
return false
}
}

return true
}
Loading