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