Skip to content

Commit

Permalink
stacks: add deferred actions to plugin protocol
Browse files Browse the repository at this point in the history
Co-authored-by: Matej Risek <[email protected]>
  • Loading branch information
DanielMSchmidt and matejrisek committed Mar 26, 2024
1 parent e650c03 commit 54e5a6e
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 6 deletions.
34 changes: 34 additions & 0 deletions internal/plugin/convert/deferred.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package convert

import (
"github.com/hashicorp/terraform/internal/providers"
proto "github.com/hashicorp/terraform/internal/tfplugin5"
)

// ProtoToDeferred translates a proto.Deferred to a providers.Deferred.
func ProtoToDeferred(d *proto.Deferred) *providers.Deferred {
if d == nil {
return nil
}

var reason int32
switch d.Reason {
case proto.Deferred_UNKNOWN:
reason = providers.DEFERRED_REASON_UNKNOWN
case proto.Deferred_RESOURCE_CONFIG_UNKNOWN:
reason = providers.DEFERRED_REASON_RESOURCE_CONFIG_UNKNOWN
case proto.Deferred_PROVIDER_CONFIG_UNKNOWN:
reason = providers.DEFERRED_REASON_PROVIDER_CONFIG_UNKNOWN
case proto.Deferred_ABSENT_PREREQ:
reason = providers.DEFERRED_REASON_ABSENT_PREREQ
default:
reason = providers.DEFERRED_REASON_UNKNOWN
}

return &providers.Deferred{
Reason: reason,
}
}
56 changes: 56 additions & 0 deletions internal/plugin/convert/deferred_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package convert

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/internal/providers"
proto "github.com/hashicorp/terraform/internal/tfplugin5"
)

func TestProtoDeferred(t *testing.T) {
testCases := []struct {
reason proto.Deferred_Reason
expected int32
}{
{
reason: proto.Deferred_UNKNOWN,
expected: providers.DEFERRED_REASON_UNKNOWN,
},
{
reason: proto.Deferred_RESOURCE_CONFIG_UNKNOWN,
expected: providers.DEFERRED_REASON_RESOURCE_CONFIG_UNKNOWN,
},
{
reason: proto.Deferred_PROVIDER_CONFIG_UNKNOWN,
expected: providers.DEFERRED_REASON_PROVIDER_CONFIG_UNKNOWN,
},
{
reason: proto.Deferred_ABSENT_PREREQ,
expected: providers.DEFERRED_REASON_ABSENT_PREREQ,
},
}

for _, tc := range testCases {
t.Run(fmt.Sprintf("deferred reason %q", tc.reason.String()), func(t *testing.T) {
d := &proto.Deferred{
Reason: tc.reason,
}

deferred := ProtoToDeferred(d)
if deferred.Reason != tc.expected {
t.Fatalf("expected %d, got %d", tc.expected, deferred.Reason)
}
})
}
}

func TestProtoDeferred_Nil(t *testing.T) {
deferred := ProtoToDeferred(nil)
if deferred != nil {
t.Fatalf("expected nil, got %v", deferred)
}
}
8 changes: 5 additions & 3 deletions internal/plugin/grpc_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,9 +399,10 @@ func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp provi
}

protoReq := &proto.ReadResource_Request{
TypeName: r.TypeName,
CurrentState: &proto.DynamicValue{Msgpack: mp},
Private: r.Private,
TypeName: r.TypeName,
CurrentState: &proto.DynamicValue{Msgpack: mp},
Private: r.Private,
DeferralAllowed: r.DeferralAllowed,
}

if metaSchema.Block != nil {
Expand All @@ -418,6 +419,7 @@ func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp provi
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
resp.Deferred = convert.ProtoToDeferred(protoResp.Deferred)
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))

state, err := decodeDynamicValue(protoResp.NewState, resSchema.Block.ImpliedType())
Expand Down
35 changes: 35 additions & 0 deletions internal/plugin/grpc_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,41 @@ func TestGRPCProvider_ReadResource(t *testing.T) {
}
}

func TestGRPCProvider_ReadResource_deferred(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}

client.EXPECT().ReadResource(
gomock.Any(),
gomock.Any(),
).Return(&proto.ReadResource_Response{
NewState: &proto.DynamicValue{
Msgpack: []byte("\x81\xa4attr\xa3bar"),
},
Deferred: &proto.Deferred{
Reason: proto.Deferred_ABSENT_PREREQ,
},
}, nil)

resp := p.ReadResource(providers.ReadResourceRequest{
TypeName: "resource",
PriorState: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
})

checkDiags(t, resp.Diagnostics)

expectedDeferred := &providers.Deferred{
Reason: providers.DEFERRED_REASON_ABSENT_PREREQ,
}
if !cmp.Equal(expectedDeferred, resp.Deferred, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expectedDeferred, resp.Deferred, typeComparer, valueComparer, equateEmpty))
}
}

