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
40 changes: 40 additions & 0 deletions helper/schema/grpc_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"strconv"
"strings"
"sync"

"github.com/hashicorp/go-cty/cty"
Expand Down Expand Up @@ -880,6 +881,7 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err)
return resp, nil
}

// Step 2: Turn cty.Value into flatmap representation
identityAttrs := hcl2shim.FlatmapValueFromHCL2(currentIdentityVal)
// Step 3: Well, set it in the instanceState
Expand Down Expand Up @@ -961,6 +963,22 @@ 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 {
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",
))
return resp, nil
}

// 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) {
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"+
Expand Down Expand Up @@ -1544,6 +1562,28 @@ 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 {
op := "Create"
if !create {
op = "Update"
}

resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf(
"Missing Resource Identity After %s: The Terraform provider unexpectedly returned no resource identity after having no errors in the resource %s. "+
"This is always a problem with the provider and should be reported to the provider developer", op, strings.ToLower(op),
))

return resp, nil
}

if !res.ResourceBehavior.MutableIdentity && !create && !plannedIdentityVal.IsNull() && !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"+
Expand Down
198 changes: 198 additions & 0 deletions helper/schema/grpc_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5707,6 +5707,103 @@ func TestReadResource(t *testing.T) {
},
},
},
"prevent-null-identity": {
server: NewGRPCProviderServer(&Provider{
ResourcesMap: map[string]*Resource{
"test": {
SchemaVersion: 1,
Schema: map[string]*Schema{
"id": {
Type: TypeString,
Required: true,
},
"test": {
Type: TypeString,
},
},
Identity: &ResourceIdentity{
Version: 1,
SchemaFunc: func() map[string]*Schema {
return map[string]*Schema{
"subscription_id": {
Type: TypeString,
RequiredForImport: true,
},
"resource_group_name": {
Type: TypeString,
RequiredForImport: true,
},
"name": {
Type: TypeString,
RequiredForImport: true,
},
}
},
},
ReadContext: func(ctx context.Context, d *ResourceData, meta interface{}) diag.Diagnostics {
err := d.Set("test", "hello")
if err != nil {
return diag.FromErr(err)
}

return nil
},
},
},
}),
req: &tfprotov5.ReadResourceRequest{
TypeName: "test",
CurrentIdentity: &tfprotov5.ResourceIdentityData{
IdentityData: &tfprotov5.DynamicValue{
MsgPack: mustMsgpackMarshal(
cty.Object(map[string]cty.Type{
"subscription_id": cty.String,
"resource_group_name": cty.String,
"name": cty.String,
}),
cty.ObjectVal(map[string]cty.Value{
"subscription_id": cty.NullVal(cty.String),
"resource_group_name": cty.NullVal(cty.String),
"name": cty.NullVal(cty.String),
}),
),
},
},
CurrentState: &tfprotov5.DynamicValue{
MsgPack: mustMsgpackMarshal(
cty.Object(map[string]cty.Type{
"id": cty.String,
"test": cty.String,
}),
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("initial"),
"test": cty.UnknownVal(cty.String),
}),
),
},
},
expected: &tfprotov5.ReadResourceResponse{
NewState: &tfprotov5.DynamicValue{
MsgPack: mustMsgpackMarshal(
cty.Object(map[string]cty.Type{
"id": cty.String,
"test": cty.String,
}),
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("initial"),
"test": cty.StringVal("hello"),
}),
),
},
Diagnostics: []*tfprotov5.Diagnostic{
{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "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",
},
},
},
},
"update-resource-identity-may-not-change": {
server: NewGRPCProviderServer(&Provider{
ResourcesMap: map[string]*Resource{
Expand Down Expand Up @@ -8233,6 +8330,107 @@ func TestApplyResourceChange(t *testing.T) {
},
},
},
"create: null identity not allowed in ApplyResourceChangeResponse": {
server: NewGRPCProviderServer(&Provider{
ResourcesMap: map[string]*Resource{
"test": {
SchemaVersion: 4,
CreateContext: func(_ context.Context, rd *ResourceData, _ interface{}) diag.Diagnostics {
rd.SetId("baz")

return nil
},
Schema: map[string]*Schema{},
Identity: &ResourceIdentity{
Version: 1,
SchemaFunc: func() map[string]*Schema {
return map[string]*Schema{
"subscription_id": {
Type: TypeString,
RequiredForImport: true,
},
"resource_group_name": {
Type: TypeString,
RequiredForImport: true,
},
"name": {
Type: TypeString,
RequiredForImport: true,
},
}
},
},
},
},
}),
req: &tfprotov5.ApplyResourceChangeRequest{
TypeName: "test",
PriorState: &tfprotov5.DynamicValue{
MsgPack: mustMsgpackMarshal(
cty.Object(map[string]cty.Type{}),
cty.NullVal(
cty.Object(map[string]cty.Type{}),
),
),
},
PlannedState: &tfprotov5.DynamicValue{
MsgPack: mustMsgpackMarshal(
cty.Object(map[string]cty.Type{
"id": cty.String,
}),
cty.ObjectVal(map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
}),
),
},
PlannedIdentity: &tfprotov5.ResourceIdentityData{
IdentityData: &tfprotov5.DynamicValue{
MsgPack: mustMsgpackMarshal(
cty.Object(map[string]cty.Type{
"subscription_id": cty.String,
"resource_group_name": cty.String,
"name": cty.String,
}),
cty.ObjectVal(map[string]cty.Value{
"subscription_id": cty.NullVal(cty.String),
"resource_group_name": cty.NullVal(cty.String),
"name": cty.NullVal(cty.String),
}),
),
},
},
Config: &tfprotov5.DynamicValue{
MsgPack: mustMsgpackMarshal(
cty.Object(map[string]cty.Type{
"id": cty.String,
}),
cty.ObjectVal(map[string]cty.Value{
"id": cty.NullVal(cty.String),
}),
),
},
},
expected: &tfprotov5.ApplyResourceChangeResponse{
NewState: &tfprotov5.DynamicValue{
MsgPack: mustMsgpackMarshal(
cty.Object(map[string]cty.Type{
"id": cty.String,
}),
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("baz"),
}),
),
},
Private: []uint8(`{"schema_version":"4"}`),
Diagnostics: []*tfprotov5.Diagnostic{
{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Missing Resource Identity After Create: The Terraform provider unexpectedly returned no resource identity after having no errors in the resource create. " +
"This is always a problem with the provider and should be reported to the provider developer",
},
},
},
},
"create-resource-identity-may-change": {
server: NewGRPCProviderServer(&Provider{
ResourcesMap: map[string]*Resource{
Expand Down
Loading