Skip to content

Commit 6dce8c1

Browse files
add action plan / invoke / cancel provider functions
1 parent 745b92b commit 6dce8c1

File tree

22 files changed

+1476
-0
lines changed

22 files changed

+1476
-0
lines changed

internal/builtin/providers/terraform/provider.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package terraform
55

66
import (
7+
"context"
78
"fmt"
89
"log"
910

@@ -72,6 +73,7 @@ func (p *Provider) GetProviderSchema() providers.GetProviderSchemaResponse {
7273
ReturnType: cty.String,
7374
},
7475
},
76+
Actions: map[string]providers.ActionSchema{},
7577
}
7678
providers.SchemaCache.Set(tfaddr.NewProvider(tfaddr.BuiltInProviderHost, tfaddr.BuiltInProviderNamespace, "terraform"), resp)
7779
return resp
@@ -264,6 +266,36 @@ func (p *Provider) CallFunction(req providers.CallFunctionRequest) providers.Cal
264266
}
265267
}
266268

269+
func (p *Provider) PlanAction(req providers.PlanActionRequest) providers.PlanActionResponse {
270+
var resp providers.PlanActionResponse
271+
272+
switch req.TypeName {
273+
default:
274+
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unsupported action %q", req.TypeName))
275+
}
276+
277+
return resp
278+
}
279+
280+
func (p *Provider) InvokeAction(_ context.Context, req providers.InvokeActionRequest) providers.InvokeActionResponse {
281+
var resp providers.InvokeActionResponse
282+
283+
switch req.TypeName {
284+
default:
285+
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unsupported action %q", req.TypeName))
286+
}
287+
288+
return resp
289+
}
290+
291+
func (p *Provider) CancelAction(req providers.CancelActionRequest) providers.CancelActionResponse {
292+
var resp providers.CancelActionResponse
293+
294+
// Handle cancellation logic if applicable
295+
resp.Diagnostics = resp.Diagnostics.Append("cancel action is not supported")
296+
return resp
297+
}
298+
267299
// Close is a noop for this provider, since it's run in-process.
268300
func (p *Provider) Close() error {
269301
return nil

internal/grpcwrap/provider.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func (p *provider) GetSchema(_ context.Context, req *tfplugin5.GetProviderSchema
4545
ResourceSchemas: make(map[string]*tfplugin5.Schema),
4646
DataSourceSchemas: make(map[string]*tfplugin5.Schema),
4747
EphemeralResourceSchemas: make(map[string]*tfplugin5.Schema),
48+
ActionSchemas: make(map[string]*tfplugin5.ActionSchema),
4849
}
4950

5051
resp.Provider = &tfplugin5.Schema{
@@ -86,6 +87,21 @@ func (p *provider) GetSchema(_ context.Context, req *tfplugin5.GetProviderSchema
8687
return resp, nil
8788
}
8889

90+
for typ, act := range p.schema.Actions {
91+
linkedResources := make([]*tfplugin5.ActionSchema_LinkedResource, len(act.LinkedResources))
92+
for i, linked := range act.LinkedResources {
93+
linkedResources[i] = &tfplugin5.ActionSchema_LinkedResource{
94+
Attribute: convert.PathToAttributePath(linked.AttributePath),
95+
Type: linked.TypeName,
96+
}
97+
}
98+
resp.ActionSchemas[typ] = &tfplugin5.ActionSchema{
99+
Version: act.Version,
100+
Block: convert.ConfigSchemaToProto(act.Block),
101+
LinkedResources: linkedResources,
102+
}
103+
}
104+
89105
resp.ServerCapabilities = &tfplugin5.ServerCapabilities{
90106
GetProviderSchemaOptional: p.schema.ServerCapabilities.GetProviderSchemaOptional,
91107
PlanDestroy: p.schema.ServerCapabilities.PlanDestroy,
@@ -736,6 +752,117 @@ func (p *provider) UpgradeResourceIdentity(_ context.Context, req *tfplugin5.Upg
736752
return resp, nil
737753
}
738754

755+
func (p *provider) PlanAction(_ context.Context, req *tfplugin5.PlanAction_Request) (*tfplugin5.PlanAction_Response, error) {
756+
resp := &tfplugin5.PlanAction_Response{}
757+
758+
actionSchema, ok := p.schema.Actions[req.TypeName]
759+
if !ok {
760+
return nil, fmt.Errorf("action schema not found for action %q", req.TypeName)
761+
}
762+
763+
ty := actionSchema.Block.ImpliedType()
764+
configVal, err := decodeDynamicValue(req.Config, ty)
765+
if err != nil {
766+
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
767+
return resp, nil
768+
}
769+
770+
planResp := p.provider.PlanAction(providers.PlanActionRequest{
771+
TypeName: req.TypeName,
772+
PlannedConfig: configVal,
773+
})
774+
775+
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, planResp.Diagnostics)
776+
if planResp.Diagnostics.HasErrors() {
777+
return resp, nil
778+
}
779+
780+
resp.NewConfig, err = encodeDynamicValue(planResp.NewConfig, ty)
781+
if err != nil {
782+
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
783+
}
784+
785+
return resp, nil
786+
}
787+
788+
func (p *provider) InvokeAction(req *tfplugin5.InvokeAction_Request, server tfplugin5.Provider_InvokeActionServer) error {
789+
790+
actionSchema, ok := p.schema.Actions[req.TypeName]
791+
if !ok {
792+
return fmt.Errorf("action schema not found for action %q", req.TypeName)
793+
}
794+
795+
ty := actionSchema.Block.ImpliedType()
796+
configVal, err := decodeDynamicValue(req.PlannedConfig, ty)
797+
if err != nil {
798+
return err
799+
}
800+
801+
// At this point we consider the action to be "started"
802+
// so we send an event to indicate that.
803+
server.Send(&tfplugin5.InvokeAction_Event{
804+
Event: &tfplugin5.InvokeAction_Event_Started_{
805+
Started: &tfplugin5.InvokeAction_Event_Started{
806+
CancellationToken: "", // TODO: Implement cancellation
807+
Diagnostics: []*tfplugin5.Diagnostic{},
808+
},
809+
},
810+
})
811+
812+
invokeClient := p.provider.InvokeAction(context.Background(), providers.InvokeActionRequest{
813+
TypeName: req.TypeName,
814+
PlannedConfig: configVal,
815+
})
816+
817+
go func() {
818+
for event := range invokeClient.Events {
819+
switch evt := event.(type) {
820+
case *providers.InvokeActionEvent_Progress:
821+
server.Send(&tfplugin5.InvokeAction_Event{
822+
Event: &tfplugin5.InvokeAction_Event_Progress_{
823+
Progress: &tfplugin5.InvokeAction_Event_Progress{
824+
Diagnostics: convert.AppendProtoDiag([]*tfplugin5.Diagnostic{}, evt.Diagnostics),
825+
Stdout: evt.Stdout,
826+
Stderr: evt.Stderr,
827+
},
828+
},
829+
})
830+
case *providers.InvokeActionEvent_Finished:
831+
diags := []*tfplugin5.Diagnostic{}
832+
833+
newConfig, err := encodeDynamicValue(evt.NewConfig, ty)
834+
if err != nil {
835+
diags = convert.AppendProtoDiag(diags, err)
836+
}
837+
838+
server.Send(&tfplugin5.InvokeAction_Event{
839+
Event: &tfplugin5.InvokeAction_Event_Finished_{
840+
Finished: &tfplugin5.InvokeAction_Event_Finished{
841+
Diagnostics: convert.AppendProtoDiag(diags, evt.Diagnostics),
842+
Cancelled: evt.Cancelled,
843+
NewConfig: newConfig,
844+
},
845+
},
846+
})
847+
return
848+
}
849+
}
850+
}()
851+
852+
return nil
853+
}
854+
855+
func (p *provider) CancelAction(_ context.Context, req *tfplugin5.CancelAction_Request) (*tfplugin5.CancelAction_Response, error) {
856+
resp := &tfplugin5.CancelAction_Response{}
857+
858+
cancelResp := p.provider.CancelAction(providers.CancelActionRequest{
859+
CancellationToken: req.CancellationToken,
860+
})
861+
862+
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, cancelResp.Diagnostics)
863+
return resp, nil
864+
}
865+
739866
func (p *provider) Stop(context.Context, *tfplugin5.Stop_Request) (*tfplugin5.Stop_Response, error) {
740867
resp := &tfplugin5.Stop_Response{}
741868
err := p.provider.Stop()

internal/grpcwrap/provider6.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,120 @@ func (p *provider6) UpgradeResourceIdentity(_ context.Context, req *tfplugin6.Up
784784
return resp, nil
785785
}
786786

787+
func (p *provider6) PlanAction(_ context.Context, req *tfplugin6.PlanAction_Request) (*tfplugin6.PlanAction_Response, error) {
788+
resp := &tfplugin6.PlanAction_Response{}
789+
actionSchema := p.schema.Actions[req.TypeName]
790+
ty := actionSchema.Block.ImpliedType()
791+
792+
configVal, err := decodeDynamicValue6(req.Config, ty)
793+
if err != nil {
794+
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
795+
return resp, nil
796+
}
797+
798+
planResp := p.provider.PlanAction(providers.PlanActionRequest{
799+
TypeName: req.TypeName,
800+
PlannedConfig: configVal,
801+
})
802+
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, planResp.Diagnostics)
803+
if planResp.Diagnostics.HasErrors() {
804+
return resp, nil
805+
}
806+
807+
resp.NewConfig, err = encodeDynamicValue6(planResp.NewConfig, ty)
808+
if err != nil {
809+
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
810+
}
811+
812+
return resp, nil
813+
}
814+
815+
func (p *provider6) InvokeAction(req *tfplugin6.InvokeAction_Request, server tfplugin6.Provider_InvokeActionServer) error {
816+
817+
actionSchema, ok := p.schema.Actions[req.TypeName]
818+
if !ok {
819+
return fmt.Errorf("action schema not found for type %q", req.TypeName)
820+
}
821+
822+
ty := actionSchema.Block.ImpliedType()
823+
configVal, err := decodeDynamicValue6(req.PlannedConfig, ty)
824+
if err != nil {
825+
return err
826+
}
827+
828+
// At this point we consider the action started and send the
829+
// started message.
830+
server.Send(&tfplugin6.InvokeAction_Event{
831+
Event: &tfplugin6.InvokeAction_Event_Started_{
832+
Started: &tfplugin6.InvokeAction_Event_Started{
833+
CancellationToken: "", // TODO: Implement cancellation
834+
Diagnostics: []*tfplugin6.Diagnostic{},
835+
},
836+
},
837+
})
838+
839+
invokeClient := p.provider.InvokeAction(context.Background(), providers.InvokeActionRequest{
840+
TypeName: req.TypeName,
841+
PlannedConfig: configVal,
842+
})
843+
844+
go func() {
845+
for event := range invokeClient.Events {
846+
if !ok {
847+
return
848+
}
849+
850+
switch evt := event.(type) {
851+
case *providers.InvokeActionEvent_Progress:
852+
server.Send(&tfplugin6.InvokeAction_Event{
853+
Event: &tfplugin6.InvokeAction_Event_Progress_{
854+
Progress: &tfplugin6.InvokeAction_Event_Progress{
855+
Diagnostics: convert.AppendProtoDiag([]*tfplugin6.Diagnostic{}, evt.Diagnostics),
856+
Stdout: evt.Stdout,
857+
Stderr: evt.Stderr,
858+
},
859+
},
860+
})
861+
case *providers.InvokeActionEvent_Finished:
862+
diags := []*tfplugin6.Diagnostic{}
863+
864+
newConfig, err := encodeDynamicValue6(evt.NewConfig, ty)
865+
if err != nil {
866+
diags = convert.AppendProtoDiag(diags, err)
867+
}
868+
869+
server.Send(&tfplugin6.InvokeAction_Event{
870+
Event: &tfplugin6.InvokeAction_Event_Finished_{
871+
Finished: &tfplugin6.InvokeAction_Event_Finished{
872+
Diagnostics: convert.AppendProtoDiag(diags, evt.Diagnostics),
873+
Cancelled: evt.Cancelled,
874+
NewConfig: newConfig,
875+
},
876+
},
877+
})
878+
return
879+
}
880+
}
881+
882+
}()
883+
884+
return nil
885+
}
886+
887+
func (p *provider6) CancelAction(_ context.Context, req *tfplugin6.CancelAction_Request) (*tfplugin6.CancelAction_Response, error) {
888+
resp := &tfplugin6.CancelAction_Response{}
889+
890+
cancelResp := p.provider.CancelAction(providers.CancelActionRequest{
891+
CancellationToken: req.CancellationToken,
892+
})
893+
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, cancelResp.Diagnostics)
894+
if cancelResp.Diagnostics.HasErrors() {
895+
return resp, nil
896+
}
897+
898+
return resp, nil
899+
}
900+
787901
func (p *provider6) StopProvider(context.Context, *tfplugin6.StopProvider_Request) (*tfplugin6.StopProvider_Response, error) {
788902
resp := &tfplugin6.StopProvider_Response{}
789903
err := p.provider.Stop()

internal/plugin/convert/schema.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,28 @@ func ProtoToProviderSchema(s *proto.Schema, id *proto.ResourceIdentitySchema) pr
105105
return schema
106106
}
107107

108+
func ProtoToActionSchema(s *proto.ActionSchema) providers.ActionSchema {
109+
schema := providers.ActionSchema{
110+
Version: s.Version,
111+
LinkedResources: []providers.LinkedResource{},
112+
}
113+
114+
if s.Block != nil {
115+
schema.Block = ProtoToConfigSchema(s.Block)
116+
}
117+
118+
if len(s.LinkedResources) > 0 {
119+
for _, lr := range s.LinkedResources {
120+
schema.LinkedResources = append(schema.LinkedResources, providers.LinkedResource{
121+
TypeName: lr.Type,
122+
AttributePath: AttributePathToPath(lr.Attribute),
123+
})
124+
}
125+
}
126+
127+
return schema
128+
}
129+
108130
// ProtoToConfigSchema takes the GetSchcema_Block from a grpc response and converts it
109131
// to a terraform *configschema.Block.
110132
func ProtoToConfigSchema(b *proto.Schema_Block) *configschema.Block {

0 commit comments

Comments
 (0)