func TestGRPCProvider_ReadResourceJSON(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
Expand Down
34 changes: 34 additions & 0 deletions internal/plugin6/convert/deferred.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package convert

import (
"github.com/hashicorp/terraform/internal/providers"
proto "github.com/hashicorp/terraform/internal/tfplugin6"
)

// ProtoToDeferred translates a proto.Deferred to a providers.Deferred.
func ProtoToDeferred(d *proto.Deferred) *providers.Deferred {
if d == nil {
return nil
}

var reason int32
switch d.Reason {
case proto.Deferred_UNKNOWN:
reason = providers.DEFERRED_REASON_UNKNOWN
case proto.Deferred_RESOURCE_CONFIG_UNKNOWN:
reason = providers.DEFERRED_REASON_RESOURCE_CONFIG_UNKNOWN
case proto.Deferred_PROVIDER_CONFIG_UNKNOWN:
reason = providers.DEFERRED_REASON_PROVIDER_CONFIG_UNKNOWN
case proto.Deferred_ABSENT_PREREQ:
reason = providers.DEFERRED_REASON_ABSENT_PREREQ
default:
reason = providers.DEFERRED_REASON_UNKNOWN
}

return &providers.Deferred{
Reason: reason,
}
}
56 changes: 56 additions & 0 deletions internal/plugin6/convert/deferred_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package convert

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/internal/providers"
proto "github.com/hashicorp/terraform/internal/tfplugin6"
)

func TestProtoDeferred(t *testing.T) {
testCases := []struct {
reason proto.Deferred_Reason
expected int32
}{
{
reason: proto.Deferred_UNKNOWN,
expected: providers.DEFERRED_REASON_UNKNOWN,
},
{
reason: proto.Deferred_RESOURCE_CONFIG_UNKNOWN,
expected: providers.DEFERRED_REASON_RESOURCE_CONFIG_UNKNOWN,
},
{
reason: proto.Deferred_PROVIDER_CONFIG_UNKNOWN,
expected: providers.DEFERRED_REASON_PROVIDER_CONFIG_UNKNOWN,
},
{
reason: proto.Deferred_ABSENT_PREREQ,
expected: providers.DEFERRED_REASON_ABSENT_PREREQ,
},
}

for _, tc := range testCases {
t.Run(fmt.Sprintf("deferred reason %q", tc.reason.String()), func(t *testing.T) {
d := &proto.Deferred{
Reason: tc.reason,
}

deferred := ProtoToDeferred(d)
if deferred.Reason != tc.expected {
t.Fatalf("expected %d, got %d", tc.expected, deferred.Reason)
}
})
}
}

func TestProtoDeferred_Nil(t *testing.T) {
deferred := ProtoToDeferred(nil)
if deferred != nil {
t.Fatalf("expected nil, got %v", deferred)
}
}
8 changes: 5 additions & 3 deletions internal/plugin6/grpc_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,10 @@ func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp provi
}

protoReq := &proto6.ReadResource_Request{
TypeName: r.TypeName,
CurrentState: &proto6.DynamicValue{Msgpack: mp},
Private: r.Private,
TypeName: r.TypeName,
CurrentState: &proto6.DynamicValue{Msgpack: mp},
Private: r.Private,
DeferralAllowed: r.DeferralAllowed,
}

if metaSchema.Block != nil {
Expand All @@ -416,6 +417,7 @@ func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp provi
}
resp.NewState = state
resp.Private = protoResp.Private
resp.Deferred = convert.ProtoToDeferred(protoResp.Deferred)

return resp
}
Expand Down
35 changes: 35 additions & 0 deletions internal/plugin6/grpc_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,41 @@ func TestGRPCProvider_ReadResource(t *testing.T) {
}
}

func TestGRPCProvider_ReadResource_deferred(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}

client.EXPECT().ReadResource(
gomock.Any(),
gomock.Any(),
).Return(&proto.ReadResource_Response{
NewState: &proto.DynamicValue{
Msgpack: []byte("\x81\xa4attr\xa3bar"),
},
Deferred: &proto.Deferred{
Reason: proto.Deferred_ABSENT_PREREQ,
},
}, nil)

resp := p.ReadResource(providers.ReadResourceRequest{
TypeName: "resource",
PriorState: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
})

checkDiags(t, resp.Diagnostics)

expectedDeferred := &providers.Deferred{
Reason: providers.DEFERRED_REASON_ABSENT_PREREQ,
}
if !cmp.Equal(expectedDeferred, resp.Deferred, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expectedDeferred, resp.Deferred, typeComparer, valueComparer, equateEmpty))
}
}

func TestGRPCProvider_ReadResourceJSON(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
Expand Down
18 changes: 18 additions & 0 deletions internal/providers/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,21 @@ type ReadResourceRequest struct {
// each provider, and it should not be used without coordination with
// HashiCorp. It is considered experimental and subject to change.
ProviderMeta cty.Value

// DeferralAllowed signals that the provider is allowed to defer the
// changes. If set the caller needs to handle the deferred response.
DeferralAllowed bool
}

const (
DEFERRED_REASON_UNKNOWN = 0
DEFERRED_REASON_RESOURCE_CONFIG_UNKNOWN = 1
DEFERRED_REASON_PROVIDER_CONFIG_UNKNOWN = 2
DEFERRED_REASON_ABSENT_PREREQ = 3
)

type Deferred struct {
Reason int32
}

type ReadResourceResponse struct {
Expand All @@ -249,6 +264,9 @@ type ReadResourceResponse struct {
// Private is an opaque blob that will be stored in state along with the
// resource. It is intended only for interpretation by the provider itself.
Private []byte

// Deferred TODO: docuemnt this
Deferred *Deferred
}

type PlanResourceChangeRequest struct {
Expand Down

0 comments on commit 54e5a6e

Please sign in to comment.