package daemon // import "github.com/docker/docker/daemon" import ( "context" "fmt" "sort" "strconv" "strings" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" imagetypes "github.com/docker/docker/api/types/image" "github.com/docker/docker/container" "github.com/docker/docker/daemon/images" "github.com/docker/docker/errdefs" "github.com/docker/docker/image" "github.com/docker/go-connections/nat" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) 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(context.Context, *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 arrives to the given container sinceFilter *container.Snapshot // taskFilter tells if we should filter based on whether a container is part of a task taskFilter bool // isTask tells us if we should filter container that is 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(ctx context.Context, config *types.ContainerListOptions) ([]*types.Container, error) { return daemon.reduceContainers(ctx, config, daemon.refreshImage) } func (daemon *Daemon) filterByNameIDMatches(view *container.View, filter *listContext) ([]container.Snapshot, error) { idSearch := false names := filter.filters.Get("name") ids := filter.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() if err != nil { return nil, err } sort.Sort(byCreatedDescending(all)) return all, nil } // 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.containersReplica.GetByPrefix(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 filter.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 { // match both on container name with, and without slash-prefix if filter.filters.Match("name", eachName) || filter.filters.Match("name", strings.TrimPrefix(eachName, "/")) { matches[id] = true } } } } cntrs := make([]container.Snapshot, 0, len(matches)) for id := range matches { c, err := view.Get(id) if err != nil { if errdefs.IsNotFound(err) { // ignore error continue } return nil, err } cntrs = append(cntrs, *c) } // 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(ctx context.Context, config *types.ContainerListOptions, reducer containerReducer) ([]*types.Container, error) { if err := config.Filters.Validate(acceptedPsFilterTags); err != nil { return nil, err } var ( view = daemon.containersReplica.Snapshot() containers = []*types.Container{} ) filter, err := daemon.foldFilter(ctx, 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, filter) if err != nil { return nil, err } for i := range containerList { t, err := daemon.reducePsContainer(ctx, &containerList[i], filter, reducer) if err != nil { if err != errStopIteration { return nil, err } break } if t != nil { containers = append(containers, t) filter.idx++ } } return containers, nil } // reducePsContainer is the basic representation for a container as expected by the ps command. func (daemon *Daemon) reducePsContainer(ctx context.Context, container *container.Snapshot, filter *listContext, reducer containerReducer) (*types.Container, error) { // filter containers to return switch includeContainerInList(container, filter) { case excludeContainer: return nil, nil case stopIteration: return nil, errStopIteration } // transform internal container struct into api structs newC, err := reducer(ctx, container, filter) if err != nil { return nil, err } // release lock because size calculation is slow if filter.Size { sizeRw, sizeRootFs, err := daemon.imageService.GetContainerLayerSize(ctx, newC.ID) if err != nil { return nil, err } 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(ctx context.Context, view *container.View, config *types.ContainerListOptions) (*listContext, error) { psFilters := config.Filters var filtExited []int err := psFilters.WalkValues("exited", func(value string) error { code, err := strconv.Atoi(value) if err != nil { return errdefs.InvalidParameter(errors.Wrapf(err, "invalid filter 'exited=%s'", value)) } 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 errdefs.InvalidParameter(fmt.Errorf("invalid filter 'status=%s'", value)) } config.All = true return nil }) if err != nil { return nil, err } taskFilter := psFilters.Contains("is-task") isTask, err := psFilters.GetBoolOrDefault("is-task", false) if err != nil { return nil, err } err = psFilters.WalkValues("health", func(value string) error { if !container.IsValidHealthString(value) { return errdefs.InvalidParameter(fmt.Errorf("unrecognized 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 = idOrNameFilter(view, value) return err }) if err != nil { return nil, err } err = psFilters.WalkValues("since", func(value string) error { sinceContFilter, err = idOrNameFilter(view, value) return err }) if err != nil { return nil, err } imagesFilter := map[image.ID]bool{} var ancestorFilter bool if psFilters.Contains("ancestor") { ancestorFilter = true err := psFilters.WalkValues("ancestor", func(ancestor string) error { img, err := daemon.imageService.GetImage(ctx, ancestor, imagetypes.GetImageOpts{}) if err != nil { logrus.Warnf("Error while looking up for image %v", ancestor) return nil } if imagesFilter[img.ID()] { // Already seen this ancestor, skip it return nil } // Then walk down the graph and put the imageIds in imagesFilter return populateImageFilterByParents(ctx, imagesFilter, img.ID(), daemon.imageService.Children) }) if err != nil { return nil, err } } 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 idOrNameFilter(view *container.View, value string) (*container.Snapshot, error) { filter, err := view.Get(value) if err != nil && errdefs.IsNotFound(err) { // Try name search instead found := "" for id, idNames := range view.GetAllNames() { for _, eachName := range idNames { if strings.TrimPrefix(value, "/") == strings.TrimPrefix(eachName, "/") { if found != "" && found != id { return nil, err } found = id } } } if found != "" { filter, err = view.Get(found) } } return filter, err } 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, filter *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 filter.beforeFilter != nil { if container.ID == filter.beforeFilter.ID { filter.beforeFilter = nil } return excludeContainer } // Stop iteration when the container arrives to the filter container if filter.sinceFilter != nil { if container.ID == filter.sinceFilter.ID { return stopIteration } } // Do not include container if it's stopped and we're not filters if !container.Running && !filter.All && filter.Limit <= 0 { return excludeContainer } // Do not include container if the name doesn't match if !filter.filters.Match("name", container.Name) && !filter.filters.Match("name", strings.TrimPrefix(container.Name, "/")) { return excludeContainer } // Do not include container if the id doesn't match if !filter.filters.Match("id", container.ID) { return excludeContainer } if filter.taskFilter { if filter.isTask != container.Managed { return excludeContainer } } // Do not include container if any of the labels don't match if !filter.filters.MatchKVList("label", container.Labels) { return excludeContainer } // Do not include container if isolation doesn't match if excludeContainer == excludeByIsolation(container, filter) { return excludeContainer } // Stop iteration when the index is over the limit if filter.Limit > 0 && filter.idx == filter.Limit { return stopIteration } // Do not include container if its exit code is not in the filter if len(filter.exitAllowed) > 0 { shouldSkip := true for _, code := range filter.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 !filter.filters.Match("status", container.State) { return excludeContainer } // Do not include container if its health doesn't match the filter if !filter.filters.ExactMatch("health", container.Health) { return excludeContainer } if filter.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 := filter.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 filter.ancestorFilter { if len(filter.images) == 0 { return excludeContainer } if !filter.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 filter.filters.Contains("network") { err := filter.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(filter.expose) > 0 || len(filter.publish) > 0 { var ( shouldSkip = true publishedPort nat.Port exposedPort nat.Port ) for _, port := range container.Ports { publishedPort = nat.Port(fmt.Sprintf("%d/%s", port.PublicPort, port.Type)) exposedPort = nat.Port(fmt.Sprintf("%d/%s", port.PrivatePort, port.Type)) if ok := filter.publish[publishedPort]; ok { shouldSkip = false break } else if ok := filter.expose[exposedPort]; 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(ctx context.Context, s *container.Snapshot, filter *listContext) (*types.Container, error) { c := s.Container tmpImage := s.Image // keep the original ref if still valid (hasn't changed) if tmpImage != s.ImageID { img, err := daemon.imageService.GetImage(ctx, tmpImage, imagetypes.GetImageOpts{}) if _, isDNE := err.(images.ErrImageDoesNotExist); err != nil && !isDNE { return nil, err } if err != nil || img.ImageID() != s.ImageID { // ref changed, we need to use original ID tmpImage = s.ImageID } } c.Image = tmpImage return &c, nil } func populateImageFilterByParents(ctx context.Context, ancestorMap map[image.ID]bool, imageID image.ID, getChildren func(context.Context, image.ID) ([]image.ID, error)) error { if !ancestorMap[imageID] { children, err := getChildren(ctx, imageID) if err != nil { return err } for _, id := range children { if err := populateImageFilterByParents(ctx, ancestorMap, id, getChildren); err != nil { return err } } ancestorMap[imageID] = true } return nil }