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