github.com/vvnotw/moby@v1.13.1/daemon/list.go (about)

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