package daemon import ( "errors" "fmt" "strconv" "strings" "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" "github.com/docker/docker/image" "github.com/docker/docker/pkg/graphdb" "github.com/docker/docker/pkg/nat" "github.com/docker/docker/pkg/parsers/filters" ) // 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, *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 { return daemon.containers.List() } // ContainersConfig is the filtering specified by the user to iterate over containers. type ContainersConfig struct { // if true show all containers, otherwise only running containers. All bool // show all containers created after this container id Since string // show all containers created before this container id Before string // number of containers to return at most Limit int // if true include the sizes of the containers Size bool // return only containers that match filters Filters string } // listContext is the daemon generated filtering to iterate over containers. // This is created based on the user specification. 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[string]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 // beforeContainer is a filter to ignore containers that appear before the one given beforeContainer *Container // sinceContainer is a filter to stop the filtering when the iterator arrive to the given container sinceContainer *Container // ContainersConfig is the filters set by the user *ContainersConfig } // Containers returns the list of containers to show given the user's filtering. func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container, error) { return daemon.reduceContainers(config, daemon.transformContainer) } // reduceContainer parses the user filtering and generates the list of containers to return based on a reducer. func (daemon *Daemon) reduceContainers(config *ContainersConfig, reducer containerReducer) ([]*types.Container, error) { var containers []*types.Container ctx, err := daemon.foldFilter(config) if err != nil { return nil, err } for _, container := range daemon.List() { t, err := daemon.reducePsContainer(container, 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, ctx *listContext, reducer containerReducer) (*types.Container, error) { container.Lock() defer container.Unlock() // filter containers to return action := includeContainerInList(container, ctx) switch action { case excludeContainer: return nil, nil case stopIteration: return nil, errStopIteration } // transform internal container struct into api structs return reducer(container, ctx) } // foldFilter generates the container filter based in the user's filtering options. func (daemon *Daemon) foldFilter(config *ContainersConfig) (*listContext, error) { psFilters, err := filters.FromParam(config.Filters) if err != nil { return nil, err } var filtExited []int if i, ok := psFilters["exited"]; ok { for _, value := range i { code, err := strconv.Atoi(value) if err != nil { return nil, err } filtExited = append(filtExited, code) } } if i, ok := psFilters["status"]; ok { for _, value := range i { if !isValidStateString(value) { return nil, errors.New("Unrecognised filter value for status") } if value == "exited" || value == "created" { config.All = true } } } imagesFilter := map[string]bool{} var ancestorFilter bool if ancestors, ok := psFilters["ancestor"]; ok { ancestorFilter = true byParents := daemon.Graph().ByParent() // The idea is to walk the graph down the most "efficient" way. for _, ancestor := range ancestors { // First, get the imageId of the ancestor filter (yay) image, err := daemon.Repositories().LookupImage(ancestor) if err != nil { logrus.Warnf("Error while looking up for image %v", ancestor) continue } if imagesFilter[ancestor] { // Already seen this ancestor, skip it continue } // Then walk down the graph and put the imageIds in imagesFilter populateImageFilterByParents(imagesFilter, image.ID, byParents) } } names := map[string][]string{} daemon.containerGraph().Walk("/", func(p string, e *graphdb.Entity) error { names[e.ID()] = append(names[e.ID()], p) return nil }, 1) var beforeCont, sinceCont *Container if config.Before != "" { beforeCont, err = daemon.Get(config.Before) if err != nil { return nil, err } } if config.Since != "" { sinceCont, err = daemon.Get(config.Since) if err != nil { return nil, err } } return &listContext{ filters: psFilters, ancestorFilter: ancestorFilter, names: names, images: imagesFilter, exitAllowed: filtExited, beforeContainer: beforeCont, sinceContainer: sinceCont, ContainersConfig: config, }, nil } // includeContainerInList decides whether a containers should be include in the output or not based in the filter. // It also decides if the iteration should be stopped or not. func includeContainerInList(container *Container, ctx *listContext) iterationAction { // Do not include container if it's stopped and we're not filters if !container.Running && !ctx.All && ctx.Limit <= 0 && ctx.beforeContainer == nil && ctx.sinceContainer == nil { 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 } // Do not include container if any of the labels don't match if !ctx.filters.MatchKVList("label", container.Config.Labels) { return excludeContainer } // 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.beforeContainer != nil { if container.ID == ctx.beforeContainer.ID { ctx.beforeContainer = nil } return excludeContainer } // Stop iteration when the index is over the limit if ctx.Limit > 0 && ctx.idx == ctx.Limit { return stopIteration } // Stop interation when the container arrives to the filter container if ctx.sinceContainer != nil { if container.ID == ctx.sinceContainer.ID { 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 { 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.StateString()) { return excludeContainer } if ctx.ancestorFilter { if len(ctx.images) == 0 { return excludeContainer } if !ctx.images[container.ImageID] { return excludeContainer } } return includeContainer } // transformContainer generates the container type expected by the docker ps command. func (daemon *Daemon) transformContainer(container *Container, ctx *listContext) (*types.Container, error) { newC := &types.Container{ ID: container.ID, Names: ctx.names[container.ID], } img, err := daemon.Repositories().LookupImage(container.Config.Image) if err != nil { // If the image can no longer be found by its original reference, // it makes sense to show the ID instead of a stale reference. newC.Image = container.ImageID } else if container.ImageID == img.ID { newC.Image = container.Config.Image } else { newC.Image = container.ImageID } if len(container.Args) > 0 { args := []string{} for _, arg := range container.Args { if strings.Contains(arg, " ") { args = append(args, fmt.Sprintf("'%s'", arg)) } else { args = append(args, arg) } } argsAsString := strings.Join(args, " ") newC.Command = fmt.Sprintf("%s %s", container.Path, argsAsString) } else { newC.Command = container.Path } newC.Created = container.Created.Unix() newC.Status = container.State.String() newC.HostConfig.NetworkMode = string(container.hostConfig.NetworkMode) newC.Ports = []types.Port{} for port, bindings := range container.NetworkSettings.Ports { p, err := nat.ParsePort(port.Port()) if err != nil { return nil, err } if len(bindings) == 0 { newC.Ports = append(newC.Ports, types.Port{ PrivatePort: p, Type: port.Proto(), }) continue } for _, binding := range bindings { h, err := nat.ParsePort(binding.HostPort) if err != nil { return nil, err } newC.Ports = append(newC.Ports, types.Port{ PrivatePort: p, PublicPort: h, Type: port.Proto(), IP: binding.HostIP, }) } } if ctx.Size { sizeRw, sizeRootFs := container.getSize() newC.SizeRw = sizeRw newC.SizeRootFs = sizeRootFs } newC.Labels = container.Config.Labels return newC, nil } // Volumes lists known volumes, using the filter to restrict the range // of volumes returned. func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, error) { var volumesOut []*types.Volume volFilters, err := filters.FromParam(filter) if err != nil { return nil, err } filterUsed := false if i, ok := volFilters["dangling"]; ok { if len(i) > 1 { return nil, fmt.Errorf("Conflict: cannot use more than 1 value for `dangling` filter") } filterValue := i[0] if strings.ToLower(filterValue) == "true" || filterValue == "1" { filterUsed = true } } volumes := daemon.volumes.List() for _, v := range volumes { if filterUsed && daemon.volumes.Count(v) == 0 { continue } volumesOut = append(volumesOut, volumeToAPIType(v)) } return volumesOut, nil } func populateImageFilterByParents(ancestorMap map[string]bool, imageID string, byParents map[string][]*image.Image) { if !ancestorMap[imageID] { if images, ok := byParents[imageID]; ok { for _, image := range images { populateImageFilterByParents(ancestorMap, image.ID, byParents) } } ancestorMap[imageID] = true } }