| package plugin |
| |
| import ( |
| "log" |
| |
| "github.com/hashicorp/terraform/helper/schema" |
| proto "github.com/hashicorp/terraform/internal/tfplugin5" |
| "github.com/hashicorp/terraform/plugin/convert" |
| "github.com/hashicorp/terraform/terraform" |
| "github.com/zclconf/go-cty/cty" |
| ctyconvert "github.com/zclconf/go-cty/cty/convert" |
| "github.com/zclconf/go-cty/cty/msgpack" |
| context "golang.org/x/net/context" |
| ) |
| |
| // NewGRPCProvisionerServerShim wraps a terraform.ResourceProvisioner in a |
| // proto.ProvisionerServer implementation. If the provided provisioner is not a |
| // *schema.Provisioner, this will return nil, |
| func NewGRPCProvisionerServerShim(p terraform.ResourceProvisioner) *GRPCProvisionerServer { |
| sp, ok := p.(*schema.Provisioner) |
| if !ok { |
| return nil |
| } |
| return &GRPCProvisionerServer{ |
| provisioner: sp, |
| } |
| } |
| |
| type GRPCProvisionerServer struct { |
| provisioner *schema.Provisioner |
| } |
| |
| func (s *GRPCProvisionerServer) GetSchema(_ context.Context, req *proto.GetProvisionerSchema_Request) (*proto.GetProvisionerSchema_Response, error) { |
| resp := &proto.GetProvisionerSchema_Response{} |
| |
| resp.Provisioner = &proto.Schema{ |
| Block: convert.ConfigSchemaToProto(schema.InternalMap(s.provisioner.Schema).CoreConfigSchema()), |
| } |
| |
| return resp, nil |
| } |
| |
| func (s *GRPCProvisionerServer) ValidateProvisionerConfig(_ context.Context, req *proto.ValidateProvisionerConfig_Request) (*proto.ValidateProvisionerConfig_Response, error) { |
| resp := &proto.ValidateProvisionerConfig_Response{} |
| |
| cfgSchema := schema.InternalMap(s.provisioner.Schema).CoreConfigSchema() |
| |
| configVal, err := msgpack.Unmarshal(req.Config.Msgpack, cfgSchema.ImpliedType()) |
| if err != nil { |
| resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) |
| return resp, nil |
| } |
| |
| config := terraform.NewResourceConfigShimmed(configVal, cfgSchema) |
| |
| warns, errs := s.provisioner.Validate(config) |
| resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs)) |
| |
| return resp, nil |
| } |
| |
| // stringMapFromValue converts a cty.Value to a map[stirng]string. |
| // This will panic if the val is not a cty.Map(cty.String). |
| func stringMapFromValue(val cty.Value) map[string]string { |
| m := map[string]string{} |
| if val.IsNull() || !val.IsKnown() { |
| return m |
| } |
| |
| for it := val.ElementIterator(); it.Next(); { |
| ak, av := it.Element() |
| name := ak.AsString() |
| |
| if !av.IsKnown() || av.IsNull() { |
| continue |
| } |
| |
| av, _ = ctyconvert.Convert(av, cty.String) |
| m[name] = av.AsString() |
| } |
| |
| return m |
| } |
| |
| // uiOutput implements the terraform.UIOutput interface to adapt the grpc |
| // stream to the legacy Provisioner.Apply method. |
| type uiOutput struct { |
| srv proto.Provisioner_ProvisionResourceServer |
| } |
| |
| func (o uiOutput) Output(s string) { |
| err := o.srv.Send(&proto.ProvisionResource_Response{ |
| Output: s, |
| }) |
| if err != nil { |
| log.Printf("[ERROR] %s", err) |
| } |
| } |
| |
| func (s *GRPCProvisionerServer) ProvisionResource(req *proto.ProvisionResource_Request, srv proto.Provisioner_ProvisionResourceServer) error { |
| // We send back a diagnostics over the stream if there was a |
| // provisioner-side problem. |
| srvResp := &proto.ProvisionResource_Response{} |
| |
| cfgSchema := schema.InternalMap(s.provisioner.Schema).CoreConfigSchema() |
| cfgVal, err := msgpack.Unmarshal(req.Config.Msgpack, cfgSchema.ImpliedType()) |
| if err != nil { |
| srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, err) |
| srv.Send(srvResp) |
| return nil |
| } |
| resourceConfig := terraform.NewResourceConfigShimmed(cfgVal, cfgSchema) |
| |
| connVal, err := msgpack.Unmarshal(req.Connection.Msgpack, cty.Map(cty.String)) |
| if err != nil { |
| srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, err) |
| srv.Send(srvResp) |
| return nil |
| } |
| |
| conn := stringMapFromValue(connVal) |
| |
| instanceState := &terraform.InstanceState{ |
| Ephemeral: terraform.EphemeralState{ |
| ConnInfo: conn, |
| }, |
| Meta: make(map[string]interface{}), |
| } |
| |
| err = s.provisioner.Apply(uiOutput{srv}, instanceState, resourceConfig) |
| if err != nil { |
| srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, err) |
| srv.Send(srvResp) |
| } |
| return nil |
| } |
| |
| func (s *GRPCProvisionerServer) Stop(_ context.Context, req *proto.Stop_Request) (*proto.Stop_Response, error) { |
| resp := &proto.Stop_Response{} |
| |
| err := s.provisioner.Stop() |
| if err != nil { |
| resp.Error = err.Error() |
| } |
| |
| return resp, nil |
| } |