github.com/rawahars/moby@v24.0.4+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 if versions.LessThan(cli.ClientVersion(), "1.30") { 34 return cli.legacyContainerWait(ctx, containerID) 35 } 36 37 resultC := make(chan container.WaitResponse) 38 errC := make(chan error, 1) 39 40 query := url.Values{} 41 if condition != "" { 42 query.Set("condition", string(condition)) 43 } 44 45 resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", query, nil, nil) 46 if err != nil { 47 defer ensureReaderClosed(resp) 48 errC <- err 49 return resultC, errC 50 } 51 52 go func() { 53 defer ensureReaderClosed(resp) 54 55 body := resp.body 56 responseText := bytes.NewBuffer(nil) 57 stream := io.TeeReader(body, responseText) 58 59 var res container.WaitResponse 60 if err := json.NewDecoder(stream).Decode(&res); err != nil { 61 // NOTE(nicks): The /wait API does not work well with HTTP proxies. 62 // At any time, the proxy could cut off the response stream. 63 // 64 // But because the HTTP status has already been written, the proxy's 65 // only option is to write a plaintext error message. 66 // 67 // If there's a JSON parsing error, read the real error message 68 // off the body and send it to the client. 69 _, _ = io.ReadAll(io.LimitReader(stream, containerWaitErrorMsgLimit)) 70 errC <- errors.New(responseText.String()) 71 return 72 } 73 74 resultC <- res 75 }() 76 77 return resultC, errC 78 } 79 80 // legacyContainerWait returns immediately and doesn't have an option to wait 81 // until the container is removed. 82 func (cli *Client) legacyContainerWait(ctx context.Context, containerID string) (<-chan container.WaitResponse, <-chan error) { 83 resultC := make(chan container.WaitResponse) 84 errC := make(chan error) 85 86 go func() { 87 resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil) 88 if err != nil { 89 errC <- err 90 return 91 } 92 defer ensureReaderClosed(resp) 93 94 var res container.WaitResponse 95 if err := json.NewDecoder(resp.body).Decode(&res); err != nil { 96 errC <- err 97 return 98 } 99 100 resultC <- res 101 }() 102 103 return resultC, errC 104 }