package daemon import ( "fmt" "sort" "strconv" "strings" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/container" "github.com/docker/docker/image" "github.com/docker/docker/volume" "github.com/docker/go-connections/nat" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) var acceptedVolumeFilterTags = map[string]bool{ "dangling": true, "name": true, "driver": true, "label": true, } var acceptedPsFilterTags = map[string]bool{ "ancestor": true, "before": true, "exited": true, "id": true, "isolation": true, "label": true, "name": true, "status": true, "health": true, "since": true, "volume": true, "network": true, "is-task": true, "publish": true, "expose": true, } // 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. type containerReducer func(*container.Snapshot, *listContext) (*types.Container, error) 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") // List returns an array of all containers registered in the daemon. func (daemon *Daemon) List() []*container.Container { return daemon.containers.List() } // listContext is the daemon generated filtering to iterate over containers. // This is created based on the user specification from types.ContainerListOptions. 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 images map[image.ID]bool // 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 // beforeFilter is a filter to ignore containers that appear before the one given beforeFilter *container.Snapshot // sinceFilter is a filter to stop the filtering when the iterator arrive to the given container sinceFilter *container.Snapshot // 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 // 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 // ContainerListOptions is the filters set by the user *types.ContainerListOptions } // byCreatedDescending is a temporary type used to sort a list of containers by creation time. type byCreatedDescending []container.Snapshot 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() } // Containers returns the list of containers to show given the user's filtering. func (daemon *Daemon) Containers(config *types.ContainerListOptions) ([]*types.Container, error) { return daemon.reduceContainers(config, daemon.refreshImage) } func (daemon *Daemon) filterByNameIDMatches(view container.View, ctx *listContext) ([]container.Snapshot, error) { 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 all, err := view.All() sort.Sort(byCreatedDescending(all)) return all, err } // 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 } } } } cntrs := make([]container.Snapshot, 0, len(matches)) for id := range matches { c, err := view.Get(id) switch err.(type) { case nil: cntrs = append(cntrs, *c) case container.NoSuchContainerError: // ignore error default: return nil, err } } // Restore sort-order after filtering // Created gives us nanosec resolution for sorting sort.Sort(byCreatedDescending(cntrs)) return cntrs, nil } // reduceContainers parses the user's filtering options and generates the list of containers to return based on a reducer. func (daemon *Daemon) reduceContainers(config *types.ContainerListOptions, reducer containerReducer) ([]*types.Container, error) { var ( view = daemon.containersReplica.Snapshot() containers = []*types.Container{} ) ctx, err := daemon.foldFilter(view, config) if err != nil { return nil, err } // fastpath to only look at a subset of containers if specific name // or ID matches were provided by the user--otherwise we potentially // end up querying many more containers than intended containerList, err := daemon.filterByNameIDMatches(view, ctx) if err != nil { return nil, err } for i := range containerList { t, err := daemon.reducePsContainer(&containerList[i], ctx, reducer) if err != nil { if err != errStopIteration { return nil, err } break } if t != nil { containers = append(containers, t) ctx.idx++ } } return containers, nil } // reducePsContainer is the basic representation for a container as expected by the ps command. func (daemon *Daemon) reducePsContainer(container *container.Snapshot, ctx *listContext, reducer containerReducer) (*types.Container, error) { // filter containers to return switch includeContainerInList(container, ctx) { case excludeContainer: return nil, nil case stopIteration: return nil, errStopIteration } // transform internal container struct into api structs 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 } // foldFilter generates the container filter based on the user's filtering options. func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerListOptions) (*listContext, error) { psFilters := config.Filters if err := psFilters.Validate(acceptedPsFilterTags); err != nil { return nil, err } var filtExited []int err := psFilters.WalkValues("exited", func(value string) error { code, err := strconv.Atoi(value) if err != nil { return err } filtExited = append(filtExited, code) return nil }) if err != nil { return nil, err } err = psFilters.WalkValues("status", func(value string) error { if !container.IsValidStateString(value) { return invalidFilter{"status", value} } config.All = true return nil }) if err != nil { return nil, err } var taskFilter, isTask bool if psFilters.Contains("is-task") { if psFilters.ExactMatch("is-task", "true") { taskFilter = true isTask = true } else if psFilters.ExactMatch("is-task", "false") { taskFilter = true isTask = false } else { return nil, invalidFilter{"is-task", psFilters.Get("is-task")} } } err = psFilters.WalkValues("health", func(value string) error { if !container.IsValidHealthString(value) { return validationError{errors.Errorf("Unrecognised filter value for health: %s", value)} } return nil }) if err != nil { return nil, err } var beforeContFilter, sinceContFilter *container.Snapshot err = psFilters.WalkValues("before", func(value string) error { beforeContFilter, err = view.Get(value) return err }) if err != nil { return nil, err } err = psFilters.WalkValues("since", func(value string) error { sinceContFilter, err = view.Get(value) return err }) if err != nil { return nil, err } imagesFilter := map[image.ID]bool{} var ancestorFilter bool if psFilters.Contains("ancestor") { ancestorFilter = true psFilters.WalkValues("ancestor", func(ancestor string) error { id, os, err := daemon.GetImageIDAndOS(ancestor) if err != nil { logrus.Warnf("Error while looking up for image %v", ancestor) return nil } if imagesFilter[id] { // Already seen this ancestor, skip it return nil } // Then walk down the graph and put the imageIds in imagesFilter populateImageFilterByParents(imagesFilter, id, daemon.stores[os].imageStore.Children) return nil }) } publishFilter := map[nat.Port]bool{} err = psFilters.WalkValues("publish", portOp("publish", publishFilter)) if err != nil { return nil, err } exposeFilter := map[nat.Port]bool{} err = psFilters.WalkValues("expose", portOp("expose", exposeFilter)) if err != nil { return nil, err } return &listContext{ filters: psFilters, ancestorFilter: ancestorFilter, images: imagesFilter, exitAllowed: filtExited, beforeFilter: beforeContFilter, sinceFilter: sinceContFilter, taskFilter: taskFilter, isTask: isTask, publish: publishFilter, expose: exposeFilter, ContainerListOptions: config, names: view.GetAllNames(), }, nil } 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 /[] or /[] 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 } } // includeContainerInList decides whether a container should be included in the output or not based in the filter. // It also decides if the iteration should be stopped or not. func includeContainerInList(container *container.Snapshot, ctx *listContext) iterationAction { // 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 } } // Do not include container if it's stopped and we're not filters if !container.Running && !ctx.All && ctx.Limit <= 0 { return excludeContainer } // Do not include container if the name doesn't match if !ctx.filters.Match("name", container.Name) { return excludeContainer } // Do not include container if the id doesn't match if !ctx.filters.Match("id", container.ID) { return excludeContainer } if ctx.taskFilter { if ctx.isTask != container.Managed { return excludeContainer } } // Do not include container if any of the labels don't match if !ctx.filters.MatchKVList("label", container.Labels) { return excludeContainer } // Do not include container if isolation doesn't match if excludeContainer == excludeByIsolation(container, ctx) { return excludeContainer } // Stop iteration when the index is over the limit if ctx.Limit > 0 && ctx.idx == ctx.Limit { return stopIteration } // 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 { if code == container.ExitCode && !container.Running && !container.StartedAt.IsZero() { shouldSkip = false break } } if shouldSkip { return excludeContainer } } // Do not include container if its status doesn't match the filter if !ctx.filters.Match("status", container.State) { return excludeContainer } // Do not include container if its health doesn't match the filter if !ctx.filters.ExactMatch("health", container.Health) { return excludeContainer } if ctx.filters.Contains("volume") { volumesByName := make(map[string]types.MountPoint) for _, m := range container.Mounts { if m.Name != "" { volumesByName[m.Name] = m } else { volumesByName[m.Source] = m } } volumesByDestination := make(map[string]types.MountPoint) for _, m := range container.Mounts { if m.Destination != "" { volumesByDestination[m.Destination] = m } } volumeExist := fmt.Errorf("volume mounted in container") err := ctx.filters.WalkValues("volume", func(value string) error { if _, exist := volumesByDestination[value]; exist { return volumeExist } if _, exist := volumesByName[value]; exist { return volumeExist } return nil }) if err != volumeExist { return excludeContainer } } if ctx.ancestorFilter { if len(ctx.images) == 0 { return excludeContainer } if !ctx.images[image.ID(container.ImageID)] { return excludeContainer } } var ( networkExist = errors.New("container part of network") noNetworks = errors.New("container is not part of any networks") ) if ctx.filters.Contains("network") { err := ctx.filters.WalkValues("network", func(value string) error { if container.NetworkSettings == nil { return noNetworks } if _, ok := container.NetworkSettings.Networks[value]; ok { return networkExist } for _, nw := range container.NetworkSettings.Networks { if nw == nil { continue } if strings.HasPrefix(nw.NetworkID, value) { return networkExist } } return nil }) if err != networkExist { return excludeContainer } } if len(ctx.publish) > 0 { shouldSkip := true for port := range ctx.publish { if _, ok := container.PortBindings[port]; ok { shouldSkip = false break } } if shouldSkip { return excludeContainer } } if len(ctx.expose) > 0 { shouldSkip := true for port := range ctx.expose { if _, ok := container.ExposedPorts[port]; ok { shouldSkip = false break } } if shouldSkip { return excludeContainer } } return includeContainer } // 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 { id, _, err := daemon.GetImageIDAndOS(image) if _, isDNE := err.(errImageDoesNotExist); err != nil && !isDNE { return nil, err } if err != nil || id.String() != s.ImageID { // ref changed, we need to use original ID image = s.ImageID } } c.Image = image return &c, nil } // Volumes lists known volumes, using the filter to restrict the range // of volumes returned. func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, []string, error) { var ( volumesOut []*types.Volume ) volFilters, err := filters.FromJSON(filter) if err != nil { return nil, nil, err } if err := volFilters.Validate(acceptedVolumeFilterTags); err != nil { return nil, nil, err } volumes, warnings, err := daemon.volumes.List() if err != nil { return nil, nil, err } filterVolumes, err := daemon.filterVolumes(volumes, volFilters) if err != nil { return nil, nil, err } for _, v := range filterVolumes { apiV := volumeToAPIType(v) if vv, ok := v.(interface { CachedPath() string }); ok { apiV.Mountpoint = vv.CachedPath() } else { apiV.Mountpoint = v.Path() } volumesOut = append(volumesOut, apiV) } return volumesOut, warnings, nil } // 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 { if filter.Contains("name") { if !filter.Match("name", vol.Name()) { continue } } if filter.Contains("driver") { if !filter.ExactMatch("driver", vol.DriverName()) { continue } } if filter.Contains("label") { v, ok := vol.(volume.DetailedVolume) if !ok { continue } if !filter.MatchKVList("label", v.Labels()) { continue } } retVols = append(retVols, vol) } danglingOnly := false if filter.Contains("dangling") { if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") { danglingOnly = true } else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") { return nil, invalidFilter{"dangling", filter.Get("dangling")} } retVols = daemon.volumes.FilterByUsed(retVols, !danglingOnly) } return retVols, nil } func populateImageFilterByParents(ancestorMap map[image.ID]bool, imageID image.ID, getChildren func(image.ID) []image.ID) { if !ancestorMap[imageID] { for _, id := range getChildren(imageID) { populateImageFilterByParents(ancestorMap, id, getChildren) } ancestorMap[imageID] = true } }