daemon/list.go
d0370076
 package daemon
74fdadc8
 
 import (
 	"fmt"
3f971335
 	"sort"
84146719
 	"strconv"
74fdadc8
 	"strings"
 
91e197d6
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
6bb0d181
 	"github.com/docker/docker/container"
d453fe35
 	"github.com/docker/docker/errdefs"
c1af0ac0
 	"github.com/docker/docker/image"
bd4fb00f
 	"github.com/docker/docker/volume"
056e7449
 	"github.com/docker/go-connections/nat"
ebcb7d6b
 	"github.com/pkg/errors"
1009e6a4
 	"github.com/sirupsen/logrus"
74fdadc8
 )
 
66c253cb
 var acceptedVolumeFilterTags = map[string]bool{
 	"dangling": true,
8e9305ef
 	"name":     true,
 	"driver":   true,
1a72934c
 	"label":    true,
66c253cb
 }
 
8a90e8a1
 var acceptedPsFilterTags = map[string]bool{
 	"ancestor":  true,
 	"before":    true,
 	"exited":    true,
 	"id":        true,
 	"isolation": true,
 	"label":     true,
 	"name":      true,
 	"status":    true,
1a149a0e
 	"health":    true,
8a90e8a1
 	"since":     true,
 	"volume":    true,
912af1ae
 	"network":   true,
5280ba83
 	"is-task":   true,
743943f6
 	"publish":   true,
 	"expose":    true,
8a90e8a1
 }
 
06699f73
 // iterationAction represents possible outcomes happening during the container iteration.
 type iterationAction int
 
 // containerReducer represents a reducer for a container.
 // Returns the object to serialize by the api.
8e425ebc
 type containerReducer func(*container.Snapshot, *listContext) (*types.Container, error)
06699f73
 
 const (
 	// includeContainer is the action to include a container in the reducer.
 	includeContainer iterationAction = iota
 	// excludeContainer is the action to exclude a container in the reducer.
 	excludeContainer
 	// stopIteration is the action to stop iterating over the list of containers.
 	stopIteration
 )
 
 // errStopIteration makes the iterator to stop without returning an error.
 var errStopIteration = errors.New("container list iteration stopped")
 
d0370076
 // List returns an array of all containers registered in the daemon.
6bb0d181
 func (daemon *Daemon) List() []*container.Container {
d0370076
 	return daemon.containers.List()
 }
 
06699f73
 // listContext is the daemon generated filtering to iterate over containers.
06d8f504
 // This is created based on the user specification from types.ContainerListOptions.
06699f73
 type listContext struct {
 	// idx is the container iteration index for this context
 	idx int
 	// ancestorFilter tells whether it should check ancestors or not
 	ancestorFilter bool
 	// names is a list of container names to filter with
 	names map[string][]string
 	// images is a list of images to filter with
4352da78
 	images map[image.ID]bool
06699f73
 	// filters is a collection of arguments to filter with, specified by the user
 	filters filters.Args
 	// exitAllowed is a list of exit codes allowed to filter with
 	exitAllowed []int
b41dba58
 
1921c629
 	// beforeFilter is a filter to ignore containers that appear before the one given
8e425ebc
 	beforeFilter *container.Snapshot
1921c629
 	// sinceFilter is a filter to stop the filtering when the iterator arrive to the given container
8e425ebc
 	sinceFilter *container.Snapshot
5280ba83
 
 	// taskFilter tells if we should filter based on wether a container is part of a task
 	taskFilter bool
 	// isTask tells us if the we should filter container that are a task (true) or not (false)
 	isTask bool
743943f6
 
 	// publish is a list of published ports to filter with
 	publish map[nat.Port]bool
 	// expose is a list of exposed ports to filter with
 	expose map[nat.Port]bool
 
06d8f504
 	// ContainerListOptions is the filters set by the user
 	*types.ContainerListOptions
06699f73
 }
 
edad5270
 // byCreatedDescending is a temporary type used to sort a list of containers by creation time.
 type byCreatedDescending []container.Snapshot
3f971335
 
edad5270
 func (r byCreatedDescending) Len() int      { return len(r) }
 func (r byCreatedDescending) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
 func (r byCreatedDescending) Less(i, j int) bool {
 	return r[j].CreatedAt.UnixNano() < r[i].CreatedAt.UnixNano()
3f971335
 }
 
