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