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