opentofu/internal/communicator/winrm/communicator.go
Martin Atkins e81162c4e1 Move provisioners/ to internal/provisioners/
This is part of a general effort to move all of Terraform's non-library
package surface under internal in order to reinforce that these are for
internal use within Terraform only.

If you were previously importing packages under this prefix into an
external codebase, you could pin to an earlier release tag as an interim
solution until you've make a plan to achieve the same functionality some
other way.
2021-05-17 14:09:07 -07:00

203 lines
4.9 KiB
Go

package winrm
import (
"fmt"
"io"
"log"
"math/rand"
"strconv"
"strings"
"time"
"github.com/hashicorp/terraform/internal/communicator/remote"
"github.com/hashicorp/terraform/internal/provisioners"
"github.com/masterzen/winrm"
"github.com/packer-community/winrmcp/winrmcp"
"github.com/zclconf/go-cty/cty"
)
// Communicator represents the WinRM communicator
type Communicator struct {
connInfo *connectionInfo
client *winrm.Client
endpoint *winrm.Endpoint
rand *rand.Rand
}
// New creates a new communicator implementation over WinRM.
func New(v cty.Value) (*Communicator, error) {
connInfo, err := parseConnectionInfo(v)
if err != nil {
return nil, err
}
endpoint := &winrm.Endpoint{
Host: connInfo.Host,
Port: int(connInfo.Port),
HTTPS: connInfo.HTTPS,
Insecure: connInfo.Insecure,
Timeout: connInfo.TimeoutVal,
}
if len(connInfo.CACert) > 0 {
endpoint.CACert = []byte(connInfo.CACert)
}
comm := &Communicator{
connInfo: connInfo,
endpoint: endpoint,
// Seed our own rand source so that script paths are not deterministic
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
}
return comm, nil
}
// Connect implementation of communicator.Communicator interface
func (c *Communicator) Connect(o provisioners.UIOutput) error {
// Set the client to nil since we'll (re)create it
c.client = nil
params := winrm.DefaultParameters
params.Timeout = formatDuration(c.Timeout())
if c.connInfo.NTLM {
params.TransportDecorator = func() winrm.Transporter { return &winrm.ClientNTLM{} }
}
client, err := winrm.NewClientWithParameters(
c.endpoint, c.connInfo.User, c.connInfo.Password, params)
if err != nil {
return err
}
if o != nil {
o.Output(fmt.Sprintf(
"Connecting to remote host via WinRM...\n"+
" Host: %s\n"+
" Port: %d\n"+
" User: %s\n"+
" Password: %t\n"+
" HTTPS: %t\n"+
" Insecure: %t\n"+
" NTLM: %t\n"+
" CACert: %t",
c.connInfo.Host,
c.connInfo.Port,
c.connInfo.User,
c.connInfo.Password != "",
c.connInfo.HTTPS,
c.connInfo.Insecure,
c.connInfo.NTLM,
c.connInfo.CACert != "",
))
}
log.Printf("[DEBUG] connecting to remote shell using WinRM")
shell, err := client.CreateShell()
if err != nil {
log.Printf("[ERROR] error creating shell: %s", err)
return err
}
err = shell.Close()
if err != nil {
log.Printf("[ERROR] error closing shell: %s", err)
return err
}
if o != nil {
o.Output("Connected!")
}
c.client = client
return nil
}
// Disconnect implementation of communicator.Communicator interface
func (c *Communicator) Disconnect() error {
c.client = nil
return nil
}
// Timeout implementation of communicator.Communicator interface
func (c *Communicator) Timeout() time.Duration {
return c.connInfo.TimeoutVal
}
// ScriptPath implementation of communicator.Communicator interface
func (c *Communicator) ScriptPath() string {
return strings.Replace(
c.connInfo.ScriptPath, "%RAND%",
strconv.FormatInt(int64(c.rand.Int31()), 10), -1)
}
// Start implementation of communicator.Communicator interface
func (c *Communicator) Start(rc *remote.Cmd) error {
rc.Init()
log.Printf("[DEBUG] starting remote command: %s", rc.Command)
// TODO: make sure communicators always connect first, so we can get output
// from the connection.
if c.client == nil {
log.Println("[WARN] winrm client not connected, attempting to connect")
if err := c.Connect(nil); err != nil {
return err
}
}
status, err := c.client.Run(rc.Command, rc.Stdout, rc.Stderr)
rc.SetExitStatus(status, err)
return nil
}
// Upload implementation of communicator.Communicator interface
func (c *Communicator) Upload(path string, input io.Reader) error {
wcp, err := c.newCopyClient()
if err != nil {
return err
}
log.Printf("[DEBUG] Uploading file to '%s'", path)
return wcp.Write(path, input)
}
// UploadScript implementation of communicator.Communicator interface
func (c *Communicator) UploadScript(path string, input io.Reader) error {
return c.Upload(path, input)
}
// UploadDir implementation of communicator.Communicator interface
func (c *Communicator) UploadDir(dst string, src string) error {
log.Printf("[DEBUG] Uploading dir '%s' to '%s'", src, dst)
wcp, err := c.newCopyClient()
if err != nil {
return err
}
return wcp.Copy(src, dst)
}
func (c *Communicator) newCopyClient() (*winrmcp.Winrmcp, error) {
addr := fmt.Sprintf("%s:%d", c.endpoint.Host, c.endpoint.Port)
config := winrmcp.Config{
Auth: winrmcp.Auth{
User: c.connInfo.User,
Password: c.connInfo.Password,
},
Https: c.connInfo.HTTPS,
Insecure: c.connInfo.Insecure,
OperationTimeout: c.Timeout(),
MaxOperationsPerShell: 15, // lowest common denominator
}
if c.connInfo.NTLM {
config.TransportDecorator = func() winrm.Transporter { return &winrm.ClientNTLM{} }
}
if c.connInfo.CACert != "" {
config.CACertBytes = []byte(c.connInfo.CACert)
}
return winrmcp.New(addr, &config)
}