mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
4fe9632f09
The main significant change here is that the package name for the proto definition is "tfplugin5", which is important because this name is part of the wire protocol for references to types defined in our package. Along with that, we also move the generated package into "internal" to make it explicit that importing the generated Go package from elsewhere is not the right approach for externally-implemented SDKs, which should instead vendor the proto definition they are using and generate their own stubs to ensure that the wire protocol is the only hard dependency between Terraform Core and plugins. After this is merged, any provider binaries built against our helper/schema package will need to be rebuilt so that they use the new "tfplugin5" package name instead of "proto". In a future commit we will include more elaborate and organized documentation on how an external codebase might make use of our RPC interface definition to implement an SDK, but the primary concern here is to ensure we have the right wire package name before release.
179 lines
4.7 KiB
Go
179 lines
4.7 KiB
Go
package plugin
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"log"
|
|
"sync"
|
|
|
|
plugin "github.com/hashicorp/go-plugin"
|
|
"github.com/hashicorp/terraform/configs/configschema"
|
|
proto "github.com/hashicorp/terraform/internal/tfplugin5"
|
|
"github.com/hashicorp/terraform/plugin/convert"
|
|
"github.com/hashicorp/terraform/provisioners"
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/msgpack"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
// GRPCProvisionerPlugin is the plugin.GRPCPlugin implementation.
|
|
type GRPCProvisionerPlugin struct {
|
|
plugin.Plugin
|
|
GRPCProvisioner func() proto.ProvisionerServer
|
|
}
|
|
|
|
func (p *GRPCProvisionerPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
|
|
return &GRPCProvisioner{
|
|
client: proto.NewProvisionerClient(c),
|
|
ctx: ctx,
|
|
}, nil
|
|
}
|
|
|
|
func (p *GRPCProvisionerPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
|
|
proto.RegisterProvisionerServer(s, p.GRPCProvisioner())
|
|
return nil
|
|
}
|
|
|
|
// provisioners.Interface grpc implementation
|
|
type GRPCProvisioner struct {
|
|
// PluginClient provides a reference to the plugin.Client which controls the plugin process.
|
|
// This allows the GRPCProvider a way to shutdown the plugin process.
|
|
PluginClient *plugin.Client
|
|
|
|
client proto.ProvisionerClient
|
|
ctx context.Context
|
|
|
|
// Cache the schema since we need it for serialization in each method call.
|
|
mu sync.Mutex
|
|
schema *configschema.Block
|
|
}
|
|
|
|
func (p *GRPCProvisioner) GetSchema() (resp provisioners.GetSchemaResponse) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if p.schema != nil {
|
|
return provisioners.GetSchemaResponse{
|
|
Provisioner: p.schema,
|
|
}
|
|
}
|
|
|
|
protoResp, err := p.client.GetSchema(p.ctx, new(proto.GetProvisionerSchema_Request))
|
|
if err != nil {
|
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
|
return resp
|
|
}
|
|
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
|
|
|
|
if protoResp.Provisioner == nil {
|
|
resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing provisioner schema"))
|
|
return resp
|
|
}
|
|
|
|
resp.Provisioner = convert.ProtoToConfigSchema(protoResp.Provisioner.Block)
|
|
|
|
p.schema = resp.Provisioner
|
|
|
|
return resp
|
|
}
|
|
|
|
func (p *GRPCProvisioner) ValidateProvisionerConfig(r provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) {
|
|
schema := p.GetSchema()
|
|
if schema.Diagnostics.HasErrors() {
|
|
resp.Diagnostics = resp.Diagnostics.Append(schema.Diagnostics)
|
|
return resp
|
|
}
|
|
|
|
mp, err := msgpack.Marshal(r.Config, schema.Provisioner.ImpliedType())
|
|
if err != nil {
|
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
|
return resp
|
|
}
|
|
|
|
protoReq := &proto.ValidateProvisionerConfig_Request{
|
|
Config: &proto.DynamicValue{Msgpack: mp},
|
|
}
|
|
protoResp, err := p.client.ValidateProvisionerConfig(p.ctx, protoReq)
|
|
if err != nil {
|
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
|
return resp
|
|
}
|
|
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
|
|
return resp
|
|
}
|
|
|
|
func (p *GRPCProvisioner) ProvisionResource(r provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
schema := p.GetSchema()
|
|
if schema.Diagnostics.HasErrors() {
|
|
resp.Diagnostics = resp.Diagnostics.Append(schema.Diagnostics)
|
|
return resp
|
|
}
|
|
|
|
mp, err := msgpack.Marshal(r.Config, schema.Provisioner.ImpliedType())
|
|
if err != nil {
|
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
|
return resp
|
|
}
|
|
|
|
// connection is always assumed to be a simple string map
|
|
connMP, err := msgpack.Marshal(r.Connection, cty.Map(cty.String))
|
|
if err != nil {
|
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
|
return resp
|
|
}
|
|
|
|
protoReq := &proto.ProvisionResource_Request{
|
|
Config: &proto.DynamicValue{Msgpack: mp},
|
|
Connection: &proto.DynamicValue{Msgpack: connMP},
|
|
}
|
|
|
|
outputClient, err := p.client.ProvisionResource(p.ctx, protoReq)
|
|
if err != nil {
|
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
|
return resp
|
|
}
|
|
|
|
for {
|
|
rcv, err := outputClient.Recv()
|
|
if rcv != nil {
|
|
r.UIOutput.Output(rcv.Output)
|
|
}
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
|
}
|
|
break
|
|
}
|
|
|
|
if len(rcv.Diagnostics) > 0 {
|
|
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(rcv.Diagnostics))
|
|
break
|
|
}
|
|
}
|
|
|
|
return resp
|
|
}
|
|
|
|
func (p *GRPCProvisioner) Stop() error {
|
|
protoResp, err := p.client.Stop(p.ctx, &proto.Stop_Request{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if protoResp.Error != "" {
|
|
return errors.New(protoResp.Error)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *GRPCProvisioner) Close() error {
|
|
// check this since it's not automatically inserted during plugin creation
|
|
if p.PluginClient == nil {
|
|
log.Println("[DEBUG] provider has no plugin.Client")
|
|
return nil
|
|
}
|
|
|
|
p.PluginClient.Kill()
|
|
return nil
|
|
}
|