github.com/amanya/packer@v0.12.1-0.20161117214323-902ac5ab2eb6/packer/rpc/communicator.go (about) 1 package rpc 2 3 import ( 4 "encoding/gob" 5 "io" 6 "log" 7 "net/rpc" 8 "os" 9 10 "github.com/mitchellh/packer/packer" 11 ) 12 13 // An implementation of packer.Communicator where the communicator is actually 14 // executed over an RPC connection. 15 type communicator struct { 16 client *rpc.Client 17 mux *muxBroker 18 } 19 20 // CommunicatorServer wraps a packer.Communicator implementation and makes 21 // it exportable as part of a Golang RPC server. 22 type CommunicatorServer struct { 23 c packer.Communicator 24 mux *muxBroker 25 } 26 27 type CommandFinished struct { 28 ExitStatus int 29 } 30 31 type CommunicatorStartArgs struct { 32 Command string 33 StdinStreamId uint32 34 StdoutStreamId uint32 35 StderrStreamId uint32 36 ResponseStreamId uint32 37 } 38 39 type CommunicatorDownloadArgs struct { 40 Path string 41 WriterStreamId uint32 42 } 43 44 type CommunicatorUploadArgs struct { 45 Path string 46 ReaderStreamId uint32 47 FileInfo *fileInfo 48 } 49 50 type CommunicatorUploadDirArgs struct { 51 Dst string 52 Src string 53 Exclude []string 54 } 55 56 type CommunicatorDownloadDirArgs struct { 57 Dst string 58 Src string 59 Exclude []string 60 } 61 62 func Communicator(client *rpc.Client) *communicator { 63 return &communicator{client: client} 64 } 65 66 func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) { 67 var args CommunicatorStartArgs 68 args.Command = cmd.Command 69 70 if cmd.Stdin != nil { 71 args.StdinStreamId = c.mux.NextId() 72 go serveSingleCopy("stdin", c.mux, args.StdinStreamId, nil, cmd.Stdin) 73 } 74 75 if cmd.Stdout != nil { 76 args.StdoutStreamId = c.mux.NextId() 77 go serveSingleCopy("stdout", c.mux, args.StdoutStreamId, cmd.Stdout, nil) 78 } 79 80 if cmd.Stderr != nil { 81 args.StderrStreamId = c.mux.NextId() 82 go serveSingleCopy("stderr", c.mux, args.StderrStreamId, cmd.Stderr, nil) 83 } 84 85 responseStreamId := c.mux.NextId() 86 args.ResponseStreamId = responseStreamId 87 88 go func() { 89 conn, err := c.mux.Accept(responseStreamId) 90 if err != nil { 91 log.Printf("[ERR] Error accepting response stream %d: %s", 92 responseStreamId, err) 93 cmd.SetExited(123) 94 return 95 } 96 defer conn.Close() 97 98 var finished CommandFinished 99 decoder := gob.NewDecoder(conn) 100 if err := decoder.Decode(&finished); err != nil { 101 log.Printf("[ERR] Error decoding response stream %d: %s", 102 responseStreamId, err) 103 cmd.SetExited(123) 104 return 105 } 106 107 log.Printf("[INFO] RPC client: Communicator ended with: %d", finished.ExitStatus) 108 cmd.SetExited(finished.ExitStatus) 109 }() 110 111 err = c.client.Call("Communicator.Start", &args, new(interface{})) 112 return 113 } 114 115 func (c *communicator) Upload(path string, r io.Reader, fi *os.FileInfo) (err error) { 116 // Pipe the reader through to the connection 117 streamId := c.mux.NextId() 118 go serveSingleCopy("uploadData", c.mux, streamId, nil, r) 119 120 args := CommunicatorUploadArgs{ 121 Path: path, 122 ReaderStreamId: streamId, 123 } 124 125 if fi != nil { 126 args.FileInfo = NewFileInfo(*fi) 127 } 128 129 err = c.client.Call("Communicator.Upload", &args, new(interface{})) 130 return 131 } 132 133 func (c *communicator) UploadDir(dst string, src string, exclude []string) error { 134 args := &CommunicatorUploadDirArgs{ 135 Dst: dst, 136 Src: src, 137 Exclude: exclude, 138 } 139 140 var reply error 141 err := c.client.Call("Communicator.UploadDir", args, &reply) 142 if err == nil { 143 err = reply 144 } 145 146 return err 147 } 148 149 func (c *communicator) DownloadDir(src string, dst string, exclude []string) error { 150 args := &CommunicatorDownloadDirArgs{ 151 Dst: dst, 152 Src: src, 153 Exclude: exclude, 154 } 155 156 var reply error 157 err := c.client.Call("Communicator.DownloadDir", args, &reply) 158 if err == nil { 159 err = reply 160 } 161 162 return err 163 } 164 165 func (c *communicator) Download(path string, w io.Writer) (err error) { 166 // Serve a single connection and a single copy 167 streamId := c.mux.NextId() 168 169 waitServer := make(chan struct{}) 170 go func() { 171 serveSingleCopy("downloadWriter", c.mux, streamId, w, nil) 172 close(waitServer) 173 }() 174 175 args := CommunicatorDownloadArgs{ 176 Path: path, 177 WriterStreamId: streamId, 178 } 179 180 // Start sending data to the RPC server 181 err = c.client.Call("Communicator.Download", &args, new(interface{})) 182 183 // Wait for the RPC server to finish receiving the data before we return 184 <-waitServer 185 186 return 187 } 188 189 func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface{}) error { 190 // Build the RemoteCmd on this side so that it all pipes over 191 // to the remote side. 192 var cmd packer.RemoteCmd 193 cmd.Command = args.Command 194 195 // Create a channel to signal we're done so that we can close 196 // our stdin/stdout/stderr streams 197 toClose := make([]io.Closer, 0) 198 doneCh := make(chan struct{}) 199 go func() { 200 <-doneCh 201 for _, conn := range toClose { 202 defer conn.Close() 203 } 204 }() 205 206 if args.StdinStreamId > 0 { 207 conn, err := c.mux.Dial(args.StdinStreamId) 208 if err != nil { 209 close(doneCh) 210 return NewBasicError(err) 211 } 212 213 toClose = append(toClose, conn) 214 cmd.Stdin = conn 215 } 216 217 if args.StdoutStreamId > 0 { 218 conn, err := c.mux.Dial(args.StdoutStreamId) 219 if err != nil { 220 close(doneCh) 221 return NewBasicError(err) 222 } 223 224 toClose = append(toClose, conn) 225 cmd.Stdout = conn 226 } 227 228 if args.StderrStreamId > 0 { 229 conn, err := c.mux.Dial(args.StderrStreamId) 230 if err != nil { 231 close(doneCh) 232 return NewBasicError(err) 233 } 234 235 toClose = append(toClose, conn) 236 cmd.Stderr = conn 237 } 238 239 // Connect to the response address so we can write our result to it 240 // when ready. 241 responseC, err := c.mux.Dial(args.ResponseStreamId) 242 if err != nil { 243 close(doneCh) 244 return NewBasicError(err) 245 } 246 responseWriter := gob.NewEncoder(responseC) 247 248 // Start the actual command 249 err = c.c.Start(&cmd) 250 if err != nil { 251 close(doneCh) 252 return NewBasicError(err) 253 } 254 255 // Start a goroutine to spin and wait for the process to actual 256 // exit. When it does, report it back to caller... 257 go func() { 258 defer close(doneCh) 259 defer responseC.Close() 260 cmd.Wait() 261 log.Printf("[INFO] RPC endpoint: Communicator ended with: %d", cmd.ExitStatus) 262 responseWriter.Encode(&CommandFinished{cmd.ExitStatus}) 263 }() 264 265 return nil 266 } 267 268 func (c *CommunicatorServer) Upload(args *CommunicatorUploadArgs, reply *interface{}) (err error) { 269 readerC, err := c.mux.Dial(args.ReaderStreamId) 270 if err != nil { 271 return 272 } 273 defer readerC.Close() 274 275 var fi *os.FileInfo 276 if args.FileInfo != nil { 277 fi = new(os.FileInfo) 278 *fi = *args.FileInfo 279 } 280 err = c.c.Upload(args.Path, readerC, fi) 281 return 282 } 283 284 func (c *CommunicatorServer) UploadDir(args *CommunicatorUploadDirArgs, reply *error) error { 285 return c.c.UploadDir(args.Dst, args.Src, args.Exclude) 286 } 287 288 func (c *CommunicatorServer) DownloadDir(args *CommunicatorUploadDirArgs, reply *error) error { 289 return c.c.DownloadDir(args.Src, args.Dst, args.Exclude) 290 } 291 292 func (c *CommunicatorServer) Download(args *CommunicatorDownloadArgs, reply *interface{}) (err error) { 293 writerC, err := c.mux.Dial(args.WriterStreamId) 294 if err != nil { 295 return 296 } 297 defer writerC.Close() 298 299 err = c.c.Download(args.Path, writerC) 300 return 301 } 302 303 func serveSingleCopy(name string, mux *muxBroker, id uint32, dst io.Writer, src io.Reader) { 304 conn, err := mux.Accept(id) 305 if err != nil { 306 log.Printf("[ERR] '%s' accept error: %s", name, err) 307 return 308 } 309 310 // Be sure to close the connection after we're done copying so 311 // that an EOF will successfully be sent to the remote side 312 defer conn.Close() 313 314 // The connection is the destination/source that is nil 315 if dst == nil { 316 dst = conn 317 } else { 318 src = conn 319 } 320 321 written, err := io.Copy(dst, src) 322 log.Printf("[INFO] %d bytes written for '%s'", written, name) 323 if err != nil { 324 log.Printf("[ERR] '%s' copy error: %s", name, err) 325 } 326 }