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

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package grpcwrap
     5  
     6  import (
     7  	"context"
     8  	"log"
     9  	"strings"
    10  	"unicode/utf8"
    11  
    12  	"github.com/terramate-io/tf/communicator/shared"
    13  	"github.com/terramate-io/tf/configs/configschema"
    14  	"github.com/terramate-io/tf/plugin/convert"
    15  	"github.com/terramate-io/tf/provisioners"
    16  	"github.com/terramate-io/tf/tfplugin5"
    17  )
    18  
    19  // New wraps a provisioners.Interface to implement a grpc ProviderServer.
    20  // This is useful for creating a test binary out of an internal provider
    21  // implementation.
    22  func Provisioner(p provisioners.Interface) tfplugin5.ProvisionerServer {
    23  	return &provisioner{
    24  		provisioner: p,
    25  		schema:      p.GetSchema().Provisioner,
    26  	}
    27  }
    28  
    29  type provisioner struct {
    30  	provisioner provisioners.Interface
    31  	schema      *configschema.Block
    32  }
    33  
    34  func (p *provisioner) GetSchema(_ context.Context, req *tfplugin5.GetProvisionerSchema_Request) (*tfplugin5.GetProvisionerSchema_Response, error) {
    35  	resp := &tfplugin5.GetProvisionerSchema_Response{}
    36  
    37  	resp.Provisioner = &tfplugin5.Schema{
    38  		Block: &tfplugin5.Schema_Block{},
    39  	}
    40  
    41  	if p.schema != nil {
    42  		resp.Provisioner.Block = convert.ConfigSchemaToProto(p.schema)
    43  	}
    44  
    45  	return resp, nil
    46  }
    47  
    48  func (p *provisioner) ValidateProvisionerConfig(_ context.Context, req *tfplugin5.ValidateProvisionerConfig_Request) (*tfplugin5.ValidateProvisionerConfig_Response, error) {
    49  	resp := &tfplugin5.ValidateProvisionerConfig_Response{}
    50  	ty := p.schema.ImpliedType()
    51  
    52  	configVal, err := decodeDynamicValue(req.Config, ty)
    53  	if err != nil {
    54  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
    55  		return resp, nil
    56  	}
    57  
    58  	validateResp := p.provisioner.ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{
    59  		Config: configVal,
    60  	})
    61  
    62  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, validateResp.Diagnostics)
    63  	return resp, nil
    64  }
    65  
    66  func (p *provisioner) ProvisionResource(req *tfplugin5.ProvisionResource_Request, srv tfplugin5.Provisioner_ProvisionResourceServer) error {
    67  	// We send back a diagnostics over the stream if there was a
    68  	// provisioner-side problem.
    69  	srvResp := &tfplugin5.ProvisionResource_Response{}
    70  
    71  	ty := p.schema.ImpliedType()
    72  	configVal, err := decodeDynamicValue(req.Config, ty)
    73  	if err != nil {
    74  		srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, err)
    75  		srv.Send(srvResp)
    76  		return nil
    77  	}
    78  
    79  	connVal, err := decodeDynamicValue(req.Connection, shared.ConnectionBlockSupersetSchema.ImpliedType())
    80  	if err != nil {
    81  		srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, err)
    82  		srv.Send(srvResp)
    83  		return nil
    84  	}
    85  
    86  	resp := p.provisioner.ProvisionResource(provisioners.ProvisionResourceRequest{
    87  		Config:     configVal,
    88  		Connection: connVal,
    89  		UIOutput:   uiOutput{srv},
    90  	})
    91  
    92  	srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, resp.Diagnostics)
    93  	srv.Send(srvResp)
    94  	return nil
    95  }
    96  
    97  func (p *provisioner) Stop(context.Context, *tfplugin5.Stop_Request) (*tfplugin5.Stop_Response, error) {
    98  	resp := &tfplugin5.Stop_Response{}
    99  	err := p.provisioner.Stop()
   100  	if err != nil {
   101  		resp.Error = err.Error()
   102  	}
   103  	return resp, nil
   104  }
   105  
   106  // uiOutput implements the terraform.UIOutput interface to adapt the grpc
   107  // stream to the legacy Provisioner.Apply method.
   108  type uiOutput struct {
   109  	srv tfplugin5.Provisioner_ProvisionResourceServer
   110  }
   111  
   112  func (o uiOutput) Output(s string) {
   113  	err := o.srv.Send(&tfplugin5.ProvisionResource_Response{
   114  		Output: strings.ToValidUTF8(s, string(utf8.RuneError)),
   115  	})
   116  	if err != nil {
   117  		log.Printf("[ERROR] %s", err)
   118  	}
   119  }