github.com/rhatdan/docker@v0.7.7-0.20180119204836-47a0dcbcd20a/daemon/list.go (about)

     1  package daemon
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/docker/docker/api/types"
    10  	"github.com/docker/docker/api/types/filters"
    11  	"github.com/docker/docker/container"
    12  	"github.com/docker/docker/errdefs"
    13  	"github.com/docker/docker/image"
    14  	"github.com/docker/docker/volume"
    15  	"github.com/docker/go-connections/nat"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  var acceptedVolumeFilterTags = map[string]bool{
    21  	"dangling": true,
    22  	"name":     true,
    23  	"driver":   true,
    24  	"label":    true,
    25  }
    26  
    27  var acceptedPsFilterTags = map[string]bool{
    28  	"ancestor":  true,
    29  	"before":    true,
    30  	"exited":    true,
    31  	"id":        true,
    32  	"isolation": true,
    33  	"label":     true,
    34  	"name":      true,
    35  	"status":    true,
    36  	"health":    true,
    37  	"since":     true,
    38  	"volume":    true,
    39  	"network":   true,
    40  	"is-task":   true,
    41  	"publish":   true,
    42  	"expose":    true,
    43  }
    44  
    45  // iterationAction represents possible outcomes happening during the container iteration.
    46  type iterationAction int
    47  
    48  // containerReducer represents a reducer for a container.
    49  // Returns the object to serialize by the api.
    50  type containerReducer func(*container.Snapshot, *listContext) (*types.Container, error)
    51  
    52  const (
    53  	// includeContainer is the action to include a container in the reducer.
    54  	includeContainer iterationAction = iota
    55  	// excludeContainer is the action to exclude a container in the reducer.
    56  	excludeContainer
    57  	// stopIteration is the action to stop iterating over the list of containers.
    58  	stopIteration
    59  )
    60  
    61  // errStopIteration makes the iterator to stop without returning an error.
    62  var errStopIteration = errors.New("container list iteration stopped")
    63  
    64  // List returns an array of all containers registered in the daemon.
    65  func (daemon *Daemon) List() []*container.Container {
    66  	return daemon.containers.List()
    67  }
    68  
    69  // listContext is the daemon generated filtering to iterate over containers.
    70  // This is created based on the user specification from types.ContainerListOptions.
    71  type listContext struct {
    72  	// idx is the container iteration index for this context
    73  	idx int
    74  	// ancestorFilter tells whether it should check ancestors or not
    75  	ancestorFilter bool
    76  	// names is a list of container names to filter with
    77  	names map[string][]string
    78  	// images is a list of images to filter with
    79  	images map[image.ID]bool
    80  	// filters is a collection of arguments to filter with, specified by the user
    81  	filters filters.Args
    82  	// exitAllowed is a list of exit codes allowed to filter with
    83  	exitAllowed []int
    84  
    85  	// beforeFilter is a filter to ignore containers that appear before the one given
    86  	beforeFilter *container.Snapshot
    87  	// sinceFilter is a filter to stop the filtering when the iterator arrive to the given container
    88  	sinceFilter *container.Snapshot
    89  
    90  	// taskFilter tells if we should filter based on wether a container is part of a task
    91  	taskFilter bool
    92  	// isTask tells us if the we should filter container that are a task (true) or not (false)
    93  	isTask bool
    94  
    95  	// publish is a list of published ports to filter with
    96  	publish map[nat.Port]bool
    97  	// expose is a list of exposed ports to filter with
    98  	expose map[nat.Port]bool
    99  
   100  	// ContainerListOptions is the filters set by the user
   101  	*types.ContainerListOptions
   102  }
   103  
   104  // byCreatedDescending is a temporary type used to sort a list of containers by creation time.
   105  type byCreatedDescending []container.Snapshot
   106  
   107  func (r byCreatedDescending) Len() int      { return len(r) }
   108  func (r byCreatedDescending) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
   109  func (r byCreatedDescending) Less(i, j int) bool {
   110  	return r[j].CreatedAt.UnixNano() < r[i].CreatedAt.UnixNano()
   111  }
   112  
   113  // Containers returns the list of containers to show given the user's filtering.
   114  func (daemon *Daemon) Containers(config *types.ContainerListOptions) ([]*types.Container, error) {
   115  	return daemon.reduceContainers(config, daemon.refreshImage)
   116  }
   117  
   118  func (daemon *Daemon) filterByNameIDMatches(view container.View, ctx *listContext) ([]container.Snapshot, error) {
   119  	idSearch := false
   120  	names := ctx.filters.Get("name")
   121  	ids := ctx.filters.Get("id")
   122  	if len(names)+len(ids) == 0 {
   123  		// if name or ID filters are not in use, return to
   124  		// standard behavior of walking the entire container
   125  		// list from the daemon's in-memory store
   126  		all, err := view.All()
   127  		sort.Sort(byCreatedDescending(all))
   128  		return all, err
   129  	}
   130  
   131  	// idSearch will determine if we limit name matching to the IDs
   132  	// matched from any IDs which were specified as filters
   133  	if len(ids) > 0 {
   134  		idSearch = true
   135  	}
   136  
   137  	matches := make(map[string]bool)
   138  	// find ID matches; errors represent "not found" and can be ignored
   139  	for _, id := range ids {
   140  		if fullID, err := daemon.idIndex.Get(id); err == nil {
   141  			matches[fullID] = true
   142  		}
   143  	}
   144  
   145  	// look for name matches; if ID filtering was used, then limit the
   146  	// search space to the matches map only; errors represent "not found"
   147  	// and can be ignored
   148  	if len(names) > 0 {
   149  		for id, idNames := range ctx.names {
   150  			// if ID filters were used and no matches on that ID were
   151  			// found, continue to next ID in the list
   152  			if idSearch && !matches[id] {
   153  				continue
   154  			}
   155  			for _, eachName := range idNames {
   156  				if ctx.filters.Match("name", eachName) {
   157  					matches[id] = true
   158  				}
   159  			}
   160  		}
   161  	}
   162  
   163  	cntrs := make([]container.Snapshot, 0, len(matches))
   164  	for id := range matches {
   165  		c, err := view.Get(id)
   166  		switch err.(type) {
   167  		case nil:
   168  			cntrs = append(cntrs, *c)
   169  		case container.NoSuchContainerError:
   170  			// ignore error
   171  		default:
   172  			return nil, err
   173  		}
   174  	}
   175  
   176  	// Restore sort-order after filtering
   177  	// Created gives us nanosec resolution for sorting
   178  	sort.Sort(byCreatedDescending(cntrs))
   179  
   180  	return cntrs, nil
   181  }
   182  
   183  // reduceContainers parses the user's filtering options and generates the list of containers to return based on a reducer.
   184  func (daemon *Daemon) reduceContainers(config *types.ContainerListOptions, reducer containerReducer) ([]*types.Container, error) {
   185  	var (
   186  		view       = daemon.containersReplica.Snapshot()
   187  		containers = []*types.Container{}
   188  	)
   189  
   190  	ctx, err := daemon.foldFilter(view, config)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	// fastpath to only look at a subset of containers if specific name
   196  	// or ID matches were provided by the user--otherwise we potentially
   197  	// end up querying many more containers than intended
   198  	containerList, err := daemon.filterByNameIDMatches(view, ctx)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	for i := range containerList {
   204  		t, err := daemon.reducePsContainer(&containerList[i], ctx, reducer)
   205  		if err != nil {
   206  			if err != errStopIteration {
   207  				return nil, err
   208  			}
   209  			break
   210  		}
   211  		if t != nil {
   212  			containers = append(containers, t)
   213  			ctx.idx++
   214  		}
   215  	}
   216  
   217  	return containers, nil
   218  }
   219  
   220  // reducePsContainer is the basic representation for a container as expected by the ps command.
   221  func (daemon *Daemon) reducePsContainer(container *container.Snapshot, ctx *listContext, reducer containerReducer) (*types.Container, error) {
   222  	// filter containers to return
   223  	switch includeContainerInList(container, ctx) {
   224  	case excludeContainer:
   225  		return nil, nil
   226  	case stopIteration:
   227  		return nil, errStopIteration
   228  	}
   229  
   230  	// transform internal container struct into api structs
   231  	newC, err := reducer(container, ctx)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	// release lock because size calculation is slow
   237  	if ctx.Size {
   238  		sizeRw, sizeRootFs := daemon.getSize(newC.ID)
   239  		newC.SizeRw = sizeRw
   240  		newC.SizeRootFs = sizeRootFs
   241  	}
   242  	return newC, nil
   243  }
   244  
   245  // foldFilter generates the container filter based on the user's filtering options.
   246  func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerListOptions) (*listContext, error) {
   247  	psFilters := config.Filters
   248  
   249  	if err := psFilters.Validate(acceptedPsFilterTags); err != nil {
   250  		return nil, err
   251  	}
   252  
   253  	var filtExited []int
   254  
   255  	err := psFilters.WalkValues("exited", func(value string) error {
   256  		code, err := strconv.Atoi(value)
   257  		if err != nil {
   258  			return err
   259  		}
   260  		filtExited = append(filtExited, code)
   261  		return nil
   262  	})
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  
   267  	err = psFilters.WalkValues("status", func(value string) error {
   268  		if !container.IsValidStateString(value) {
   269  			return invalidFilter{"status", value}
   270  		}
   271  
   272  		config.All = true
   273  		return nil
   274  	})
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  
   279  	var taskFilter, isTask bool
   280  	if psFilters.Contains("is-task") {
   281  		if psFilters.ExactMatch("is-task", "true") {
   282  			taskFilter = true
   283  			isTask = true
   284  		} else if psFilters.ExactMatch("is-task", "false") {
   285  			taskFilter = true
   286  			isTask = false
   287  		} else {
   288  			return nil, invalidFilter{"is-task", psFilters.Get("is-task")}
   289  		}
   290  	}
   291  
   292  	err = psFilters.WalkValues("health", func(value string) error {
   293  		if !container.IsValidHealthString(value) {
   294  			return errdefs.InvalidParameter(errors.Errorf("Unrecognised filter value for health: %s", value))
   295  		}
   296  
   297  		return nil
   298  	})
   299  	if err != nil {
   300  		return nil, err
   301  	}
   302  
   303  	var beforeContFilter, sinceContFilter *container.Snapshot
   304  
   305  	err = psFilters.WalkValues("before", func(value string) error {
   306  		beforeContFilter, err = view.Get(value)
   307  		return err
   308  	})
   309  	if err != nil {
   310  		return nil, err
   311  	}
   312  
   313  	err = psFilters.WalkValues("since", func(value string) error {
   314  		sinceContFilter, err = view.Get(value)
   315  		return err
   316  	})
   317  	if err != nil {
   318  		return nil, err
   319  	}
   320  
   321  	imagesFilter := map[image.ID]bool{}
   322  	var ancestorFilter bool
   323  	if psFilters.Contains("ancestor") {
   324  		ancestorFilter = true
   325  		psFilters.WalkValues("ancestor", func(ancestor string) error {
   326  			id, _, err := daemon.GetImageIDAndOS(ancestor)
   327  			if err != nil {
   328  				logrus.Warnf("Error while looking up for image %v", ancestor)
   329  				return nil
   330  			}
   331  			if imagesFilter[id] {
   332  				// Already seen this ancestor, skip it
   333  				return nil
   334  			}
   335  			// Then walk down the graph and put the imageIds in imagesFilter
   336  			populateImageFilterByParents(imagesFilter, id, daemon.imageStore.Children)
   337  			return nil
   338  		})
   339  	}
   340  
   341  	publishFilter := map[nat.Port]bool{}
   342  	err = psFilters.WalkValues("publish", portOp("publish", publishFilter))
   343  	if err != nil {
   344  		return nil, err
   345  	}
   346  
   347  	exposeFilter := map[nat.Port]bool{}
   348  	err = psFilters.WalkValues("expose", portOp("expose", exposeFilter))
   349  	if err != nil {
   350  		return nil, err
   351  	}
   352  
   353  	return &listContext{
   354  		filters:              psFilters,
   355  		ancestorFilter:       ancestorFilter,
   356  		images:               imagesFilter,
   357  		exitAllowed:          filtExited,
   358  		beforeFilter:         beforeContFilter,
   359  		sinceFilter:          sinceContFilter,
   360  		taskFilter:           taskFilter,
   361  		isTask:               isTask,
   362  		publish:              publishFilter,
   363  		expose:               exposeFilter,
   364  		ContainerListOptions: config,
   365  		names:                view.GetAllNames(),
   366  	}, nil
   367  }
   368  func portOp(key string, filter map[nat.Port]bool) func(value string) error {
   369  	return func(value string) error {
   370  		if strings.Contains(value, ":") {
   371  			return fmt.Errorf("filter for '%s' should not contain ':': %s", key, value)
   372  		}
   373  		//support two formats, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
   374  		proto, port := nat.SplitProtoPort(value)
   375  		start, end, err := nat.ParsePortRange(port)
   376  		if err != nil {
   377  			return fmt.Errorf("error while looking up for %s %s: %s", key, value, err)
   378  		}
   379  		for i := start; i <= end; i++ {
   380  			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
   381  			if err != nil {
   382  				return fmt.Errorf("error while looking up for %s %s: %s", key, value, err)
   383  			}
   384  			filter[p] = true
   385  		}
   386  		return nil
   387  	}
   388  }
   389  
   390  // includeContainerInList decides whether a container should be included in the output or not based in the filter.
   391  // It also decides if the iteration should be stopped or not.
   392  func includeContainerInList(container *container.Snapshot, ctx *listContext) iterationAction {
   393  	// Do not include container if it's in the list before the filter container.
   394  	// Set the filter container to nil to include the rest of containers after this one.
   395  	if ctx.beforeFilter != nil {
   396  		if container.ID == ctx.beforeFilter.ID {
   397  			ctx.beforeFilter = nil
   398  		}
   399  		return excludeContainer
   400  	}
   401  
   402  	// Stop iteration when the container arrives to the filter container
   403  	if ctx.sinceFilter != nil {
   404  		if container.ID == ctx.sinceFilter.ID {
   405  			return stopIteration
   406  		}
   407  	}
   408  
   409  	// Do not include container if it's stopped and we're not filters
   410  	if !container.Running && !ctx.All && ctx.Limit <= 0 {
   411  		return excludeContainer
   412  	}
   413  
   414  	// Do not include container if the name doesn't match
   415  	if !ctx.filters.Match("name", container.Name) {
   416  		return excludeContainer
   417  	}
   418  
   419  	// Do not include container if the id doesn't match
   420  	if !ctx.filters.Match("id", container.ID) {
   421  		return excludeContainer
   422  	}
   423  
   424  	if ctx.taskFilter {
   425  		if ctx.isTask != container.Managed {
   426  			return excludeContainer
   427  		}
   428  	}
   429  
   430  	// Do not include container if any of the labels don't match
   431  	if !ctx.filters.MatchKVList("label", container.Labels) {
   432  		return excludeContainer
   433  	}
   434  
   435  	// Do not include container if isolation doesn't match
   436  	if excludeContainer == excludeByIsolation(container, ctx) {
   437  		return excludeContainer
   438  	}
   439  
   440  	// Stop iteration when the index is over the limit
   441  	if ctx.Limit > 0 && ctx.idx == ctx.Limit {
   442  		return stopIteration
   443  	}
   444  
   445  	// Do not include container if its exit code is not in the filter
   446  	if len(ctx.exitAllowed) > 0 {
   447  		shouldSkip := true
   448  		for _, code := range ctx.exitAllowed {
   449  			if code == container.ExitCode && !container.Running && !container.StartedAt.IsZero() {
   450  				shouldSkip = false
   451  				break
   452  			}
   453  		}
   454  		if shouldSkip {
   455  			return excludeContainer
   456  		}
   457  	}
   458  
   459  	// Do not include container if its status doesn't match the filter
   460  	if !ctx.filters.Match("status", container.State) {
   461  		return excludeContainer
   462  	}
   463  
   464  	// Do not include container if its health doesn't match the filter
   465  	if !ctx.filters.ExactMatch("health", container.Health) {
   466  		return excludeContainer
   467  	}
   468  
   469  	if ctx.filters.Contains("volume") {
   470  		volumesByName := make(map[string]types.MountPoint)
   471  		for _, m := range container.Mounts {
   472  			if m.Name != "" {
   473  				volumesByName[m.Name] = m
   474  			} else {
   475  				volumesByName[m.Source] = m
   476  			}
   477  		}
   478  		volumesByDestination := make(map[string]types.MountPoint)
   479  		for _, m := range container.Mounts {
   480  			if m.Destination != "" {
   481  				volumesByDestination[m.Destination] = m
   482  			}
   483  		}
   484  
   485  		volumeExist := fmt.Errorf("volume mounted in container")
   486  		err := ctx.filters.WalkValues("volume", func(value string) error {
   487  			if _, exist := volumesByDestination[value]; exist {
   488  				return volumeExist
   489  			}
   490  			if _, exist := volumesByName[value]; exist {
   491  				return volumeExist
   492  			}
   493  			return nil
   494  		})
   495  		if err != volumeExist {
   496  			return excludeContainer
   497  		}
   498  	}
   499  
   500  	if ctx.ancestorFilter {
   501  		if len(ctx.images) == 0 {
   502  			return excludeContainer
   503  		}
   504  		if !ctx.images[image.ID(container.ImageID)] {
   505  			return excludeContainer
   506  		}
   507  	}
   508  
   509  	var (
   510  		networkExist = errors.New("container part of network")
   511  		noNetworks   = errors.New("container is not part of any networks")
   512  	)
   513  	if ctx.filters.Contains("network") {
   514  		err := ctx.filters.WalkValues("network", func(value string) error {
   515  			if container.NetworkSettings == nil {
   516  				return noNetworks
   517  			}
   518  			if _, ok := container.NetworkSettings.Networks[value]; ok {
   519  				return networkExist
   520  			}
   521  			for _, nw := range container.NetworkSettings.Networks {
   522  				if nw == nil {
   523  					continue
   524  				}
   525  				if strings.HasPrefix(nw.NetworkID, value) {
   526  					return networkExist
   527  				}
   528  			}
   529  			return nil
   530  		})
   531  		if err != networkExist {
   532  			return excludeContainer
   533  		}
   534  	}
   535  
   536  	if len(ctx.publish) > 0 {
   537  		shouldSkip := true
   538  		for port := range ctx.publish {
   539  			if _, ok := container.PortBindings[port]; ok {
   540  				shouldSkip = false
   541  				break
   542  			}
   543  		}
   544  		if shouldSkip {
   545  			return excludeContainer
   546  		}
   547  	}
   548  
   549  	if len(ctx.expose) > 0 {
   550  		shouldSkip := true
   551  		for port := range ctx.expose {
   552  			if _, ok := container.ExposedPorts[port]; ok {
   553  				shouldSkip = false
   554  				break
   555  			}
   556  		}
   557  		if shouldSkip {
   558  			return excludeContainer
   559  		}
   560  	}
   561  
   562  	return includeContainer
   563  }
   564  
   565  // refreshImage checks if the Image ref still points to the correct ID, and updates the ref to the actual ID when it doesn't
   566  func (daemon *Daemon) refreshImage(s *container.Snapshot, ctx *listContext) (*types.Container, error) {
   567  	c := s.Container
   568  	image := s.Image // keep the original ref if still valid (hasn't changed)
   569  	if image != s.ImageID {
   570  		id, _, err := daemon.GetImageIDAndOS(image)
   571  		if _, isDNE := err.(errImageDoesNotExist); err != nil && !isDNE {
   572  			return nil, err
   573  		}
   574  		if err != nil || id.String() != s.ImageID {
   575  			// ref changed, we need to use original ID
   576  			image = s.ImageID
   577  		}
   578  	}
   579  	c.Image = image
   580  	return &c, nil
   581  }
   582  
   583  // Volumes lists known volumes, using the filter to restrict the range
   584  // of volumes returned.
   585  func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, []string, error) {
   586  	var (
   587  		volumesOut []*types.Volume
   588  	)
   589  	volFilters, err := filters.FromJSON(filter)
   590  	if err != nil {
   591  		return nil, nil, err
   592  	}
   593  
   594  	if err := volFilters.Validate(acceptedVolumeFilterTags); err != nil {
   595  		return nil, nil, err
   596  	}
   597  
   598  	volumes, warnings, err := daemon.volumes.List()
   599  	if err != nil {
   600  		return nil, nil, err
   601  	}
   602  
   603  	filterVolumes, err := daemon.filterVolumes(volumes, volFilters)
   604  	if err != nil {
   605  		return nil, nil, err
   606  	}
   607  	for _, v := range filterVolumes {
   608  		apiV := volumeToAPIType(v)
   609  		if vv, ok := v.(interface {
   610  			CachedPath() string
   611  		}); ok {
   612  			apiV.Mountpoint = vv.CachedPath()
   613  		} else {
   614  			apiV.Mountpoint = v.Path()
   615  		}
   616  		volumesOut = append(volumesOut, apiV)
   617  	}
   618  	return volumesOut, warnings, nil
   619  }
   620  
   621  // filterVolumes filters volume list according to user specified filter
   622  // and returns user chosen volumes
   623  func (daemon *Daemon) filterVolumes(vols []volume.Volume, filter filters.Args) ([]volume.Volume, error) {
   624  	// if filter is empty, return original volume list
   625  	if filter.Len() == 0 {
   626  		return vols, nil
   627  	}
   628  
   629  	var retVols []volume.Volume
   630  	for _, vol := range vols {
   631  		if filter.Contains("name") {
   632  			if !filter.Match("name", vol.Name()) {
   633  				continue
   634  			}
   635  		}
   636  		if filter.Contains("driver") {
   637  			if !filter.ExactMatch("driver", vol.DriverName()) {
   638  				continue
   639  			}
   640  		}
   641  		if filter.Contains("label") {
   642  			v, ok := vol.(volume.DetailedVolume)
   643  			if !ok {
   644  				continue
   645  			}
   646  			if !filter.MatchKVList("label", v.Labels()) {
   647  				continue
   648  			}
   649  		}
   650  		retVols = append(retVols, vol)
   651  	}
   652  	danglingOnly := false
   653  	if filter.Contains("dangling") {
   654  		if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") {
   655  			danglingOnly = true
   656  		} else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") {
   657  			return nil, invalidFilter{"dangling", filter.Get("dangling")}
   658  		}
   659  		retVols = daemon.volumes.FilterByUsed(retVols, !danglingOnly)
   660  	}
   661  	return retVols, nil
   662  }
   663  
   664  func populateImageFilterByParents(ancestorMap map[image.ID]bool, imageID image.ID, getChildren func(image.ID) []image.ID) {
   665  	if !ancestorMap[imageID] {
   666  		for _, id := range getChildren(imageID) {
   667  			populateImageFilterByParents(ancestorMap, id, getChildren)
   668  		}
   669  		ancestorMap[imageID] = true
   670  	}
   671  }