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 }