06699f73
 // Containers returns the list of containers to show given the user's filtering.
06d8f504
 func (daemon *Daemon) Containers(config *types.ContainerListOptions) ([]*types.Container, error) {
edad5270
 	return daemon.reduceContainers(config, daemon.refreshImage)
06699f73
 }
 
aacddda8
 func (daemon *Daemon) filterByNameIDMatches(view container.View, ctx *listContext) ([]container.Snapshot, error) {
8e4a4514
 	idSearch := false
 	names := ctx.filters.Get("name")
 	ids := ctx.filters.Get("id")
 	if len(names)+len(ids) == 0 {
 		// if name or ID filters are not in use, return to
 		// standard behavior of walking the entire container
 		// list from the daemon's in-memory store
8e425ebc
 		all, err := view.All()
edad5270
 		sort.Sort(byCreatedDescending(all))
8e425ebc
 		return all, err
8e4a4514
 	}
 
 	// idSearch will determine if we limit name matching to the IDs
 	// matched from any IDs which were specified as filters
 	if len(ids) > 0 {
 		idSearch = true
 	}
 
 	matches := make(map[string]bool)
 	// find ID matches; errors represent "not found" and can be ignored
 	for _, id := range ids {
 		if fullID, err := daemon.idIndex.Get(id); err == nil {
 			matches[fullID] = true
 		}
 	}
 
 	// look for name matches; if ID filtering was used, then limit the
 	// search space to the matches map only; errors represent "not found"
 	// and can be ignored
 	if len(names) > 0 {
 		for id, idNames := range ctx.names {
 			// if ID filters were used and no matches on that ID were
 			// found, continue to next ID in the list
 			if idSearch && !matches[id] {
 				continue
 			}
 			for _, eachName := range idNames {
 				if ctx.filters.Match("name", eachName) {
 					matches[id] = true
 				}
 			}
 		}
 	}
 
8e425ebc
 	cntrs := make([]container.Snapshot, 0, len(matches))
8e4a4514
 	for id := range matches {
8e425ebc
 		c, err := view.Get(id)
d257a63f
 		switch err.(type) {
 		case nil:
8e425ebc
 			cntrs = append(cntrs, *c)
d257a63f
 		case container.NoSuchContainerError:
 			// ignore error
 		default:
 			return nil, err
a020ec4c
 		}
8e4a4514
 	}
3f971335
 
 	// Restore sort-order after filtering
 	// Created gives us nanosec resolution for sorting
edad5270
 	sort.Sort(byCreatedDescending(cntrs))
3f971335
 
8e425ebc
 	return cntrs, nil
8e4a4514
 }
 
dd93571c
 // reduceContainers parses the user's filtering options and generates the list of containers to return based on a reducer.
06d8f504
 func (daemon *Daemon) reduceContainers(config *types.ContainerListOptions, reducer containerReducer) ([]*types.Container, error) {
3343d234
 	var (
1128fc1a
 		view       = daemon.containersReplica.Snapshot()
3343d234
 		containers = []*types.Container{}
 	)
06699f73
 
8e425ebc
 	ctx, err := daemon.foldFilter(view, config)
06699f73
 	if err != nil {
 		return nil, err
 	}
 
8e4a4514
 	// fastpath to only look at a subset of containers if specific name
 	// or ID matches were provided by the user--otherwise we potentially
8e425ebc
 	// end up querying many more containers than intended
 	containerList, err := daemon.filterByNameIDMatches(view, ctx)
 	if err != nil {
 		return nil, err
 	}
8e4a4514
 
8e425ebc
 	for i := range containerList {
 		t, err := daemon.reducePsContainer(&containerList[i], ctx, reducer)
06699f73
 		if err != nil {
 			if err != errStopIteration {
 				return nil, err
 			}
 			break
 		}
 		if t != nil {
 			containers = append(containers, t)
b08f071e
 			ctx.idx++
06699f73
 		}
 	}
3343d234
 
06699f73
 	return containers, nil
 }
 
 // reducePsContainer is the basic representation for a container as expected by the ps command.
