github.com/hashicorp/terraform-plugin-sdk@v1.17.2/plugin/grpc_provisioner.go (about)

     1  package plugin
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"io"
     7  	"log"
     8  	"sync"
     9  
    10  	plugin "github.com/hashicorp/go-plugin"
    11  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
    12  	"github.com/hashicorp/terraform-plugin-sdk/internal/plugin/convert"
    13  	"github.com/hashicorp/terraform-plugin-sdk/internal/provisioners"
    14  	proto "github.com/hashicorp/terraform-plugin-sdk/internal/tfplugin5"
    15  	"github.com/zclconf/go-cty/cty"
    16  	"github.com/zclconf/go-cty/cty/msgpack"
    17  	"google.golang.org/grpc"
    18  )
    19  
    20  // GRPCProvisionerPlugin is the plugin.GRPCPlugin implementation.
    21  type GRPCProvisionerPlugin struct {
    22  	plugin.Plugin
    23  	GRPCProvisioner func() proto.ProvisionerServer
    24  }
    25  
    26  func (p *GRPCProvisionerPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
    27  	return &GRPCProvisioner{
    28  		client: proto.NewProvisionerClient(c),
    29  		ctx:    ctx,
    30  	}, nil
    31  }
    32  
    33  func (p *GRPCProvisionerPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
    34  	proto.RegisterProvisionerServer(s, p.GRPCProvisioner())
    35  	return nil
    36  }
    37  
    38  // provisioners.Interface grpc implementation
    39  type GRPCProvisioner struct {
    40  	// PluginClient provides a reference to the plugin.Client which controls the plugin process.
    41  	// This allows the GRPCProvider a way to shutdown the plugin process.
    42  	PluginClient *plugin.Client
    43  
    44  	client proto.ProvisionerClient
    45  	ctx    context.Context
    46  
    47  	// Cache the schema since we need it for serialization in each method call.
    48  	mu     sync.Mutex
    49  	schema *configschema.Block
    50  }
    51  
    52  func (p *GRPCProvisioner) GetSchema() (resp provisioners.GetSchemaResponse) {
    53  	p.mu.Lock()
    54  	defer p.mu.Unlock()
    55  
    56  	if p.schema != nil {
    57  		return provisioners.GetSchemaResponse{
    58  			Provisioner: p.schema,
    59  		}
    60  	}
    61  
    62  	protoResp, err := p.client.GetSchema(p.ctx, new(proto.GetProvisionerSchema_Request))
    63  	if err != nil {
    64  		resp.Diagnostics = resp.Diagnostics.Append(err)
    65  		return resp
    66  	}
    67  	resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
    68  
    69  	if protoResp.Provisioner == nil {
    70  		resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing provisioner schema"))
    71  		return resp
    72  	}
    73  
    74  	resp.Provisioner = convert.ProtoToConfigSchema(protoResp.Provisioner.Block)
    75  
    76  	p.schema = resp.Provisioner
    77  
    78  	return resp
    79  }
    80  
    81  func (p *GRPCProvisioner) ValidateProvisionerConfig(r provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) {
    82  	schema := p.GetSchema()
    83  	if schema.Diagnostics.HasErrors() {
    84  		resp.Diagnostics = resp.Diagnostics.Append(schema.Diagnostics)
    85  		return resp
    86  	}
    87  
    88  	mp, err := msgpack.Marshal(r.Config, schema.Provisioner.ImpliedType())
    89  	if err != nil {
    90  		resp.Diagnostics = resp.Diagnostics.Append(err)
    91  		return resp
    92  	}
    93  
    94  	protoReq := &proto.ValidateProvisionerConfig_Request{
    95  		Config: &proto.DynamicValue{Msgpack: mp},
    96  	}
    97  	protoResp, err := p.client.ValidateProvisionerConfig(p.ctx, protoReq)
    98  	if err != nil {
    99  		resp.Diagnostics = resp.Diagnostics.Append(err)
   100  		return resp
   101  	}
   102  	resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
   103  	return resp
   104  }
   105  
   106  func (p *GRPCProvisioner) ProvisionResource(r provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
   107  	schema := p.GetSchema()
   108  	if schema.Diagnostics.HasErrors() {
   109  		resp.Diagnostics = resp.Diagnostics.Append(schema.Diagnostics)
   110  		return resp
   111  	}
   112  
   113  	mp, err := msgpack.Marshal(r.Config, schema.Provisioner.ImpliedType())
   114  	if err != nil {
   115  		resp.Diagnostics = resp.Diagnostics.Append(err)
   116  		return resp
   117  	}
   118  
   119  	// connection is always assumed to be a simple string map
   120  	connMP, err := msgpack.Marshal(r.Connection, cty.Map(cty.String))
   121  	if err != nil {
   122  		resp.Diagnostics = resp.Diagnostics.Append(err)
   123  		return resp
   124  	}
   125  
   126  	protoReq := &proto.ProvisionResource_Request{
   127  		Config:     &proto.DynamicValue{Msgpack: mp},
   128  		Connection: &proto.DynamicValue{Msgpack: connMP},
   129  	}
   130  
   131  	outputClient, err := p.client.ProvisionResource(p.ctx, protoReq)
   132  	if err != nil {
   133  		resp.Diagnostics = resp.Diagnostics.Append(err)
   134  		return resp
   135  	}
   136  
   137  	for {
   138  		rcv, err := outputClient.Recv()
   139  		if rcv != nil {
   140  			r.UIOutput.Output(rcv.Output)
   141  		}
   142  		if err != nil {
   143  			if err != io.EOF {
   144  				resp.Diagnostics = resp.Diagnostics.Append(err)
   145  			}
   146  			break
   147  		}
   148  
   149  		if len(rcv.Diagnostics) > 0 {
   150  			resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(rcv.Diagnostics))
   151  			break
   152  		}
   153  	}
   154  
   155  	return resp
   156  }
   157  
   158  func (p *GRPCProvisioner) Stop() error {
   159  	protoResp, err := p.client.Stop(p.ctx, &proto.Stop_Request{})
   160  	if err != nil {
   161  		return err
   162  	}
   163  	if protoResp.Error != "" {
   164  		return errors.New(protoResp.Error)
   165  	}
   166  	return nil
   167  }
   168  
   169  func (p *GRPCProvisioner) Close() error {
   170  	// check this since it's not automatically inserted during plugin creation
   171  	if p.PluginClient == nil {
   172  		log.Println("[DEBUG] provider has no plugin.Client")
   173  		return nil
   174  	}
   175  
   176  	p.PluginClient.Kill()
   177  	return nil
   178  }