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