8e425ebc
 func (daemon *Daemon) reducePsContainer(container *container.Snapshot, ctx *listContext, reducer containerReducer) (*types.Container, error) {
06699f73
 	// filter containers to return
8e425ebc
 	switch includeContainerInList(container, ctx) {
06699f73
 	case excludeContainer:
 		return nil, nil
 	case stopIteration:
 		return nil, errStopIteration
 	}
 
 	// transform internal container struct into api structs
bd33a99a
 	newC, err := reducer(container, ctx)
 	if err != nil {
 		return nil, err
 	}
 
 	// release lock because size calculation is slow
 	if ctx.Size {
 		sizeRw, sizeRootFs := daemon.getSize(newC.ID)
 		newC.SizeRw = sizeRw
 		newC.SizeRootFs = sizeRootFs
 	}
 	return newC, nil
06699f73
 }
 
dd93571c
 // foldFilter generates the container filter based on the user's filtering options.
aacddda8
 func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerListOptions) (*listContext, error) {
89a69667
 	psFilters := config.Filters
06699f73
 
8a90e8a1
 	if err := psFilters.Validate(acceptedPsFilterTags); err != nil {
 		return nil, err
 	}
 
06699f73
 	var filtExited []int
8a90e8a1
 
06d8f504
 	err := psFilters.WalkValues("exited", func(value string) error {
93d1dd80
 		code, err := strconv.Atoi(value)
 		if err != nil {
 			return err
84146719
 		}
93d1dd80
 		filtExited = append(filtExited, code)
 		return nil
 	})
 	if err != nil {
 		return nil, err
84146719
 	}
 
93d1dd80
 	err = psFilters.WalkValues("status", func(value string) error {
6bb0d181
 		if !container.IsValidStateString(value) {
ebcb7d6b
 			return invalidFilter{"status", value}
2639e073
 		}
93d1dd80
 
 		config.All = true
 		return nil
 	})
 	if err != nil {
 		return nil, err
2639e073
 	}
c1af0ac0
 
5280ba83
 	var taskFilter, isTask bool
97c5ae25
 	if psFilters.Contains("is-task") {
5280ba83
 		if psFilters.ExactMatch("is-task", "true") {
 			taskFilter = true
 			isTask = true
 		} else if psFilters.ExactMatch("is-task", "false") {
 			taskFilter = true
 			isTask = false
 		} else {
ebcb7d6b
 			return nil, invalidFilter{"is-task", psFilters.Get("is-task")}
5280ba83
 		}
 	}
 
1a149a0e
 	err = psFilters.WalkValues("health", func(value string) error {
 		if !container.IsValidHealthString(value) {
87a12421
 			return errdefs.InvalidParameter(errors.Errorf("Unrecognised filter value for health: %s", value))
1a149a0e
 		}
 
 		return nil
 	})
 	if err != nil {
 		return nil, err
 	}
 
8e425ebc
 	var beforeContFilter, sinceContFilter *container.Snapshot
b41dba58
 
93d1dd80
 	err = psFilters.WalkValues("before", func(value string) error {
8e425ebc
 		beforeContFilter, err = view.Get(value)
93d1dd80
 		return err
 	})
 	if err != nil {
 		return nil, err
1921c629
 	}
 
93d1dd80
 	err = psFilters.WalkValues("since", func(value string) error {
8e425ebc
 		sinceContFilter, err = view.Get(value)
93d1dd80
 		return err
 	})
 	if err != nil {
 		return nil, err
1921c629
 	}
 
4352da78
 	imagesFilter := map[image.ID]bool{}
06699f73
 	var ancestorFilter bool
97c5ae25
 	if psFilters.Contains("ancestor") {
c1af0ac0
 		ancestorFilter = true
93d1dd80
 		psFilters.WalkValues("ancestor", func(ancestor string) error {
ce8e529e
 			id, _, err := daemon.GetImageIDAndOS(ancestor)
c1af0ac0
 			if err != nil {
 				logrus.Warnf("Error while looking up for image %v", ancestor)
93d1dd80
 				return nil
c1af0ac0
 			}
4352da78
 			if imagesFilter[id] {
c1af0ac0
 				// Already seen this ancestor, skip it
93d1dd80
 				return nil
c1af0ac0
 			}
 			// Then walk down the graph and put the imageIds in imagesFilter
ce8e529e
 			populateImageFilterByParents(imagesFilter, id, daemon.imageStore.Children)
93d1dd80
 			return nil
 		})
c1af0ac0
 	}
 
743943f6
 	publishFilter := map[nat.Port]bool{}
af0d9bdf
 	err = psFilters.WalkValues("publish", portOp("publish", publishFilter))
743943f6
 	if err != nil {
 		return nil, err
 	}
 
 	exposeFilter := map[nat.Port]bool{}
af0d9bdf
 	err = psFilters.WalkValues("expose", portOp("expose", exposeFilter))
743943f6
 	if err != nil {
 		return nil, err
 	}
 
06699f73
 	return &listContext{
06d8f504
 		filters:              psFilters,
 		ancestorFilter:       ancestorFilter,
 		images:               imagesFilter,
 		exitAllowed:          filtExited,
 		beforeFilter:         beforeContFilter,
 		sinceFilter:          sinceContFilter,
5280ba83
 		taskFilter:           taskFilter,
 		isTask:               isTask,
743943f6
 		publish:              publishFilter,
 		expose:               exposeFilter,
06d8f504
 		ContainerListOptions: config,
1128fc1a
 		names:                view.GetAllNames(),
06699f73
 	}, nil
 }
