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, ¶ms) 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 }