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