github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/daemon/list.go (about)

     1  package daemon
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/Sirupsen/logrus"
    10  	"github.com/docker/docker/api/types"
    11  	"github.com/docker/docker/image"
    12  	"github.com/docker/docker/pkg/graphdb"
    13  	"github.com/docker/docker/pkg/nat"
    14  	"github.com/docker/docker/pkg/parsers/filters"
    15  )
    16  
    17  // iterationAction represents possible outcomes happening during the container iteration.
    18  type iterationAction int
    19  
    20  // containerReducer represents a reducer for a container.
    21  // Returns the object to serialize by the api.
    22  type containerReducer func(*Container, *listContext) (*types.Container, error)
    23  
    24  const (
    25  	// includeContainer is the action to include a container in the reducer.
    26  	includeContainer iterationAction = iota
    27  	// excludeContainer is the action to exclude a container in the reducer.
    28  	excludeContainer
    29  	// stopIteration is the action to stop iterating over the list of containers.
    30  	stopIteration
    31  )
    32  
    33  // errStopIteration makes the iterator to stop without returning an error.
    34  var errStopIteration = errors.New("container list iteration stopped")
    35  
    36  // List returns an array of all containers registered in the daemon.
    37  func (daemon *Daemon) List() []*Container {
    38  	return daemon.containers.List()
    39  }
    40  
    41  // ContainersConfig is the filtering specified by the user to iterate over containers.
    42  type ContainersConfig struct {
    43  	// if true show all containers, otherwise only running containers.
    44  	All bool
    45  	// show all containers created after this container id
    46  	Since string
    47  	// show all containers created before this container id
    48  	Before string
    49  	// number of containers to return at most
    50  	Limit int
    51  	// if true include the sizes of the containers
    52  	Size bool
    53  	// return only containers that match filters
    54  	Filters string
    55  }
    56  
    57  // listContext is the daemon generated filtering to iterate over containers.
    58  // This is created based on the user specification.
    59  type listContext struct {
    60  	// idx is the container iteration index for this context
    61  	idx int
    62  	// ancestorFilter tells whether it should check ancestors or not
    63  	ancestorFilter bool
    64  	// names is a list of container names to filter with
    65  	names map[string][]string
    66  	// images is a list of images to filter with
    67  	images map[string]bool
    68  	// filters is a collection of arguments to filter with, specified by the user
    69  	filters filters.Args
    70  	// exitAllowed is a list of exit codes allowed to filter with
    71  	exitAllowed []int
    72  	// beforeContainer is a filter to ignore containers that appear before the one given
    73  	beforeContainer *Container
    74  	// sinceContainer is a filter to stop the filtering when the iterator arrive to the given container
    75  	sinceContainer *Container
    76  	// ContainersConfig is the filters set by the user
    77  	*ContainersConfig
    78  }
    79  
    80  // Containers returns the list of containers to show given the user's filtering.
    81  func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container, error) {
    82  	return daemon.reduceContainers(config, daemon.transformContainer)
    83  }
    84  
    85  // reduceContainer parses the user filtering and generates the list of containers to return based on a reducer.
    86  func (daemon *Daemon) reduceContainers(config *ContainersConfig, reducer containerReducer) ([]*types.Container, error) {
    87  	containers := []*types.Container{}
    88  
    89  	ctx, err := daemon.foldFilter(config)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	for _, container := range daemon.List() {
    95  		t, err := daemon.reducePsContainer(container, ctx, reducer)
    96  		if err != nil {
    97  			if err != errStopIteration {
    98  				return nil, err
    99  			}
   100  			break
   101  		}
   102  		if t != nil {
   103  			containers = append(containers, t)
   104  			ctx.idx++
   105  		}
   106  	}
   107  	return containers, nil
   108  }
   109  
   110  // reducePsContainer is the basic representation for a container as expected by the ps command.
   111  func (daemon *Daemon) reducePsContainer(container *Container, ctx *listContext, reducer containerReducer) (*types.Container, error) {
   112  	container.Lock()
   113  	defer container.Unlock()
   114  
   115  	// filter containers to return
   116  	action := includeContainerInList(container, ctx)
   117  	switch action {
   118  	case excludeContainer:
   119  		return nil, nil
   120  	case stopIteration:
   121  		return nil, errStopIteration
   122  	}
   123  
   124  	// transform internal container struct into api structs
   125  	return reducer(container, ctx)
   126  }
   127  
   128  // foldFilter generates the container filter based in the user's filtering options.
   129  func (daemon *Daemon) foldFilter(config *ContainersConfig) (*listContext, error) {
   130  	psFilters, err := filters.FromParam(config.Filters)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	var filtExited []int
   136  	if i, ok := psFilters["exited"]; ok {
   137  		for _, value := range i {
   138  			code, err := strconv.Atoi(value)
   139  			if err != nil {
   140  				return nil, err
   141  			}
   142  			filtExited = append(filtExited, code)
   143  		}
   144  	}
   145  
   146  	if i, ok := psFilters["status"]; ok {
   147  		for _, value := range i {
   148  			if !isValidStateString(value) {
   149  				return nil, errors.New("Unrecognised filter value for status")
   150  			}
   151  			if value == "exited" || value == "created" {
   152  				config.All = true
   153  			}
   154  		}
   155  	}
   156  
   157  	imagesFilter := map[string]bool{}
   158  	var ancestorFilter bool
   159  	if ancestors, ok := psFilters["ancestor"]; ok {
   160  		ancestorFilter = true
   161  		byParents := daemon.Graph().ByParent()
   162  		// The idea is to walk the graph down the most "efficient" way.
   163  		for _, ancestor := range ancestors {
   164  			// First, get the imageId of the ancestor filter (yay)
   165  			image, err := daemon.Repositories().LookupImage(ancestor)
   166  			if err != nil {
   167  				logrus.Warnf("Error while looking up for image %v", ancestor)
   168  				continue
   169  			}
   170  			if imagesFilter[ancestor] {
   171  				// Already seen this ancestor, skip it
   172  				continue
   173  			}
   174  			// Then walk down the graph and put the imageIds in imagesFilter
   175  			populateImageFilterByParents(imagesFilter, image.ID, byParents)
   176  		}
   177  	}
   178  
   179  	names := map[string][]string{}
   180  	daemon.containerGraph().Walk("/", func(p string, e *graphdb.Entity) error {
   181  		names[e.ID()] = append(names[e.ID()], p)
   182  		return nil
   183  	}, 1)
   184  
   185  	var beforeCont, sinceCont *Container
   186  	if config.Before != "" {
   187  		beforeCont, err = daemon.Get(config.Before)
   188  		if err != nil {
   189  			return nil, err
   190  		}
   191  	}
   192  
   193  	if config.Since != "" {
   194  		sinceCont, err = daemon.Get(config.Since)
   195  		if err != nil {
   196  			return nil, err
   197  		}
   198  	}
   199  
   200  	return &listContext{
   201  		filters:          psFilters,
   202  		ancestorFilter:   ancestorFilter,
   203  		names:            names,
   204  		images:           imagesFilter,
   205  		exitAllowed:      filtExited,
   206  		beforeContainer:  beforeCont,
   207  		sinceContainer:   sinceCont,
   208  		ContainersConfig: config,
   209  	}, nil
   210  }
   211  
   212  // includeContainerInList decides whether a containers should be include in the output or not based in the filter.
   213  // It also decides if the iteration should be stopped or not.
   214  func includeContainerInList(container *Container, ctx *listContext) iterationAction {
   215  	// Do not include container if it's stopped and we're not filters
   216  	if !container.Running && !ctx.All && ctx.Limit <= 0 && ctx.beforeContainer == nil && ctx.sinceContainer == nil {
   217  		return excludeContainer
   218  	}
   219  
   220  	// Do not include container if the name doesn't match
   221  	if !ctx.filters.Match("name", container.Name) {
   222  		return excludeContainer
   223  	}
   224  
   225  	// Do not include container if the id doesn't match
   226  	if !ctx.filters.Match("id", container.ID) {
   227  		return excludeContainer
   228  	}
   229  
   230  	// Do not include container if any of the labels don't match
   231  	if !ctx.filters.MatchKVList("label", container.Config.Labels) {
   232  		return excludeContainer
   233  	}
   234  
   235  	// Do not include container if it's in the list before the filter container.
   236  	// Set the filter container to nil to include the rest of containers after this one.
   237  	if ctx.beforeContainer != nil {
   238  		if container.ID == ctx.beforeContainer.ID {
   239  			ctx.beforeContainer = nil
   240  		}
   241  		return excludeContainer
   242  	}
   243  
   244  	// Stop iteration when the index is over the limit
   245  	if ctx.Limit > 0 && ctx.idx == ctx.Limit {
   246  		return stopIteration
   247  	}
   248  
   249  	// Stop interation when the container arrives to the filter container
   250  	if ctx.sinceContainer != nil {
   251  		if container.ID == ctx.sinceContainer.ID {
   252  			return stopIteration
   253  		}
   254  	}
   255  
   256  	// Do not include container if its exit code is not in the filter
   257  	if len(ctx.exitAllowed) > 0 {
   258  		shouldSkip := true
   259  		for _, code := range ctx.exitAllowed {
   260  			if code == container.ExitCode && !container.Running {
   261  				shouldSkip = false
   262  				break
   263  			}
   264  		}
   265  		if shouldSkip {
   266  			return excludeContainer
   267  		}
   268  	}
   269  
   270  	// Do not include container if its status doesn't match the filter
   271  	if !ctx.filters.Match("status", container.State.StateString()) {
   272  		return excludeContainer
   273  	}
   274  
   275  	if ctx.ancestorFilter {
   276  		if len(ctx.images) == 0 {
   277  			return excludeContainer
   278  		}
   279  		if !ctx.images[container.ImageID] {
   280  			return excludeContainer
   281  		}
   282  	}
   283  
   284  	return includeContainer
   285  }
   286  
   287  // transformContainer generates the container type expected by the docker ps command.
   288  func (daemon *Daemon) transformContainer(container *Container, ctx *listContext) (*types.Container, error) {
   289  	newC := &types.Container{
   290  		ID:    container.ID,
   291  		Names: ctx.names[container.ID],
   292  	}
   293  
   294  	img, err := daemon.Repositories().LookupImage(container.Config.Image)
   295  	if err != nil {
   296  		// If the image can no longer be found by its original reference,
   297  		// it makes sense to show the ID instead of a stale reference.
   298  		newC.Image = container.ImageID
   299  	} else if container.ImageID == img.ID {
   300  		newC.Image = container.Config.Image
   301  	} else {
   302  		newC.Image = container.ImageID
   303  	}
   304  
   305  	if len(container.Args) > 0 {
   306  		args := []string{}
   307  		for _, arg := range container.Args {
   308  			if strings.Contains(arg, " ") {
   309  				args = append(args, fmt.Sprintf("'%s'", arg))
   310  			} else {
   311  				args = append(args, arg)
   312  			}
   313  		}
   314  		argsAsString := strings.Join(args, " ")
   315  
   316  		newC.Command = fmt.Sprintf("%s %s", container.Path, argsAsString)
   317  	} else {
   318  		newC.Command = container.Path
   319  	}
   320  	newC.Created = container.Created.Unix()
   321  	newC.Status = container.State.String()
   322  	newC.HostConfig.NetworkMode = string(container.hostConfig.NetworkMode)
   323  
   324  	newC.Ports = []types.Port{}
   325  	for port, bindings := range container.NetworkSettings.Ports {
   326  		p, err := nat.ParsePort(port.Port())
   327  		if err != nil {
   328  			return nil, err
   329  		}
   330  		if len(bindings) == 0 {
   331  			newC.Ports = append(newC.Ports, types.Port{
   332  				PrivatePort: p,
   333  				Type:        port.Proto(),
   334  			})
   335  			continue
   336  		}
   337  		for _, binding := range bindings {
   338  			h, err := nat.ParsePort(binding.HostPort)
   339  			if err != nil {
   340  				return nil, err
   341  			}
   342  			newC.Ports = append(newC.Ports, types.Port{
   343  				PrivatePort: p,
   344  				PublicPort:  h,
   345  				Type:        port.Proto(),
   346  				IP:          binding.HostIP,
   347  			})
   348  		}
   349  	}
   350  
   351  	if ctx.Size {
   352  		sizeRw, sizeRootFs := container.getSize()
   353  		newC.SizeRw = sizeRw
   354  		newC.SizeRootFs = sizeRootFs
   355  	}
   356  	newC.Labels = container.Config.Labels
   357  
   358  	return newC, nil
   359  }
   360  
   361  // Volumes lists known volumes, using the filter to restrict the range
   362  // of volumes returned.
   363  func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, error) {
   364  	var volumesOut []*types.Volume
   365  	volFilters, err := filters.FromParam(filter)
   366  	if err != nil {
   367  		return nil, err
   368  	}
   369  
   370  	filterUsed := false
   371  	if i, ok := volFilters["dangling"]; ok {
   372  		if len(i) > 1 {
   373  			return nil, fmt.Errorf("Conflict: cannot use more than 1 value for `dangling` filter")
   374  		}
   375  
   376  		filterValue := i[0]
   377  		if strings.ToLower(filterValue) == "true" || filterValue == "1" {
   378  			filterUsed = true
   379  		}
   380  	}
   381  
   382  	volumes := daemon.volumes.List()
   383  	for _, v := range volumes {
   384  		if filterUsed && daemon.volumes.Count(v) > 0 {
   385  			continue
   386  		}
   387  		volumesOut = append(volumesOut, volumeToAPIType(v))
   388  	}
   389  	return volumesOut, nil
   390  }
   391  
   392  func populateImageFilterByParents(ancestorMap map[string]bool, imageID string, byParents map[string][]*image.Image) {
   393  	if !ancestorMap[imageID] {
   394  		if images, ok := byParents[imageID]; ok {
   395  			for _, image := range images {
   396  				populateImageFilterByParents(ancestorMap, image.ID, byParents)
   397  			}
   398  		}
   399  		ancestorMap[imageID] = true
   400  	}
   401  }