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