202 lines
5.4 KiB
Go
202 lines
5.4 KiB
Go
|
package plugin
|
||
|
|
||
|
import (
|
||
|
"log"
|
||
|
"strings"
|
||
|
"unicode/utf8"
|
||
|
|
||
|
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
|
||
|
"github.com/hashicorp/terraform/internal/legacy/terraform"
|
||
|
proto "github.com/hashicorp/terraform/internal/tfplugin5"
|
||
|
"github.com/hashicorp/terraform/plugin/convert"
|
||
|
"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: toValidUTF8(s, string(utf8.RuneError)),
|
||
|
})
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// FIXME: backported from go1.13 strings package, remove once terraform is
|
||
|
// using go >= 1.13
|
||
|
// ToValidUTF8 returns a copy of the string s with each run of invalid UTF-8 byte sequences
|
||
|
// replaced by the replacement string, which may be empty.
|
||
|
func toValidUTF8(s, replacement string) string {
|
||
|
var b strings.Builder
|
||
|
|
||
|
for i, c := range s {
|
||
|
if c != utf8.RuneError {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
_, wid := utf8.DecodeRuneInString(s[i:])
|
||
|
if wid == 1 {
|
||
|
b.Grow(len(s) + len(replacement))
|
||
|
b.WriteString(s[:i])
|
||
|
s = s[i:]
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Fast path for unchanged input
|
||
|
if b.Cap() == 0 { // didn't call b.Grow above
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
invalid := false // previous byte was from an invalid UTF-8 sequence
|
||
|
for i := 0; i < len(s); {
|
||
|
c := s[i]
|
||
|
if c < utf8.RuneSelf {
|
||
|
i++
|
||
|
invalid = false
|
||
|
b.WriteByte(c)
|
||
|
continue
|
||
|
}
|
||
|
_, wid := utf8.DecodeRuneInString(s[i:])
|
||
|
if wid == 1 {
|
||
|
i++
|
||
|
if !invalid {
|
||
|
invalid = true
|
||
|
b.WriteString(replacement)
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
invalid = false
|
||
|
b.WriteString(s[i : i+wid])
|
||
|
i += wid
|
||
|
}
|
||
|
|
||
|
return b.String()
|
||
|
}
|