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