github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/plugin/grpc_provisioner.go (about)

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