| package plugin |
| |
| import ( |
| "context" |
| "errors" |
| "io" |
| "sync" |
| |
| plugin "github.com/hashicorp/go-plugin" |
| "github.com/hashicorp/terraform/internal/configs/configschema" |
| "github.com/hashicorp/terraform/internal/plugin/convert" |
| "github.com/hashicorp/terraform/internal/provisioners" |
| proto "github.com/hashicorp/terraform/internal/tfplugin5" |
| "github.com/zclconf/go-cty/cty" |
| "github.com/zclconf/go-cty/cty/msgpack" |
| "google.golang.org/grpc" |
| ) |
| |
| // GRPCProvisionerPlugin is the plugin.GRPCPlugin implementation. |
| type GRPCProvisionerPlugin struct { |
| plugin.Plugin |
| GRPCProvisioner func() proto.ProvisionerServer |
| } |
| |
| func (p *GRPCProvisionerPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { |
| return &GRPCProvisioner{ |
| client: proto.NewProvisionerClient(c), |
| ctx: ctx, |
| }, nil |
| } |
| |
| func (p *GRPCProvisionerPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { |
| proto.RegisterProvisionerServer(s, p.GRPCProvisioner()) |
| return nil |
| } |
| |
| // provisioners.Interface grpc implementation |
| type GRPCProvisioner struct { |
| // PluginClient provides a reference to the plugin.Client which controls the plugin process. |
| // This allows the GRPCProvider a way to shutdown the plugin process. |
| PluginClient *plugin.Client |
| |
| client proto.ProvisionerClient |
| ctx context.Context |
| |
| // Cache the schema since we need it for serialization in each method call. |
| mu sync.Mutex |
| schema *configschema.Block |
| } |
| |
| func (p *GRPCProvisioner) GetSchema() (resp provisioners.GetSchemaResponse) { |
| p.mu.Lock() |
| defer p.mu.Unlock() |
| |
| if p.schema != nil { |
| return provisioners.GetSchemaResponse{ |
| Provisioner: p.schema, |
| } |
| } |
| |
| protoResp, err := p.client.GetSchema(p.ctx, new(proto.GetProvisionerSchema_Request)) |
| if err != nil { |
| resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) |
| return resp |
| } |
| resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) |
| |
| if protoResp.Provisioner == nil { |
| resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing provisioner schema")) |
| return resp |
| } |
| |
| resp.Provisioner = convert.ProtoToConfigSchema(protoResp.Provisioner.Block) |
| |
| p.schema = resp.Provisioner |
| |
| return resp |
| } |
| |
| func (p *GRPCProvisioner) ValidateProvisionerConfig(r provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) { |
| schema := p.GetSchema() |
| if schema.Diagnostics.HasErrors() { |
| resp.Diagnostics = resp.Diagnostics.Append(schema.Diagnostics) |
| return resp |
| } |
| |
| mp, err := msgpack.Marshal(r.Config, schema.Provisioner.ImpliedType()) |
| if err != nil { |
| resp.Diagnostics = resp.Diagnostics.Append(err) |
| return resp |
| } |
| |
| protoReq := &proto.ValidateProvisionerConfig_Request{ |
| Config: &proto.DynamicValue{Msgpack: mp}, |
| } |
| protoResp, err := p.client.ValidateProvisionerConfig(p.ctx, protoReq) |
| if err != nil { |
| resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) |
| return resp |
| } |
| resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) |
| return resp |
| } |
| |
| func (p *GRPCProvisioner) ProvisionResource(r provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { |
| schema := p.GetSchema() |
| if schema.Diagnostics.HasErrors() { |
| resp.Diagnostics = resp.Diagnostics.Append(schema.Diagnostics) |
| return resp |
| } |
| |
| mp, err := msgpack.Marshal(r.Config, schema.Provisioner.ImpliedType()) |
| if err != nil { |
| resp.Diagnostics = resp.Diagnostics.Append(err) |
| return resp |
| } |
| |
| // connection is always assumed to be a simple string map |
| connMP, err := msgpack.Marshal(r.Connection, cty.Map(cty.String)) |
| if err != nil { |
| resp.Diagnostics = resp.Diagnostics.Append(err) |
| return resp |
| } |
| |
| protoReq := &proto.ProvisionResource_Request{ |
| Config: &proto.DynamicValue{Msgpack: mp}, |
| Connection: &proto.DynamicValue{Msgpack: connMP}, |
| } |
| |
| outputClient, err := p.client.ProvisionResource(p.ctx, protoReq) |
| if err != nil { |
| resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) |
| return resp |
| } |
| |
| for { |
| rcv, err := outputClient.Recv() |
| if rcv != nil { |
| r.UIOutput.Output(rcv.Output) |
| } |
| if err != nil { |
| if err != io.EOF { |
| resp.Diagnostics = resp.Diagnostics.Append(err) |
| } |
| break |
| } |
| |
| if len(rcv.Diagnostics) > 0 { |
| resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(rcv.Diagnostics)) |
| break |
| } |
| } |
| |
| return resp |
| } |
| |
| func (p *GRPCProvisioner) Stop() error { |
| protoResp, err := p.client.Stop(p.ctx, &proto.Stop_Request{}) |
| if err != nil { |
| return err |
| } |
| if protoResp.Error != "" { |
| return errors.New(protoResp.Error) |
| } |
| return nil |
| } |
| |
| func (p *GRPCProvisioner) Close() error { |
| // check this since it's not automatically inserted during plugin creation |
| if p.PluginClient == nil { |
| logger.Debug("provisioner has no plugin.Client") |
| return nil |
| } |
| |
| p.PluginClient.Kill() |
| return nil |
| } |