github.com/scottwinkler/terraform@v0.11.6-0.20180329211809-05143987aea8/communicator/winrm/communicator.go (about)

     1  package winrm
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"log"
     7  	"math/rand"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/hashicorp/terraform/communicator/remote"
    14  	"github.com/hashicorp/terraform/terraform"
    15  	"github.com/masterzen/winrm"
    16  	"github.com/packer-community/winrmcp/winrmcp"
    17  
    18  	// This import is a bit strange, but it's needed so `make updatedeps` can see and download it
    19  	_ "github.com/dylanmei/winrmtest"
    20  )
    21  
    22  // Communicator represents the WinRM communicator
    23  type Communicator struct {
    24  	connInfo *connectionInfo
    25  	client   *winrm.Client
    26  	endpoint *winrm.Endpoint
    27  	rand     *rand.Rand
    28  }
    29  
    30  // New creates a new communicator implementation over WinRM.
    31  func New(s *terraform.InstanceState) (*Communicator, error) {
    32  	connInfo, err := parseConnectionInfo(s)
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  
    37  	endpoint := &winrm.Endpoint{
    38  		Host:     connInfo.Host,
    39  		Port:     connInfo.Port,
    40  		HTTPS:    connInfo.HTTPS,
    41  		Insecure: connInfo.Insecure,
    42  	}
    43  	if len(connInfo.CACert) > 0 {
    44  		endpoint.CACert = []byte(connInfo.CACert)
    45  	}
    46  
    47  	comm := &Communicator{
    48  		connInfo: connInfo,
    49  		endpoint: endpoint,
    50  		// Seed our own rand source so that script paths are not deterministic
    51  		rand: rand.New(rand.NewSource(time.Now().UnixNano())),
    52  	}
    53  
    54  	return comm, nil
    55  }
    56  
    57  // Connect implementation of communicator.Communicator interface
    58  func (c *Communicator) Connect(o terraform.UIOutput) error {
    59  	if c.client != nil {
    60  		return nil
    61  	}
    62  
    63  	params := winrm.DefaultParameters
    64  	params.Timeout = formatDuration(c.Timeout())
    65  
    66  	client, err := winrm.NewClientWithParameters(
    67  		c.endpoint, c.connInfo.User, c.connInfo.Password, params)
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	if o != nil {
    73  		o.Output(fmt.Sprintf(
    74  			"Connecting to remote host via WinRM...\n"+
    75  				"  Host: %s\n"+
    76  				"  Port: %d\n"+
    77  				"  User: %s\n"+
    78  				"  Password: %t\n"+
    79  				"  HTTPS: %t\n"+
    80  				"  Insecure: %t\n"+
    81  				"  CACert: %t",
    82  			c.connInfo.Host,
    83  			c.connInfo.Port,
    84  			c.connInfo.User,
    85  			c.connInfo.Password != "",
    86  			c.connInfo.HTTPS,
    87  			c.connInfo.Insecure,
    88  			c.connInfo.CACert != "",
    89  		))
    90  	}
    91  
    92  	log.Printf("connecting to remote shell using WinRM")
    93  	shell, err := client.CreateShell()
    94  	if err != nil {
    95  		log.Printf("connection error: %s", err)
    96  		return err
    97  	}
    98  
    99  	err = shell.Close()
   100  	if err != nil {
   101  		log.Printf("error closing connection: %s", err)
   102  		return err
   103  	}
   104  
   105  	if o != nil {
   106  		o.Output("Connected!")
   107  	}
   108  
   109  	c.client = client
   110  
   111  	return nil
   112  }
   113  
   114  // Disconnect implementation of communicator.Communicator interface
   115  func (c *Communicator) Disconnect() error {
   116  	c.client = nil
   117  	return nil
   118  }
   119  
   120  // Timeout implementation of communicator.Communicator interface
   121  func (c *Communicator) Timeout() time.Duration {
   122  	return c.connInfo.TimeoutVal
   123  }
   124  
   125  // ScriptPath implementation of communicator.Communicator interface
   126  func (c *Communicator) ScriptPath() string {
   127  	return strings.Replace(
   128  		c.connInfo.ScriptPath, "%RAND%",
   129  		strconv.FormatInt(int64(c.rand.Int31()), 10), -1)
   130  }
   131  
   132  // Start implementation of communicator.Communicator interface
   133  func (c *Communicator) Start(rc *remote.Cmd) error {
   134  	rc.Init()
   135  
   136  	err := c.Connect(nil)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	shell, err := c.client.CreateShell()
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	log.Printf("starting remote command: %s", rc.Command)
   147  	cmd, err := shell.Execute(rc.Command)
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	go runCommand(shell, cmd, rc)
   153  	return nil
   154  }
   155  
   156  func runCommand(shell *winrm.Shell, cmd *winrm.Command, rc *remote.Cmd) {
   157  	defer shell.Close()
   158  
   159  	var wg sync.WaitGroup
   160  	go func() {
   161  		wg.Add(1)
   162  		io.Copy(rc.Stdout, cmd.Stdout)
   163  		wg.Done()
   164  	}()
   165  	go func() {
   166  		wg.Add(1)
   167  		io.Copy(rc.Stderr, cmd.Stderr)
   168  		wg.Done()
   169  	}()
   170  
   171  	cmd.Wait()
   172  	wg.Wait()
   173  
   174  	rc.SetExitStatus(cmd.ExitCode(), nil)
   175  }
   176  
   177  // Upload implementation of communicator.Communicator interface
   178  func (c *Communicator) Upload(path string, input io.Reader) error {
   179  	wcp, err := c.newCopyClient()
   180  	if err != nil {
   181  		return err
   182  	}
   183  	log.Printf("Uploading file to '%s'", path)
   184  	return wcp.Write(path, input)
   185  }
   186  
   187  // UploadScript implementation of communicator.Communicator interface
   188  func (c *Communicator) UploadScript(path string, input io.Reader) error {
   189  	return c.Upload(path, input)
   190  }
   191  
   192  // UploadDir implementation of communicator.Communicator interface
   193  func (c *Communicator) UploadDir(dst string, src string) error {
   194  	log.Printf("Uploading dir '%s' to '%s'", src, dst)
   195  	wcp, err := c.newCopyClient()
   196  	if err != nil {
   197  		return err
   198  	}
   199  	return wcp.Copy(src, dst)
   200  }
   201  
   202  func (c *Communicator) newCopyClient() (*winrmcp.Winrmcp, error) {
   203  	addr := fmt.Sprintf("%s:%d", c.endpoint.Host, c.endpoint.Port)
   204  
   205  	config := winrmcp.Config{
   206  		Auth: winrmcp.Auth{
   207  			User:     c.connInfo.User,
   208  			Password: c.connInfo.Password,
   209  		},
   210  		Https:                 c.connInfo.HTTPS,
   211  		Insecure:              c.connInfo.Insecure,
   212  		OperationTimeout:      c.Timeout(),
   213  		MaxOperationsPerShell: 15, // lowest common denominator
   214  	}
   215  
   216  	if c.connInfo.CACert != "" {
   217  		config.CACertBytes = []byte(c.connInfo.CACert)
   218  	}
   219  
   220  	return winrmcp.New(addr, &config)
   221  }