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