github.com/rish1988/moby@v25.0.2+incompatible/client/container_wait.go (about) 1 package client // import "github.com/docker/docker/client" 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "io" 9 "net/url" 10 11 "github.com/docker/docker/api/types/container" 12 "github.com/docker/docker/api/types/versions" 13 ) 14 15 const containerWaitErrorMsgLimit = 2 * 1024 /* Max: 2KiB */ 16 17 // ContainerWait waits until the specified container is in a certain state 18 // indicated by the given condition, either "not-running" (default), 19 // "next-exit", or "removed". 20 // 21 // If this client's API version is before 1.30, condition is ignored and 22 // ContainerWait will return immediately with the two channels, as the server 23 // will wait as if the condition were "not-running". 24 // 25 // If this client's API version is at least 1.30, ContainerWait blocks until 26 // the request has been acknowledged by the server (with a response header), 27 // then returns two channels on which the caller can wait for the exit status 28 // of the container or an error if there was a problem either beginning the 29 // wait request or in getting the response. This allows the caller to 30 // synchronize ContainerWait with other calls, such as specifying a 31 // "next-exit" condition before issuing a ContainerStart request. 32 func (cli *Client) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) { 33 // Make sure we negotiated (if the client is configured to do so), 34 // as code below contains API-version specific handling of options. 35 // 36 // Normally, version-negotiation (if enabled) would not happen until 37 // the API request is made. 38 cli.checkVersion(ctx) 39 if versions.LessThan(cli.ClientVersion(), "1.30") { 40 return cli.legacyContainerWait(ctx, containerID) 41 } 42 43 resultC := make(chan container.WaitResponse) 44 errC := make(chan error, 1) 45 46 query := url.Values{} 47 if condition != "" { 48 query.Set("condition", string(condition)) 49 } 50 51 resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", query, nil, nil) 52 if err != nil { 53 defer ensureReaderClosed(resp) 54 errC <- err 55 return resultC, errC 56 } 57 58 go func() { 59 defer ensureReaderClosed(resp) 60 61 body := resp.body 62 responseText := bytes.NewBuffer(nil) 63 stream := io.TeeReader(body, responseText) 64 65 var res container.WaitResponse 66 if err := json.NewDecoder(stream).Decode(&res); err != nil { 67 // NOTE(nicks): The /wait API does not work well with HTTP proxies. 68 // At any time, the proxy could cut off the response stream. 69 // 70 // But because the HTTP status has already been written, the proxy's 71 // only option is to write a plaintext error message. 72 // 73 // If there's a JSON parsing error, read the real error message 74 // off the body and send it to the client. 75 if errors.As(err, new(*json.SyntaxError)) { 76 _, _ = io.ReadAll(io.LimitReader(stream, containerWaitErrorMsgLimit)) 77 errC <- errors.New(responseText.String()) 78 } else { 79 errC <- err 80 } 81 return 82 } 83 84 resultC <- res 85 }() 86 87 return resultC, errC 88 } 89 90 // legacyContainerWait returns immediately and doesn't have an option to wait 91 // until the container is removed. 92 func (cli *Client) legacyContainerWait(ctx context.Context, containerID string) (<-chan container.WaitResponse, <-chan error) { 93 resultC := make(chan container.WaitResponse) 94 errC := make(chan error) 95 96 go func() { 97 resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil) 98 if err != nil { 99 errC <- err 100 return 101 } 102 defer ensureReaderClosed(resp) 103 104 var res container.WaitResponse 105 if err := json.NewDecoder(resp.body).Decode(&res); err != nil { 106 errC <- err 107 return 108 } 109 110 resultC <- res 111 }() 112 113 return resultC, errC 114 }