github.com/opentofu/opentofu@v1.7.1/internal/plugin/grpc_provisioner.go (about)

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