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 }