af0d9bdf
 func portOp(key string, filter map[nat.Port]bool) func(value string) error {
 	return func(value string) error {
 		if strings.Contains(value, ":") {
 			return fmt.Errorf("filter for '%s' should not contain ':': %s", key, value)
 		}
 		//support two formats, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
 		proto, port := nat.SplitProtoPort(value)
 		start, end, err := nat.ParsePortRange(port)
 		if err != nil {
 			return fmt.Errorf("error while looking up for %s %s: %s", key, value, err)
 		}
 		for i := start; i <= end; i++ {
 			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
 			if err != nil {
 				return fmt.Errorf("error while looking up for %s %s: %s", key, value, err)
 			}
 			filter[p] = true
 		}
 		return nil
 	}
 }
16346253
 
f1d34ac2
 // includeContainerInList decides whether a container should be included in the output or not based in the filter.
06699f73
 // It also decides if the iteration should be stopped or not.
8e425ebc
 func includeContainerInList(container *container.Snapshot, ctx *listContext) iterationAction {
bc72883f
 	// Do not include container if it's in the list before the filter container.
 	// Set the filter container to nil to include the rest of containers after this one.
 	if ctx.beforeFilter != nil {
 		if container.ID == ctx.beforeFilter.ID {
 			ctx.beforeFilter = nil
 		}
 		return excludeContainer
 	}
 
 	// Stop iteration when the container arrives to the filter container
 	if ctx.sinceFilter != nil {
 		if container.ID == ctx.sinceFilter.ID {
 			return stopIteration
 		}
 	}
 
06699f73
 	// Do not include container if it's stopped and we're not filters
91b71570
 	if !container.Running && !ctx.All && ctx.Limit <= 0 {
06699f73
 		return excludeContainer
 	}
16346253
 
06699f73
 	// Do not include container if the name doesn't match
 	if !ctx.filters.Match("name", container.Name) {
 		return excludeContainer
 	}
abb5e9a0
 
06699f73
 	// Do not include container if the id doesn't match
 	if !ctx.filters.Match("id", container.ID) {
 		return excludeContainer
 	}
 
5280ba83
 	if ctx.taskFilter {
 		if ctx.isTask != container.Managed {
 			return excludeContainer
 		}
 	}
 
06699f73
 	// Do not include container if any of the labels don't match
8e425ebc
 	if !ctx.filters.MatchKVList("label", container.Labels) {
06699f73
 		return excludeContainer
 	}
 
d4b07324
 	// Do not include container if isolation doesn't match
9c581417
 	if excludeContainer == excludeByIsolation(container, ctx) {
 		return excludeContainer
 	}
 
1921c629
 	// Stop iteration when the index is over the limit
 	if ctx.Limit > 0 && ctx.idx == ctx.Limit {
 		return stopIteration
 	}
 
06699f73
 	// Do not include container if its exit code is not in the filter
 	if len(ctx.exitAllowed) > 0 {
 		shouldSkip := true
 		for _, code := range ctx.exitAllowed {
8e425ebc
 			if code == container.ExitCode && !container.Running && !container.StartedAt.IsZero() {
06699f73
 				shouldSkip = false
 				break
74fdadc8
 			}
 		}
06699f73
 		if shouldSkip {
 			return excludeContainer
84146719
 		}
06699f73
 	}
16346253
 
06699f73
 	// Do not include container if its status doesn't match the filter
8e425ebc
 	if !ctx.filters.Match("status", container.State) {
06699f73
 		return excludeContainer
 	}
c1af0ac0
 
1a149a0e
 	// Do not include container if its health doesn't match the filter
8e425ebc
 	if !ctx.filters.ExactMatch("health", container.Health) {
1a149a0e
 		return excludeContainer
 	}
 
97c5ae25
 	if ctx.filters.Contains("volume") {
8e425ebc
 		volumesByName := make(map[string]types.MountPoint)
 		for _, m := range container.Mounts {
8451a08e
 			if m.Name != "" {
 				volumesByName[m.Name] = m
 			} else {
 				volumesByName[m.Source] = m
 			}
bd4fb00f
 		}
8e425ebc
 		volumesByDestination := make(map[string]types.MountPoint)
 		for _, m := range container.Mounts {
 			if m.Destination != "" {
 				volumesByDestination[m.Destination] = m
 			}
 		}
bd4fb00f
 
 		volumeExist := fmt.Errorf("volume mounted in container")
 		err := ctx.filters.WalkValues("volume", func(value string) error {
8e425ebc
 			if _, exist := volumesByDestination[value]; exist {
bd4fb00f
 				return volumeExist
 			}
 			if _, exist := volumesByName[value]; exist {
 				return volumeExist
 			}
 			return nil
 		})
 		if err != volumeExist {
 			return excludeContainer
 		}
 	}
 
06699f73
 	if ctx.ancestorFilter {
 		if len(ctx.images) == 0 {
 			return excludeContainer
c1af0ac0
 		}
8e425ebc
 		if !ctx.images[image.ID(container.ImageID)] {
06699f73
 			return excludeContainer
065648a8
 		}
06699f73
 	}
2167f40a
 
edad5270
 	var (
 		networkExist = errors.New("container part of network")
 		noNetworks   = errors.New("container is not part of any networks")
 	)
97c5ae25
 	if ctx.filters.Contains("network") {
912af1ae
 		err := ctx.filters.WalkValues("network", func(value string) error {
edad5270
 			if container.NetworkSettings == nil {
 				return noNetworks
 			}
7c46ba02
 			if _, ok := container.NetworkSettings.Networks[value]; ok {
912af1ae
 				return networkExist
 			}
7c46ba02
 			for _, nw := range container.NetworkSettings.Networks {
8e425ebc
 				if nw == nil {
99a98ccc
 					continue
 				}
59b70cda
 				if strings.HasPrefix(nw.NetworkID, value) {
7c46ba02
 					return networkExist
 				}
 			}
912af1ae
 			return nil
 		})
 		if err != networkExist {
 			return excludeContainer
 		}
 	}
 
743943f6
 	if len(ctx.publish) > 0 {
 		shouldSkip := true
 		for port := range ctx.publish {
edad5270
 			if _, ok := container.PortBindings[port]; ok {
743943f6
 				shouldSkip = false
 				break
 			}
 		}
 		if shouldSkip {
 			return excludeContainer
 		}
 	}
 
 	if len(ctx.expose) > 0 {
 		shouldSkip := true
 		for port := range ctx.expose {
8e425ebc
 			if _, ok := container.ExposedPorts[port]; ok {
743943f6
 				shouldSkip = false
 				break
 			}
 		}
 		if shouldSkip {
 			return excludeContainer
 		}
 	}
 
06699f73
 	return includeContainer
 }
