terraform/communicator/winrm/communicator.go

217 lines
4.8 KiB
Go
Raw Normal View History

2015-04-10 20:34:46 +02:00
package winrm
import (
"fmt"
"io"
"log"
"math/rand"
"strconv"
"strings"
communicator/winrm: fix data race in io copy As the command completes, the winrm.Command does not wait for its copy of the remote.Command streams to report itself completed. This adds an additional sync.WaitGroup to ensure that copy is finished up before moving on, solving the data race. Fixes the following data race: ``` ================== WARNING: DATA RACE Read by goroutine 6: github.com/hashicorp/terraform/communicator/winrm.TestStart() /Users/phinze/go/src/github.com/hashicorp/terraform/communicator/winrm/communicator_test.go:79 +0xa44 testing.tRunner() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/testing/testing.go:456 +0xdc Previous write by goroutine 14: bytes.(*Buffer).ReadFrom() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/bytes/buffer.go:174 +0x465 io.copyBuffer() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/io/io.go:375 +0x1a5 io.Copy() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/io/io.go:351 +0x78 Goroutine 6 (running) created at: testing.RunTests() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/testing/testing.go:561 +0xaa3 testing.(*M).Run() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/testing/testing.go:494 +0xe4 main.main() github.com/hashicorp/terraform/communicator/winrm/_test/_testmain.go:62 +0x20f Goroutine 14 (finished) created at: github.com/hashicorp/terraform/communicator/winrm.runCommand() /Users/phinze/go/src/github.com/hashicorp/terraform/communicator/winrm/communicator.go:151 +0xf8 ================== ```
2016-01-16 19:33:24 +01:00
"sync"
2015-04-10 20:34:46 +02:00
"time"
"github.com/hashicorp/terraform/communicator/remote"
"github.com/hashicorp/terraform/terraform"
"github.com/masterzen/winrm/winrm"
"github.com/packer-community/winrmcp/winrmcp"
2015-05-01 22:28:12 +02:00
// This import is a bit strange, but it's needed so `make updatedeps` can see and download it
_ "github.com/dylanmei/winrmtest"
2015-04-10 20:34:46 +02:00
)
// Communicator represents the WinRM communicator
type Communicator struct {
connInfo *connectionInfo
client *winrm.Client
endpoint *winrm.Endpoint
rand *rand.Rand
2015-04-10 20:34:46 +02:00
}
// New creates a new communicator implementation over WinRM.
func New(s *terraform.InstanceState) (*Communicator, error) {
connInfo, err := parseConnectionInfo(s)
if err != nil {
return nil, err
}
endpoint := &winrm.Endpoint{
Host: connInfo.Host,
Port: connInfo.Port,
HTTPS: connInfo.HTTPS,
Insecure: connInfo.Insecure,
CACert: 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())),
2015-04-10 20:34:46 +02:00
}
return comm, nil
}
// Connect implementation of communicator.Communicator interface
func (c *Communicator) Connect(o terraform.UIOutput) error {
if c.client != nil {
return nil
}
params := winrm.DefaultParameters()
params.Timeout = formatDuration(c.Timeout())
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"+
" CACert: %t",
c.connInfo.Host,
c.connInfo.Port,
c.connInfo.User,
c.connInfo.Password != "",
c.connInfo.HTTPS,
c.connInfo.Insecure,
c.connInfo.CACert != nil,
))
}
log.Printf("connecting to remote shell using WinRM")
shell, err := client.CreateShell()
if err != nil {
log.Printf("connection error: %s", err)
return err
}
err = shell.Close()
if err != nil {
log.Printf("error closing connection: %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)
2015-04-10 20:34:46 +02:00
}
// Start implementation of communicator.Communicator interface
func (c *Communicator) Start(rc *remote.Cmd) error {
err := c.Connect(nil)
if err != nil {
return err
}
shell, err := c.client.CreateShell()
if err != nil {
return err
}
log.Printf("starting remote command: %s", rc.Command)
2015-04-10 20:34:46 +02:00
cmd, err := shell.Execute(rc.Command)
if err != nil {
return err
}
go runCommand(shell, cmd, rc)
return nil
}
func runCommand(shell *winrm.Shell, cmd *winrm.Command, rc *remote.Cmd) {
defer shell.Close()
communicator/winrm: fix data race in io copy As the command completes, the winrm.Command does not wait for its copy of the remote.Command streams to report itself completed. This adds an additional sync.WaitGroup to ensure that copy is finished up before moving on, solving the data race. Fixes the following data race: ``` ================== WARNING: DATA RACE Read by goroutine 6: github.com/hashicorp/terraform/communicator/winrm.TestStart() /Users/phinze/go/src/github.com/hashicorp/terraform/communicator/winrm/communicator_test.go:79 +0xa44 testing.tRunner() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/testing/testing.go:456 +0xdc Previous write by goroutine 14: bytes.(*Buffer).ReadFrom() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/bytes/buffer.go:174 +0x465 io.copyBuffer() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/io/io.go:375 +0x1a5 io.Copy() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/io/io.go:351 +0x78 Goroutine 6 (running) created at: testing.RunTests() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/testing/testing.go:561 +0xaa3 testing.(*M).Run() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/testing/testing.go:494 +0xe4 main.main() github.com/hashicorp/terraform/communicator/winrm/_test/_testmain.go:62 +0x20f Goroutine 14 (finished) created at: github.com/hashicorp/terraform/communicator/winrm.runCommand() /Users/phinze/go/src/github.com/hashicorp/terraform/communicator/winrm/communicator.go:151 +0xf8 ================== ```
2016-01-16 19:33:24 +01:00
var wg sync.WaitGroup
go func() {
wg.Add(1)
io.Copy(rc.Stdout, cmd.Stdout)
wg.Done()
}()
go func() {
wg.Add(1)
io.Copy(rc.Stderr, cmd.Stderr)
wg.Done()
}()
2015-04-10 20:34:46 +02:00
cmd.Wait()
communicator/winrm: fix data race in io copy As the command completes, the winrm.Command does not wait for its copy of the remote.Command streams to report itself completed. This adds an additional sync.WaitGroup to ensure that copy is finished up before moving on, solving the data race. Fixes the following data race: ``` ================== WARNING: DATA RACE Read by goroutine 6: github.com/hashicorp/terraform/communicator/winrm.TestStart() /Users/phinze/go/src/github.com/hashicorp/terraform/communicator/winrm/communicator_test.go:79 +0xa44 testing.tRunner() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/testing/testing.go:456 +0xdc Previous write by goroutine 14: bytes.(*Buffer).ReadFrom() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/bytes/buffer.go:174 +0x465 io.copyBuffer() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/io/io.go:375 +0x1a5 io.Copy() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/io/io.go:351 +0x78 Goroutine 6 (running) created at: testing.RunTests() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/testing/testing.go:561 +0xaa3 testing.(*M).Run() /private/var/folders/vd/7l9ys5k57l91x63sh28wl_kc0000gn/T/workdir/go/src/testing/testing.go:494 +0xe4 main.main() github.com/hashicorp/terraform/communicator/winrm/_test/_testmain.go:62 +0x20f Goroutine 14 (finished) created at: github.com/hashicorp/terraform/communicator/winrm.runCommand() /Users/phinze/go/src/github.com/hashicorp/terraform/communicator/winrm/communicator.go:151 +0xf8 ================== ```
2016-01-16 19:33:24 +01:00
wg.Wait()
2015-04-10 20:34:46 +02:00
rc.SetExited(cmd.ExitCode())
}
// 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("Uploading file to '%s'", path)
2015-04-10 20:34:46 +02:00
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("Uploading dir '%s' to '%s'", src, dst)
2015-04-10 20:34:46 +02:00
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{
2015-04-10 20:34:46 +02:00
Auth: winrmcp.Auth{
User: c.connInfo.User,
Password: c.connInfo.Password,
},
Https: c.connInfo.HTTPS,
Insecure: c.connInfo.Insecure,
2015-04-10 20:34:46 +02:00
OperationTimeout: c.Timeout(),
MaxOperationsPerShell: 15, // lowest common denominator
}
if c.connInfo.CACert != nil {
config.CACertBytes = *c.connInfo.CACert
}
return winrmcp.New(addr, &config)
2015-04-10 20:34:46 +02:00
}