github.com/kaixiang/packer@v0.5.2-0.20140114230416-1f5786b0d7f1/packer/plugin/client.go (about) 1 package plugin 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "github.com/mitchellh/packer/packer" 8 packrpc "github.com/mitchellh/packer/packer/rpc" 9 "io" 10 "io/ioutil" 11 "log" 12 "net" 13 "os" 14 "os/exec" 15 "strings" 16 "sync" 17 "time" 18 "unicode" 19 ) 20 21 // If this is true, then the "unexpected EOF" panic will not be 22 // raised throughout the clients. 23 var Killed = false 24 25 // This is a slice of the "managed" clients which are cleaned up when 26 // calling Cleanup 27 var managedClients = make([]*Client, 0, 5) 28 29 // Client handles the lifecycle of a plugin application, determining its 30 // RPC address, and returning various types of packer interface implementations 31 // across the multi-process communication layer. 32 type Client struct { 33 config *ClientConfig 34 exited bool 35 doneLogging chan struct{} 36 l sync.Mutex 37 address net.Addr 38 } 39 40 // ClientConfig is the configuration used to initialize a new 41 // plugin client. After being used to initialize a plugin client, 42 // that configuration must not be modified again. 43 type ClientConfig struct { 44 // The unstarted subprocess for starting the plugin. 45 Cmd *exec.Cmd 46 47 // Managed represents if the client should be managed by the 48 // plugin package or not. If true, then by calling CleanupClients, 49 // it will automatically be cleaned up. Otherwise, the client 50 // user is fully responsible for making sure to Kill all plugin 51 // clients. By default the client is _not_ managed. 52 Managed bool 53 54 // The minimum and maximum port to use for communicating with 55 // the subprocess. If not set, this defaults to 10,000 and 25,000 56 // respectively. 57 MinPort, MaxPort uint 58 59 // StartTimeout is the timeout to wait for the plugin to say it 60 // has started successfully. 61 StartTimeout time.Duration 62 63 // If non-nil, then the stderr of the client will be written to here 64 // (as well as the log). 65 Stderr io.Writer 66 } 67 68 // This makes sure all the managed subprocesses are killed and properly 69 // logged. This should be called before the parent process running the 70 // plugins exits. 71 // 72 // This must only be called _once_. 73 func CleanupClients() { 74 // Set the killed to true so that we don't get unexpected panics 75 Killed = true 76 77 // Kill all the managed clients in parallel and use a WaitGroup 78 // to wait for them all to finish up. 79 var wg sync.WaitGroup 80 for _, client := range managedClients { 81 wg.Add(1) 82 83 go func(client *Client) { 84 client.Kill() 85 wg.Done() 86 }(client) 87 } 88 89 log.Println("waiting for all plugin processes to complete...") 90 wg.Wait() 91 } 92 93 // Creates a new plugin client which manages the lifecycle of an external 94 // plugin and gets the address for the RPC connection. 95 // 96 // The client must be cleaned up at some point by calling Kill(). If 97 // the client is a managed client (created with NewManagedClient) you 98 // can just call CleanupClients at the end of your program and they will 99 // be properly cleaned. 100 func NewClient(config *ClientConfig) (c *Client) { 101 if config.MinPort == 0 && config.MaxPort == 0 { 102 config.MinPort = 10000 103 config.MaxPort = 25000 104 } 105 106 if config.StartTimeout == 0 { 107 config.StartTimeout = 1 * time.Minute 108 } 109 110 if config.Stderr == nil { 111 config.Stderr = ioutil.Discard 112 } 113 114 c = &Client{config: config} 115 if config.Managed { 116 managedClients = append(managedClients, c) 117 } 118 119 return 120 } 121 122 // Tells whether or not the underlying process has exited. 123 func (c *Client) Exited() bool { 124 c.l.Lock() 125 defer c.l.Unlock() 126 return c.exited 127 } 128 129 // Returns a builder implementation that is communicating over this 130 // client. If the client hasn't been started, this will start it. 131 func (c *Client) Builder() (packer.Builder, error) { 132 client, err := c.packrpcClient() 133 if err != nil { 134 return nil, err 135 } 136 137 return &cmdBuilder{client.Builder(), c}, nil 138 } 139 140 // Returns a command implementation that is communicating over this 141 // client. If the client hasn't been started, this will start it. 142 func (c *Client) Command() (packer.Command, error) { 143 client, err := c.packrpcClient() 144 if err != nil { 145 return nil, err 146 } 147 148 return &cmdCommand{client.Command(), c}, nil 149 } 150 151 // Returns a hook implementation that is communicating over this 152 // client. If the client hasn't been started, this will start it. 153 func (c *Client) Hook() (packer.Hook, error) { 154 client, err := c.packrpcClient() 155 if err != nil { 156 return nil, err 157 } 158 159 return &cmdHook{client.Hook(), c}, nil 160 } 161 162 // Returns a post-processor implementation that is communicating over 163 // this client. If the client hasn't been started, this will start it. 164 func (c *Client) PostProcessor() (packer.PostProcessor, error) { 165 client, err := c.packrpcClient() 166 if err != nil { 167 return nil, err 168 } 169 170 return &cmdPostProcessor{client.PostProcessor(), c}, nil 171 } 172 173 // Returns a provisioner implementation that is communicating over this 174 // client. If the client hasn't been started, this will start it. 175 func (c *Client) Provisioner() (packer.Provisioner, error) { 176 client, err := c.packrpcClient() 177 if err != nil { 178 return nil, err 179 } 180 181 return &cmdProvisioner{client.Provisioner(), c}, nil 182 } 183 184 // End the executing subprocess (if it is running) and perform any cleanup 185 // tasks necessary such as capturing any remaining logs and so on. 186 // 187 // This method blocks until the process successfully exits. 188 // 189 // This method can safely be called multiple times. 190 func (c *Client) Kill() { 191 cmd := c.config.Cmd 192 193 if cmd.Process == nil { 194 return 195 } 196 197 cmd.Process.Kill() 198 199 // Wait for the client to finish logging so we have a complete log 200 <-c.doneLogging 201 } 202 203 // Starts the underlying subprocess, communicating with it to negotiate 204 // a port for RPC connections, and returning the address to connect via RPC. 205 // 206 // This method is safe to call multiple times. Subsequent calls have no effect. 207 // Once a client has been started once, it cannot be started again, even if 208 // it was killed. 209 func (c *Client) Start() (addr net.Addr, err error) { 210 c.l.Lock() 211 defer c.l.Unlock() 212 213 if c.address != nil { 214 return c.address, nil 215 } 216 217 c.doneLogging = make(chan struct{}) 218 219 env := []string{ 220 fmt.Sprintf("%s=%s", MagicCookieKey, MagicCookieValue), 221 fmt.Sprintf("PACKER_PLUGIN_MIN_PORT=%d", c.config.MinPort), 222 fmt.Sprintf("PACKER_PLUGIN_MAX_PORT=%d", c.config.MaxPort), 223 } 224 225 stdout_r, stdout_w := io.Pipe() 226 stderr_r, stderr_w := io.Pipe() 227 228 cmd := c.config.Cmd 229 cmd.Env = append(cmd.Env, os.Environ()...) 230 cmd.Env = append(cmd.Env, env...) 231 cmd.Stdin = os.Stdin 232 cmd.Stderr = stderr_w 233 cmd.Stdout = stdout_w 234 235 log.Printf("Starting plugin: %s %#v", cmd.Path, cmd.Args) 236 err = cmd.Start() 237 if err != nil { 238 return 239 } 240 241 // Make sure the command is properly cleaned up if there is an error 242 defer func() { 243 r := recover() 244 245 if err != nil || r != nil { 246 cmd.Process.Kill() 247 } 248 249 if r != nil { 250 panic(r) 251 } 252 }() 253 254 // Start goroutine to wait for process to exit 255 exitCh := make(chan struct{}) 256 go func() { 257 // Make sure we close the write end of our stderr/stdout so 258 // that the readers send EOF properly. 259 defer stderr_w.Close() 260 defer stdout_w.Close() 261 262 // Wait for the command to end. 263 cmd.Wait() 264 265 // Log and make sure to flush the logs write away 266 log.Printf("%s: plugin process exited\n", cmd.Path) 267 os.Stderr.Sync() 268 269 // Mark that we exited 270 close(exitCh) 271 272 // Set that we exited, which takes a lock 273 c.l.Lock() 274 defer c.l.Unlock() 275 c.exited = true 276 }() 277 278 // Start goroutine that logs the stderr 279 go c.logStderr(stderr_r) 280 281 // Start a goroutine that is going to be reading the lines 282 // out of stdout 283 linesCh := make(chan []byte) 284 go func() { 285 defer close(linesCh) 286 287 buf := bufio.NewReader(stdout_r) 288 for { 289 line, err := buf.ReadBytes('\n') 290 if line != nil { 291 linesCh <- line 292 } 293 294 if err == io.EOF { 295 return 296 } 297 } 298 }() 299 300 // Make sure after we exit we read the lines from stdout forever 301 // so they dont' block since it is an io.Pipe 302 defer func() { 303 go func() { 304 for _ = range linesCh { 305 } 306 }() 307 }() 308 309 // Some channels for the next step 310 timeout := time.After(c.config.StartTimeout) 311 312 // Start looking for the address 313 log.Printf("Waiting for RPC address for: %s", cmd.Path) 314 select { 315 case <-timeout: 316 err = errors.New("timeout while waiting for plugin to start") 317 case <-exitCh: 318 err = errors.New("plugin exited before we could connect") 319 case lineBytes := <-linesCh: 320 // Trim the line and split by "|" in order to get the parts of 321 // the output. 322 line := strings.TrimSpace(string(lineBytes)) 323 parts := strings.SplitN(line, "|", 3) 324 if len(parts) < 3 { 325 err = fmt.Errorf("Unrecognized remote plugin message: %s", line) 326 return 327 } 328 329 // Test the API version 330 if parts[0] != APIVersion { 331 err = fmt.Errorf("Incompatible API version with plugin. "+ 332 "Plugin version: %s, Ours: %s", parts[0], APIVersion) 333 return 334 } 335 336 switch parts[1] { 337 case "tcp": 338 addr, err = net.ResolveTCPAddr("tcp", parts[2]) 339 case "unix": 340 addr, err = net.ResolveUnixAddr("unix", parts[2]) 341 default: 342 err = fmt.Errorf("Unknown address type: %s", parts[1]) 343 } 344 } 345 346 c.address = addr 347 return 348 } 349 350 func (c *Client) logStderr(r io.Reader) { 351 bufR := bufio.NewReader(r) 352 for { 353 line, err := bufR.ReadString('\n') 354 if line != "" { 355 c.config.Stderr.Write([]byte(line)) 356 357 line = strings.TrimRightFunc(line, unicode.IsSpace) 358 log.Printf("%s: %s", c.config.Cmd.Path, line) 359 } 360 361 if err == io.EOF { 362 break 363 } 364 } 365 366 // Flag that we've completed logging for others 367 close(c.doneLogging) 368 } 369 370 func (c *Client) packrpcClient() (*packrpc.Client, error) { 371 addr, err := c.Start() 372 if err != nil { 373 return nil, err 374 } 375 376 conn, err := net.Dial(addr.Network(), addr.String()) 377 if err != nil { 378 return nil, err 379 } 380 381 if tcpConn, ok := conn.(*net.TCPConn); ok { 382 // Make sure to set keep alive so that the connection doesn't die 383 tcpConn.SetKeepAlive(true) 384 } 385 386 client, err := packrpc.NewClient(conn) 387 if err != nil { 388 conn.Close() 389 return nil, err 390 } 391 392 return client, nil 393 }