github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/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  }