github.com/olljanat/moby@v1.13.1/cli/command/container/utils.go (about)

     1  package container
     2  
     3  import (
     4  	"strconv"
     5  
     6  	"golang.org/x/net/context"
     7  
     8  	"github.com/Sirupsen/logrus"
     9  	"github.com/docker/docker/api/types"
    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/cli/command"
    14  	clientapi "github.com/docker/docker/client"
    15  )
    16  
    17  func waitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, 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  	var removeErr error
    24  	statusChan := make(chan int)
    25  	exitCode := 125
    26  
    27  	// Get events via Events API
    28  	f := filters.NewArgs()
    29  	f.Add("type", "container")
    30  	f.Add("container", containerID)
    31  	options := types.EventsOptions{
    32  		Filters: f,
    33  	}
    34  	eventCtx, cancel := context.WithCancel(ctx)
    35  	eventq, errq := dockerCli.Client().Events(eventCtx, options)
    36  
    37  	eventProcessor := func(e events.Message) bool {
    38  		stopProcessing := false
    39  		switch e.Status {
    40  		case "die":
    41  			if v, ok := e.Actor.Attributes["exitCode"]; ok {
    42  				code, cerr := strconv.Atoi(v)
    43  				if cerr != nil {
    44  					logrus.Errorf("failed to convert exitcode '%q' to int: %v", v, cerr)
    45  				} else {
    46  					exitCode = code
    47  				}
    48  			}
    49  			if !waitRemove {
    50  				stopProcessing = true
    51  			} else {
    52  				// If we are talking to an older daemon, `AutoRemove` is not supported.
    53  				// We need to fall back to the old behavior, which is client-side removal
    54  				if versions.LessThan(dockerCli.Client().ClientVersion(), "1.25") {
    55  					go func() {
    56  						removeErr = dockerCli.Client().ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{RemoveVolumes: true})
    57  						if removeErr != nil {
    58  							logrus.Errorf("error removing container: %v", removeErr)
    59  							cancel() // cancel the event Q
    60  						}
    61  					}()
    62  				}
    63  			}
    64  		case "detach":
    65  			exitCode = 0
    66  			stopProcessing = true
    67  		case "destroy":
    68  			stopProcessing = true
    69  		}
    70  		return stopProcessing
    71  	}
    72  
    73  	go func() {
    74  		defer func() {
    75  			statusChan <- exitCode // must always send an exit code or the caller will block
    76  			cancel()
    77  		}()
    78  
    79  		for {
    80  			select {
    81  			case <-eventCtx.Done():
    82  				if removeErr != nil {
    83  					return
    84  				}
    85  			case evt := <-eventq:
    86  				if eventProcessor(evt) {
    87  					return
    88  				}
    89  			case err := <-errq:
    90  				logrus.Errorf("error getting events from daemon: %v", err)
    91  				return
    92  			}
    93  		}
    94  	}()
    95  
    96  	return statusChan
    97  }
    98  
    99  // getExitCode performs an inspect on the container. It returns
   100  // the running state and the exit code.
   101  func getExitCode(ctx context.Context, dockerCli *command.DockerCli, containerID string) (bool, int, error) {
   102  	c, err := dockerCli.Client().ContainerInspect(ctx, containerID)
   103  	if err != nil {
   104  		// If we can't connect, then the daemon probably died.
   105  		if !clientapi.IsErrConnectionFailed(err) {
   106  			return false, -1, err
   107  		}
   108  		return false, -1, nil
   109  	}
   110  	return c.State.Running, c.State.ExitCode, nil
   111  }
   112  
   113  func parallelOperation(ctx context.Context, containers []string, op func(ctx context.Context, container string) error) chan error {
   114  	if len(containers) == 0 {
   115  		return nil
   116  	}
   117  	const defaultParallel int = 50
   118  	sem := make(chan struct{}, defaultParallel)
   119  	errChan := make(chan error)
   120  
   121  	// make sure result is printed in correct order
   122  	output := map[string]chan error{}
   123  	for _, c := range containers {
   124  		output[c] = make(chan error, 1)
   125  	}
   126  	go func() {
   127  		for _, c := range containers {
   128  			err := <-output[c]
   129  			errChan <- err
   130  		}
   131  	}()
   132  
   133  	go func() {
   134  		for _, c := range containers {
   135  			sem <- struct{}{} // Wait for active queue sem to drain.
   136  			go func(container string) {
   137  				output[container] <- op(ctx, container)
   138  				<-sem
   139  			}(c)
   140  		}
   141  	}()
   142  	return errChan
   143  }