github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/plugin/grpc_provisioner.go (about)

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