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
}
} |