github.com/thajeztah/cli@v0.0.0-20240223162942-dc6bfac81a8b/cli/command/container/utils.go (about) 1 package container 2 3 import ( 4 "context" 5 "strconv" 6 7 "github.com/docker/docker/api/types" 8 "github.com/docker/docker/api/types/container" 9 "github.com/docker/docker/api/types/events" 10 "github.com/docker/docker/api/types/filters" 11 "github.com/docker/docker/api/types/versions" 12 "github.com/docker/docker/client" 13 "github.com/sirupsen/logrus" 14 ) 15 16 func waitExitOrRemoved(ctx context.Context, apiClient client.APIClient, 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(apiClient.ClientVersion(), "1.30") { 26 return legacyWaitExitOrRemoved(ctx, apiClient, containerID, waitRemove) 27 } 28 29 condition := container.WaitConditionNextExit 30 if waitRemove { 31 condition = container.WaitConditionRemoved 32 } 33 34 resultC, errC := apiClient.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, apiClient client.APIClient, 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 := apiClient.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 if versions.LessThan(apiClient.ClientVersion(), "1.25") { 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 go func() { 88 removeErr = apiClient.ContainerRemove(ctx, containerID, container.RemoveOptions{RemoveVolumes: true}) 89 if removeErr != nil { 90 logrus.Errorf("error removing container: %v", removeErr) 91 cancel() // cancel the event Q 92 } 93 }() 94 } 95 case "detach": 96 exitCode = 0 97 stopProcessing = true 98 case "destroy": 99 stopProcessing = true 100 } 101 return stopProcessing 102 } 103 104 go func() { 105 defer func() { 106 statusChan <- exitCode // must always send an exit code or the caller will block 107 cancel() 108 }() 109 110 for { 111 select { 112 case <-eventCtx.Done(): 113 if removeErr != nil { 114 return 115 } 116 case evt := <-eventq: 117 if eventProcessor(evt) { 118 return 119 } 120 case err := <-errq: 121 logrus.Errorf("error getting events from daemon: %v", err) 122 return 123 } 124 } 125 }() 126 127 return statusChan 128 } 129 130 func parallelOperation(ctx context.Context, containers []string, op func(ctx context.Context, containerID string) error) chan error { 131 if len(containers) == 0 { 132 return nil 133 } 134 const defaultParallel int = 50 135 sem := make(chan struct{}, defaultParallel) 136 errChan := make(chan error) 137 138 // make sure result is printed in correct order 139 output := map[string]chan error{} 140 for _, c := range containers { 141 output[c] = make(chan error, 1) 142 } 143 go func() { 144 for _, c := range containers { 145 err := <-output[c] 146 errChan <- err 147 } 148 }() 149 150 go func() { 151 for _, c := range containers { 152 sem <- struct{}{} // Wait for active queue sem to drain. 153 go func(container string) { 154 output[container] <- op(ctx, container) 155 <-sem 156 }(c) 157 } 158 }() 159 return errChan 160 }