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