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  }