github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/communicator/winrm/communicator.go (about)

     1  package winrm
     2  
     3  import (
     4  	"encoding/base64"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"log"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/hashicorp/packer/packer"
    15  	"github.com/masterzen/winrm"
    16  	"github.com/packer-community/winrmcp/winrmcp"
    17  )
    18  
    19  // Communicator represents the WinRM communicator
    20  type Communicator struct {
    21  	config   *Config
    22  	client   *winrm.Client
    23  	endpoint *winrm.Endpoint
    24  }
    25  
    26  // New creates a new communicator implementation over WinRM.
    27  func New(config *Config) (*Communicator, error) {
    28  	endpoint := &winrm.Endpoint{
    29  		Host:     config.Host,
    30  		Port:     config.Port,
    31  		HTTPS:    config.Https,
    32  		Insecure: config.Insecure,
    33  
    34  		/*
    35  			TODO
    36  			HTTPS:    connInfo.HTTPS,
    37  			Insecure: connInfo.Insecure,
    38  			CACert:   connInfo.CACert,
    39  		*/
    40  	}
    41  
    42  	// Create the client
    43  	params := *winrm.DefaultParameters
    44  
    45  	if config.TransportDecorator != nil {
    46  		params.TransportDecorator = config.TransportDecorator
    47  	}
    48  
    49  	params.Timeout = formatDuration(config.Timeout)
    50  	client, err := winrm.NewClientWithParameters(
    51  		endpoint, config.Username, config.Password, &params)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	// Create the shell to verify the connection
    57  	log.Printf("[DEBUG] connecting to remote shell using WinRM")
    58  	shell, err := client.CreateShell()
    59  	if err != nil {
    60  		log.Printf("[ERROR] connection error: %s", err)
    61  		return nil, err
    62  	}
    63  
    64  	if err := shell.Close(); err != nil {
    65  		log.Printf("[ERROR] error closing connection: %s", err)
    66  		return nil, err
    67  	}
    68  
    69  	return &Communicator{
    70  		config:   config,
    71  		client:   client,
    72  		endpoint: endpoint,
    73  	}, nil
    74  }
    75  
    76  // Start implementation of communicator.Communicator interface
    77  func (c *Communicator) Start(rc *packer.RemoteCmd) error {
    78  	shell, err := c.client.CreateShell()
    79  	if err != nil {
    80  		return err
    81  	}
    82  
    83  	log.Printf("[INFO] starting remote command: %s", rc.Command)
    84  	cmd, err := shell.Execute(rc.Command)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	go runCommand(shell, cmd, rc)
    90  	return nil
    91  }
    92  
    93  func runCommand(shell *winrm.Shell, cmd *winrm.Command, rc *packer.RemoteCmd) {
    94  	defer shell.Close()
    95  	var wg sync.WaitGroup
    96  
    97  	copyFunc := func(w io.Writer, r io.Reader) {
    98  		defer wg.Done()
    99  		io.Copy(w, r)
   100  	}
   101  
   102  	if rc.Stdout != nil && cmd.Stdout != nil {
   103  		wg.Add(1)
   104  		go copyFunc(rc.Stdout, cmd.Stdout)
   105  	} else {
   106  		log.Printf("[WARN] Failed to read stdout for command '%s'", rc.Command)
   107  	}
   108  
   109  	if rc.Stderr != nil && cmd.Stderr != nil {
   110  		wg.Add(1)
   111  		go copyFunc(rc.Stderr, cmd.Stderr)
   112  	} else {
   113  		log.Printf("[WARN] Failed to read stderr for command '%s'", rc.Command)
   114  	}
   115  
   116  	cmd.Wait()
   117  	wg.Wait()
   118  
   119  	code := cmd.ExitCode()
   120  	log.Printf("[INFO] command '%s' exited with code: %d", rc.Command, code)
   121  	rc.SetExited(code)
   122  }
   123  
   124  // Upload implementation of communicator.Communicator interface
   125  func (c *Communicator) Upload(path string, input io.Reader, _ *os.FileInfo) error {
   126  	wcp, err := c.newCopyClient()
   127  	if err != nil {
   128  		return err
   129  	}
   130  	log.Printf("Uploading file to '%s'", path)
   131  	return wcp.Write(path, input)
   132  }
   133  
   134  // UploadDir implementation of communicator.Communicator interface
   135  func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
   136  	if !strings.HasSuffix(src, "/") {
   137  		dst = fmt.Sprintf("%s\\%s", dst, filepath.Base(src))
   138  	}
   139  	log.Printf("Uploading dir '%s' to '%s'", src, dst)
   140  	wcp, err := c.newCopyClient()
   141  	if err != nil {
   142  		return err
   143  	}
   144  	return wcp.Copy(src, dst)
   145  }
   146  
   147  func (c *Communicator) Download(src string, dst io.Writer) error {
   148  	endpoint := winrm.NewEndpoint(c.endpoint.Host, c.endpoint.Port, c.config.Https, c.config.Insecure, nil, nil, nil, c.config.Timeout)
   149  	client, err := winrm.NewClient(endpoint, c.config.Username, c.config.Password)
   150  	if err != nil {
   151  		return err
   152  	}
   153  
   154  	encodeScript := `$file=[System.IO.File]::ReadAllBytes("%s"); Write-Output $([System.Convert]::ToBase64String($file))`
   155  
   156  	base64DecodePipe := &Base64Pipe{w: dst}
   157  
   158  	cmd := winrm.Powershell(fmt.Sprintf(encodeScript, src))
   159  	_, err = client.Run(cmd, base64DecodePipe, ioutil.Discard)
   160  
   161  	return err
   162  }
   163  
   164  func (c *Communicator) DownloadDir(src string, dst string, exclude []string) error {
   165  	return fmt.Errorf("WinRM doesn't support download dir.")
   166  }
   167  
   168  func (c *Communicator) newCopyClient() (*winrmcp.Winrmcp, error) {
   169  	addr := fmt.Sprintf("%s:%d", c.endpoint.Host, c.endpoint.Port)
   170  	return winrmcp.New(addr, &winrmcp.Config{
   171  		Auth: winrmcp.Auth{
   172  			User:     c.config.Username,
   173  			Password: c.config.Password,
   174  		},
   175  		Https:                 c.config.Https,
   176  		Insecure:              c.config.Insecure,
   177  		OperationTimeout:      c.config.Timeout,
   178  		MaxOperationsPerShell: 15, // lowest common denominator
   179  		TransportDecorator:    c.config.TransportDecorator,
   180  	})
   181  }
   182  
   183  type Base64Pipe struct {
   184  	w io.Writer // underlying writer (file, buffer)
   185  }
   186  
   187  func (d *Base64Pipe) ReadFrom(r io.Reader) (int64, error) {
   188  	b, err := ioutil.ReadAll(r)
   189  	if err != nil {
   190  		return 0, err
   191  	}
   192  
   193  	var i int
   194  	i, err = d.Write(b)
   195  
   196  	if err != nil {
   197  		return 0, err
   198  	}
   199  
   200  	return int64(i), err
   201  }
   202  
   203  func (d *Base64Pipe) Write(p []byte) (int, error) {
   204  	dst := make([]byte, base64.StdEncoding.DecodedLen(len(p)))
   205  
   206  	decodedBytes, err := base64.StdEncoding.Decode(dst, p)
   207  	if err != nil {
   208  		return 0, err
   209  	}
   210  
   211  	return d.w.Write(dst[0:decodedBytes])
   212  }