2167f40a
 
edad5270
 // refreshImage checks if the Image ref still points to the correct ID, and updates the ref to the actual ID when it doesn't
 func (daemon *Daemon) refreshImage(s *container.Snapshot, ctx *listContext) (*types.Container, error) {
 	c := s.Container
 	image := s.Image // keep the original ref if still valid (hasn't changed)
 	if image != s.ImageID {
0380fbff
 		id, _, err := daemon.GetImageIDAndOS(image)
ebcb7d6b
 		if _, isDNE := err.(errImageDoesNotExist); err != nil && !isDNE {
4352da78
 			return nil, err
 		}
edad5270
 		if err != nil || id.String() != s.ImageID {
 			// ref changed, we need to use original ID
 			image = s.ImageID
4352da78
 		}
06699f73
 	}
edad5270
 	c.Image = image
 	return &c, nil
74fdadc8
 }
b3b7eb27
 
abd72d40
 // Volumes lists known volumes, using the filter to restrict the range
 // of volumes returned.
d3eca445
 func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, []string, error) {
66c253cb
 	var (
8e9305ef
 		volumesOut []*types.Volume
66c253cb
 	)
a4efe66c
 	volFilters, err := filters.FromJSON(filter)
b3b7eb27
 	if err != nil {
d3eca445
 		return nil, nil, err
b3b7eb27
 	}
 
66c253cb
 	if err := volFilters.Validate(acceptedVolumeFilterTags); err != nil {
 		return nil, nil, err
 	}
 
d3eca445
 	volumes, warnings, err := daemon.volumes.List()
ed2d300e
 	if err != nil {
 		return nil, nil, err
 	}
 
8e9305ef
 	filterVolumes, err := daemon.filterVolumes(volumes, volFilters)
d3eca445
 	if err != nil {
 		return nil, nil, err
 	}
8e9305ef
 	for _, v := range filterVolumes {
9e6b1852
 		apiV := volumeToAPIType(v)
 		if vv, ok := v.(interface {
 			CachedPath() string
 		}); ok {
 			apiV.Mountpoint = vv.CachedPath()
 		} else {
 			apiV.Mountpoint = v.Path()
 		}
 		volumesOut = append(volumesOut, apiV)
b3b7eb27
 	}
d3eca445
 	return volumesOut, warnings, nil
b3b7eb27
 }
