github.com/kunnos/engine@v1.13.1/cli/command/container/utils.go (about) 1 package container 2 3 import ( 4 "strconv" 5 6 "golang.org/x/net/context" 7 8 "github.com/sirupsen/logrus" 9 "github.com/docker/docker/api/types" 10 "github.com/docker/docker/api/types/events" 11 "github.com/docker/docker/api/types/filters" 12 "github.com/docker/docker/api/types/versions" 13 "github.com/docker/docker/cli/command" 14 clientapi "github.com/docker/docker/client" 15 ) 16 17 func waitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, containerID string, waitRemove bool) chan int { 18 if len(containerID) == 0 { 19 // containerID can never be empty 20 panic("Internal Error: waitExitOrRemoved needs a containerID as parameter") 21 } 22 23 var removeErr error 24 statusChan := make(chan int) 25 exitCode := 125 26 27 // Get events via Events API 28 f := filters.NewArgs() 29 f.Add("type", "container") 30 f.Add("container", containerID) 31 options := types.EventsOptions{ 32 Filters: f, 33 } 34 eventCtx, cancel := context.WithCancel(ctx) 35 eventq, errq := dockerCli.Client().Events(eventCtx, options) 36 37 eventProcessor := func(e events.Message) bool { 38 stopProcessing := false 39 switch e.Status { 40 case "die": 41 if v, ok := e.Actor.Attributes["exitCode"]; ok { 42 code, cerr := strconv.Atoi(v) 43 if cerr != nil { 44 logrus.Errorf("failed to convert exitcode '%q' to int: %v", v, cerr) 45 } else { 46 exitCode = code 47 } 48 } 49 if !waitRemove { 50 stopProcessing = true 51 } else { 52 // If we are talking to an older daemon, `AutoRemove` is not supported. 53 // We need to fall back to the old behavior, which is client-side removal 54 if versions.LessThan(dockerCli.Client().ClientVersion(), "1.25") { 55 go func() { 56 removeErr = dockerCli.Client().ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{RemoveVolumes: true}) 57 if removeErr != nil { 58 logrus.Errorf("error removing container: %v", removeErr) 59 cancel() // cancel the event Q 60 } 61 }() 62 } 63 } 64 case "detach": 65 exitCode = 0 66 stopProcessing = true 67 case "destroy": 68 stopProcessing = true 69 } 70 return stopProcessing 71 } 72 73 go func() { 74 defer func() { 75 statusChan <- exitCode // must always send an exit code or the caller will block 76 cancel() 77 }() 78 79 for { 80 select { 81 case <-eventCtx.Done(): 82 if removeErr != nil { 83 return 84 } 85 case evt := <-eventq: 86 if eventProcessor(evt) { 87 return 88 } 89 case err := <-errq: 90 logrus.Errorf("error getting events from daemon: %v", err) 91 return 92 } 93 } 94 }() 95 96 return statusChan 97 } 98 99 // getExitCode performs an inspect on the container. It returns 100 // the running state and the exit code. 101 func getExitCode(ctx context.Context, dockerCli *command.DockerCli, containerID string) (bool, int, error) { 102 c, err := dockerCli.Client().ContainerInspect(ctx, containerID) 103 if err != nil { 104 // If we can't connect, then the daemon probably died. 105 if !clientapi.IsErrConnectionFailed(err) { 106 return false, -1, err 107 } 108 return false, -1, nil 109 } 110 return c.State.Running, c.State.ExitCode, nil 111 } 112 113 func parallelOperation(ctx context.Context, containers []string, op func(ctx context.Context, container string) error) chan error { 114 if len(containers) == 0 { 115 return nil 116 } 117 const defaultParallel int = 50 118 sem := make(chan struct{}, defaultParallel) 119 errChan := make(chan error) 120 121 // make sure result is printed in correct order 122 output := map[string]chan error{} 123 for _, c := range containers { 124 output[c] = make(chan error, 1) 125 } 126 go func() { 127 for _, c := range containers { 128 err := <-output[c] 129 errChan <- err 130 } 131 }() 132 133 go func() { 134 for _, c := range containers { 135 sem <- struct{}{} // Wait for active queue sem to drain. 136 go func(container string) { 137 output[container] <- op(ctx, container) 138 <-sem 139 }(c) 140 } 141 }() 142 return errChan 143 }