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