c1af0ac0
 
8e9305ef
 // filterVolumes filters volume list according to user specified filter
 // and returns user chosen volumes
 func (daemon *Daemon) filterVolumes(vols []volume.Volume, filter filters.Args) ([]volume.Volume, error) {
 	// if filter is empty, return original volume list
 	if filter.Len() == 0 {
 		return vols, nil
 	}
 
 	var retVols []volume.Volume
 	for _, vol := range vols {
97c5ae25
 		if filter.Contains("name") {
8e9305ef
 			if !filter.Match("name", vol.Name()) {
 				continue
 			}
 		}
97c5ae25
 		if filter.Contains("driver") {
0fdab496
 			if !filter.ExactMatch("driver", vol.DriverName()) {
8e9305ef
 				continue
 			}
 		}
97c5ae25
 		if filter.Contains("label") {
9ce8aac5
 			v, ok := vol.(volume.DetailedVolume)
6d787dae
 			if !ok {
 				continue
1a72934c
 			}
6d787dae
 			if !filter.MatchKVList("label", v.Labels()) {
 				continue
1a72934c
 			}
 		}
8e9305ef
 		retVols = append(retVols, vol)
 	}
 	danglingOnly := false
97c5ae25
 	if filter.Contains("dangling") {
8e9305ef
 		if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") {
 			danglingOnly = true
 		} else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") {
ebcb7d6b
 			return nil, invalidFilter{"dangling", filter.Get("dangling")}
8e9305ef
 		}
 		retVols = daemon.volumes.FilterByUsed(retVols, !danglingOnly)
 	}
 	return retVols, nil
 }
 
4352da78
 func populateImageFilterByParents(ancestorMap map[image.ID]bool, imageID image.ID, getChildren func(image.ID) []image.ID) {
c1af0ac0
 	if !ancestorMap[imageID] {
4352da78
 		for _, id := range getChildren(imageID) {
 			populateImageFilterByParents(ancestorMap, id, getChildren)
c1af0ac0
 		}
 		ancestorMap[imageID] = true
 	}
 }