validate action deferrals in grpc client

pull/38668/head
James Bardin 1 month ago
parent 844084783b
commit 4b4a7f85d5

@ -23,6 +23,7 @@ import (
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/plugin/convert"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/tfdiags"
proto "github.com/hashicorp/terraform/internal/tfplugin5"
)
@ -1507,11 +1508,17 @@ func (p *GRPCProvider) PlanAction(r providers.PlanActionRequest) (resp providers
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
if resp.Diagnostics.HasErrors() {
return resp
// We only allow deferral from the provider as a whole. The provider must be
// able to accept unknown configuration.
if protoResp.Deferred != nil && protoResp.Deferred.Reason != proto.Deferred_PROVIDER_CONFIG_UNKNOWN {
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
tfdiags.Error,
"Invalid deferred reason",
fmt.Sprintf("An action can only be deferred due to an unknown provider configuration. Provider %s returned %s.", p.Addr.ForDisplay(), protoResp.Deferred.Reason),
))
}
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
return resp
}

@ -1646,6 +1646,37 @@ func TestGRPCProvider_planAction_invalid_config(t *testing.T) {
checkDiagsHasError(t, resp.Diagnostics)
}
func TestGRPCProvider_planAction_invalid_defer(t *testing.T) {
client := mockProviderClient(t)
client.EXPECT().PlanAction(
gomock.Any(),
gomock.Any(),
).Return(&proto.PlanAction_Response{
Deferred: &proto.Deferred{
Reason: proto.Deferred_RESOURCE_CONFIG_UNKNOWN,
},
}, nil)
p := &GRPCProvider{
Addr: addrs.Provider{
Type: "test",
Namespace: "hashicorp",
Hostname: "terraform.io",
},
client: client,
}
resp := p.PlanAction(providers.PlanActionRequest{
ActionType: "action",
ProposedActionData: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
})
checkDiagsHasError(t, resp.Diagnostics)
}
// Mock implementation of the ListResource stream client
type mockListResourceStreamClient struct {
events []*proto.ListResource_Event

@ -25,6 +25,7 @@ import (
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/plugin6/convert"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/tfdiags"
proto6 "github.com/hashicorp/terraform/internal/tfplugin6"
)
@ -1931,11 +1932,17 @@ func (p *GRPCProvider) PlanAction(r providers.PlanActionRequest) (resp providers
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
if resp.Diagnostics.HasErrors() {
return resp
// We only allow deferral from the provider as a whole. The provider must be
// able to accept unknown configuration.
if protoResp.Deferred != nil && protoResp.Deferred.Reason != proto6.Deferred_PROVIDER_CONFIG_UNKNOWN {
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
tfdiags.Error,
"Invalid deferred reason",
fmt.Sprintf("An action can only be deferred due to an unknown provider configuration. Provider %s returned %s.", p.Addr.ForDisplay(), protoResp.Deferred.Reason),
))
}
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
return resp
}

@ -2138,7 +2138,36 @@ func TestGRPCProvider_invokeAction_invalid(t *testing.T) {
checkDiagsHasError(t, resp.Diagnostics)
}
func TestGRPCProvider_planAction_invalid_defer(t *testing.T) {
client := mockProviderClient(t)
client.EXPECT().PlanAction(
gomock.Any(),
gomock.Any(),
).Return(&proto.PlanAction_Response{
Deferred: &proto.Deferred{
Reason: proto.Deferred_RESOURCE_CONFIG_UNKNOWN,
},
}, nil)
p := &GRPCProvider{
Addr: addrs.Provider{
Type: "test",
Namespace: "hashicorp",
Hostname: "terraform.io",
},
client: client,
}
resp := p.PlanAction(providers.PlanActionRequest{
ActionType: "action",
ProposedActionData: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
})
checkDiagsHasError(t, resp.Diagnostics)
}
func TestGRPCProvider_ValidateStateStoreConfig_returns_validation_errors(t *testing.T) {
storeName := "mock_store" // mockProviderClient returns a mock that has this state store in its schemas

Loading…
Cancel
Save