github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/cli/command/container/utils.go (about)

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