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