github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/command/container/utils.go (about) 1 package container 2 3 import ( 4 "context" 5 "strconv" 6 7 "github.com/docker/cli/cli/command" 8 "github.com/docker/docker/api/types" 9 "github.com/docker/docker/api/types/container" 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/sirupsen/logrus" 14 ) 15 16 func waitExitOrRemoved(ctx context.Context, dockerCli command.Cli, 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 // Older versions used the Events API, and even older versions did not 23 // support server-side removal. This legacyWaitExitOrRemoved method 24 // preserves that old behavior and any issues it may have. 25 if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") { 26 return legacyWaitExitOrRemoved(ctx, dockerCli, containerID, waitRemove) 27 } 28 29 condition := container.WaitConditionNextExit 30 if waitRemove { 31 condition = container.WaitConditionRemoved 32 } 33 34 resultC, errC := dockerCli.Client().ContainerWait(ctx, containerID, condition) 35 36 statusC := make(chan int) 37 go func() { 38 select { 39 case result := <-resultC: 40 if result.Error != nil { 41 logrus.Errorf("Error waiting for container: %v", result.Error.Message) 42 statusC <- 125 43 } else { 44 statusC <- int(result.StatusCode) 45 } 46 case err := <-errC: 47 logrus.Errorf("error waiting for container: %v", err) 48 statusC <- 125 49 } 50 }() 51 52 return statusC 53 } 54 55 func legacyWaitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID string, waitRemove bool) <-chan int { 56 var removeErr error 57 statusChan := make(chan int) 58 exitCode := 125 59 60 // Get events via Events API 61 f := filters.NewArgs() 62 f.Add("type", "container") 63 f.Add("container", containerID) 64 options := types.EventsOptions{ 65 Filters: f, 66 } 67 eventCtx, cancel := context.WithCancel(ctx) 68 eventq, errq := dockerCli.Client().Events(eventCtx, options) 69 70 eventProcessor := func(e events.Message) bool { 71 stopProcessing := false 72 switch e.Status { 73 case "die": 74 if v, ok := e.Actor.Attributes["exitCode"]; ok { 75 code, cerr := strconv.Atoi(v) 76 if cerr != nil { 77 logrus.Errorf("failed to convert exitcode '%q' to int: %v", v, cerr) 78 } else { 79 exitCode = code 80 } 81 } 82 if !waitRemove { 83 stopProcessing = true 84 } else { 85 // If we are talking to an older daemon, `AutoRemove` is not supported. 86 // We need to fall back to the old behavior, which is client-side removal 87 if versions.LessThan(dockerCli.Client().ClientVersion(), "1.25") { 88 go func() { 89 removeErr = dockerCli.Client().ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{RemoveVolumes: true}) 90 if removeErr != nil { 91 logrus.Errorf("error removing container: %v", removeErr) 92 cancel() // cancel the event Q 93 } 94 }() 95 } 96 } 97 case "detach": 98 exitCode = 0 99 stopProcessing = true 100 case "destroy": 101 stopProcessing = true 102 } 103 return stopProcessing 104 } 105 106 go func() { 107 defer func() { 108 statusChan <- exitCode // must always send an exit code or the caller will block 109 cancel() 110 }() 111 112 for { 113 select { 114 case <-eventCtx.Done(): 115 if removeErr != nil { 116 return 117 } 118 case evt := <-eventq: 119 if eventProcessor(evt) { 120 return 121 } 122 case err := <-errq: 123 logrus.Errorf("error getting events from daemon: %v", err) 124 return 125 } 126 } 127 }() 128 129 return statusChan 130 } 131 132 func parallelOperation(ctx context.Context, containers []string, op func(ctx context.Context, container string) error) chan error { 133 if len(containers) == 0 { 134 return nil 135 } 136 const defaultParallel int = 50 137 sem := make(chan struct{}, defaultParallel) 138 errChan := make(chan error) 139 140 // make sure result is printed in correct order 141 output := map[string]chan error{} 142 for _, c := range containers { 143 output[c] = make(chan error, 1) 144 } 145 go func() { 146 for _, c := range containers { 147 err := <-output[c] 148 errChan <- err 149 } 150 }() 151 152 go func() { 153 for _, c := range containers { 154 sem <- struct{}{} // Wait for active queue sem to drain. 155 go func(container string) { 156 output[container] <- op(ctx, container) 157 <-sem 158 }(c) 159 } 160 }() 161 return errChan 162 }