123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596 |
- package daemon
- import (
- "errors"
- "fmt"
- "strconv"
- "strings"
- "github.com/Sirupsen/logrus"
- "github.com/docker/docker/container"
- "github.com/docker/docker/image"
- "github.com/docker/docker/volume"
- "github.com/docker/engine-api/types"
- "github.com/docker/engine-api/types/filters"
- networktypes "github.com/docker/engine-api/types/network"
- "github.com/docker/go-connections/nat"
- )
- var acceptedVolumeFilterTags = map[string]bool{
- "dangling": true,
- "name": true,
- "driver": true,
- }
- var acceptedPsFilterTags = map[string]bool{
- "ancestor": true,
- "before": true,
- "exited": true,
- "id": true,
- "isolation": true,
- "label": true,
- "name": true,
- "status": true,
- "since": true,
- "volume": true,
- "network": 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.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.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
- // this is used for --filter=before= and --before=, the latter is deprecated.
- beforeFilter *container.Container
- // sinceFilter is a filter to stop the filtering when the iterator arrive to the given container
- // this is used for --filter=since= and --since=, the latter is deprecated.
- sinceFilter *container.Container
- // ContainerListOptions is the filters set by the user
- *types.ContainerListOptions
- }
- // 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.transformContainer)
- }
- // ListContainersForNode returns all containerID that match the specified nodeID
- func (daemon *Daemon) ListContainersForNode(nodeID string) []string {
- var ids []string
- for _, c := range daemon.List() {
- if c.Config.Labels["com.docker.swarm.node.id"] == nodeID {
- ids = append(ids, c.ID)
- }
- }
- return ids
- }
- func (daemon *Daemon) filterByNameIDMatches(ctx *listContext) []*container.Container {
- 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
- return daemon.List()
- }
- // 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.Container, 0, len(matches))
- for id := range matches {
- cntrs = append(cntrs, daemon.containers.Get(id))
- }
- return cntrs
- }
- // 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) {
- containers := []*types.Container{}
- ctx, err := daemon.foldFilter(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 locking and querying many more containers than intended
- containerList := daemon.filterByNameIDMatches(ctx)
- for _, container := range containerList {
- 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.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 on the user's filtering options.
- func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listContext, error) {
- psFilters := config.Filter
- 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 fmt.Errorf("Unrecognised filter value for status: %s", value)
- }
- config.All = true
- return nil
- })
- if err != nil {
- return nil, err
- }
- var beforeContFilter, sinceContFilter *container.Container
- err = psFilters.WalkValues("before", func(value string) error {
- beforeContFilter, err = daemon.GetContainer(value)
- return err
- })
- if err != nil {
- return nil, err
- }
- err = psFilters.WalkValues("since", func(value string) error {
- sinceContFilter, err = daemon.GetContainer(value)
- return err
- })
- if err != nil {
- return nil, err
- }
- imagesFilter := map[image.ID]bool{}
- var ancestorFilter bool
- if psFilters.Include("ancestor") {
- ancestorFilter = true
- psFilters.WalkValues("ancestor", func(ancestor string) error {
- id, err := daemon.GetImageID(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.imageStore.Children)
- return nil
- })
- }
- return &listContext{
- filters: psFilters,
- ancestorFilter: ancestorFilter,
- images: imagesFilter,
- exitAllowed: filtExited,
- beforeFilter: beforeContFilter,
- sinceFilter: sinceContFilter,
- ContainerListOptions: config,
- names: daemon.nameIndex.GetAll(),
- }, 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.Container, 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
- }
- // 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 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.StateString()) {
- return excludeContainer
- }
- if ctx.filters.Include("volume") {
- volumesByName := make(map[string]*volume.MountPoint)
- for _, m := range container.MountPoints {
- if m.Name != "" {
- volumesByName[m.Name] = m
- } else {
- volumesByName[m.Source] = m
- }
- }
- volumeExist := fmt.Errorf("volume mounted in container")
- err := ctx.filters.WalkValues("volume", func(value string) error {
- if _, exist := container.MountPoints[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[container.ImageID] {
- return excludeContainer
- }
- }
- networkExist := fmt.Errorf("container part of network")
- if ctx.filters.Include("network") {
- err := ctx.filters.WalkValues("network", func(value string) error {
- if _, ok := container.NetworkSettings.Networks[value]; ok {
- return networkExist
- }
- for _, nw := range container.NetworkSettings.Networks {
- if nw.NetworkID == value {
- return networkExist
- }
- }
- return nil
- })
- if err != networkExist {
- return excludeContainer
- }
- }
- return includeContainer
- }
- // transformContainer generates the container type expected by the docker ps command.
- func (daemon *Daemon) transformContainer(container *container.Container, ctx *listContext) (*types.Container, error) {
- newC := &types.Container{
- ID: container.ID,
- Names: ctx.names[container.ID],
- ImageID: container.ImageID.String(),
- }
- if newC.Names == nil {
- // Dead containers will often have no name, so make sure the response isn't null
- newC.Names = []string{}
- }
- image := container.Config.Image // if possible keep the original ref
- if image != container.ImageID.String() {
- id, err := daemon.GetImageID(image)
- if _, isDNE := err.(ErrImageDoesNotExist); err != nil && !isDNE {
- return nil, err
- }
- if err != nil || id != container.ImageID {
- image = container.ImageID.String()
- }
- }
- newC.Image = image
- 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.State = container.State.StateString()
- newC.Status = container.State.String()
- newC.HostConfig.NetworkMode = string(container.HostConfig.NetworkMode)
- // copy networks to avoid races
- networks := make(map[string]*networktypes.EndpointSettings)
- for name, network := range container.NetworkSettings.Networks {
- if network == nil {
- continue
- }
- networks[name] = &networktypes.EndpointSettings{
- EndpointID: network.EndpointID,
- Gateway: network.Gateway,
- IPAddress: network.IPAddress,
- IPPrefixLen: network.IPPrefixLen,
- IPv6Gateway: network.IPv6Gateway,
- GlobalIPv6Address: network.GlobalIPv6Address,
- GlobalIPv6PrefixLen: network.GlobalIPv6PrefixLen,
- MacAddress: network.MacAddress,
- NetworkID: network.NetworkID,
- }
- if network.IPAMConfig != nil {
- networks[name].IPAMConfig = &networktypes.EndpointIPAMConfig{
- IPv4Address: network.IPAMConfig.IPv4Address,
- IPv6Address: network.IPAMConfig.IPv6Address,
- }
- }
- }
- newC.NetworkSettings = &types.SummaryNetworkSettings{Networks: networks}
- 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 := daemon.getSize(container)
- newC.SizeRw = sizeRw
- newC.SizeRootFs = sizeRootFs
- }
- newC.Labels = container.Config.Labels
- newC.Mounts = addMountPoints(container)
- 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, []string, error) {
- var (
- volumesOut []*types.Volume
- )
- volFilters, err := filters.FromParam(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.Include("name") {
- if !filter.Match("name", vol.Name()) {
- continue
- }
- }
- if filter.Include("driver") {
- if !filter.Match("driver", vol.DriverName()) {
- continue
- }
- }
- retVols = append(retVols, vol)
- }
- danglingOnly := false
- if filter.Include("dangling") {
- if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") {
- danglingOnly = true
- } else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") {
- return nil, fmt.Errorf("Invalid filter 'dangling=%s'", 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
- }
- }
|